<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Linux | AkitaOnRails.com</title><link>https://akitaonrails.github.io/en/tags/linux/</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:51 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/en/tags/linux/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>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>Bypassing the GitHub API Block in Brazil</title><link>https://akitaonrails.github.io/en/2026/06/11/bypassing-github-api-block-brazil/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/06/11/bypassing-github-api-block-brazil/</guid><pubDate>Thu, 11 Jun 2026 20:00:00 -0300</pubDate><description>&lt;p&gt;This is a Brazil problem today. It may be your problem tomorrow if you live under a government that likes silent network blocks, copyright panic, opaque court orders, or any other flavor of &amp;ldquo;trust us, we know what we are doing.&amp;rdquo; The concrete symptom here is simple: &lt;code&gt;api.github.com&lt;/code&gt; started timing out in Brazil. The main &lt;code&gt;github.com&lt;/code&gt; site opens. SSH clones may keep working. But tools that depend on the GitHub API, like &lt;code&gt;gh&lt;/code&gt;, CI automations, personal scripts, bots, CLIs, dashboards, and anything that queries issues and pull requests, start hanging.&lt;/p&gt;</description><content:encoded><![CDATA[<p>This is a Brazil problem today. It may be your problem tomorrow if you live under a government that likes silent network blocks, copyright panic, opaque court orders, or any other flavor of &ldquo;trust us, we know what we are doing.&rdquo; The concrete symptom here is simple: <code>api.github.com</code> started timing out in Brazil. The main <code>github.com</code> site opens. SSH clones may keep working. But tools that depend on the GitHub API, like <code>gh</code>, CI automations, personal scripts, bots, CLIs, dashboards, and anything that queries issues and pull requests, start hanging.</p>
<p><a href="https://x.com/ayubio/status/2064878070394183955"target="_blank" rel="noopener">Ayub posted on X</a> that this has all the usual signs of a national block ordered by Anatel against <code>api.github.com</code>. In that same context, the tweet he quoted showed <code>api.github.com</code> resolving to <code>4.228.31.149</code>, but with no working route. The author said he tested through VIVO, Claro, and Oracle, and that it only worked through a Miami VPN. In other words: this does not look like &ldquo;my Wi-Fi is down.&rdquo; It looks like a block somewhere in the path.</p>
<p>According to Ayub, this kind of thing often happens in blocking waves, many times after Wednesday meetings with large carriers, inside the Brazilian mechanism for blocking addresses under the excuse of fighting piracy. The problem is the blast radius: instead of taking down only the target, it breaks legitimate infrastructure. Today it was the GitHub API. Tomorrow it can be anything your work depends on.</p>
<p>And yes, I confirmed it from here.</p>
<h2>How to know if you were affected<span class="hx:absolute hx:-mt-20" id="how-to-know-if-you-were-affected"></span>
    <a href="#how-to-know-if-you-were-affected" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>First, test whether the main site opens:</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">curl -4 -I --connect-timeout <span class="m">8</span> --max-time <span class="m">12</span> https://github.com/</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>Here it worked. Then test the API:</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">curl -4 -I --connect-timeout <span class="m">8</span> --max-time <span class="m">12</span> https://api.github.com/</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>Here I got:</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">curl: (28) Connection timed out after 8002 milliseconds</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>Now check which IP your system resolves:</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">getent ahostsv4 api.github.com</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>In my case:</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">4.228.31.149    STREAM api.github.com
</span></span><span class="line"><span class="cl">4.228.31.149    DGRAM
</span></span><span class="line"><span class="cl">4.228.31.149    RAW</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 <code>gh</code> depends on this API too. So commands like these may hang:</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">gh api rate_limit
</span></span><span class="line"><span class="cl">gh pr list
</span></span><span class="line"><span class="cl">gh issue list</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 you are a developer and suddenly <code>gh</code> stopped, your release scripts stopped, your PR-listing bot stopped, or some CLI that queries issues started hanging, this may be it. It is not your bug. Your route to <code>api.github.com</code> is being thrown into a hole.</p>
<h2>First try DNS 9.9.9.9<span class="hx:absolute hx:-mt-20" id="first-try-dns-9999"></span>
    <a href="#first-try-dns-9999" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Some people mentioned that changing DNS to <a href="https://www.quad9.net/"target="_blank" rel="noopener">Quad9</a>, <code>9.9.9.9</code>, could work around it. It makes sense to test, because some blocks are done through DNS: the provider returns the wrong IP, NXDOMAIN, or sends you into a sinkhole. If the problem is only your provider&rsquo;s DNS, changing DNS fixes it.</p>
<p>Test without changing anything in the system:</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">dig @9.9.9.9 +short api.github.com A</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>In my case, it did not help. Quad9 also returned:</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">4.228.31.149</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 forcing that IP in <code>curl</code> still timed out:</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">curl -4 -I --connect-timeout <span class="m">8</span> --max-time <span class="m">12</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --resolve api.github.com:443:4.228.31.149 <span class="se">\
</span></span></span><span class="line"><span class="cl">  https://api.github.com/</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>Result:</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">curl: (28) Connection timed out after 8002 milliseconds</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>So, on my link, 9.9.9.9 did not fix it. But test yours. If your provider is only poisoning DNS, it may work.</p>
<p>On Arch with NetworkManager, it would be something 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">sudo pacman -S bind-tools curl
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">nmcli con show --active
</span></span><span class="line"><span class="cl">sudo nmcli con mod <span class="s2">&#34;YOUR CONNECTION NAME&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  ipv4.ignore-auto-dns yes <span class="se">\
</span></span></span><span class="line"><span class="cl">  ipv4.dns <span class="s2">&#34;9.9.9.9 149.112.112.112&#34;</span>
</span></span><span class="line"><span class="cl">sudo nmcli con down <span class="s2">&#34;YOUR CONNECTION NAME&#34;</span>
</span></span><span class="line"><span class="cl">sudo nmcli con up <span class="s2">&#34;YOUR CONNECTION NAME&#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>On Ubuntu, same idea. Install the tools and adjust through NetworkManager:</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 apt install dnsutils curl
</span></span><span class="line"><span class="cl">nmcli con show --active</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>Or use the GNOME/KDE network settings screen and set manual DNS to <code>9.9.9.9</code> and <code>149.112.112.112</code> for the connection. Then disconnect and reconnect.</p>
<p>Test again:</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">dig +short api.github.com A
</span></span><span class="line"><span class="cl">curl -4 -I --connect-timeout <span class="m">8</span> --max-time <span class="m">12</span> https://api.github.com/</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 it works, good. You are done here.</p>
<p>If it keeps timing out, DNS was only the first layer. Then you need to route the connection through another path.</p>
<h2>Why I do not like the fixed-IP hack<span class="hx:absolute hx:-mt-20" id="why-i-do-not-like-the-fixed-ip-hack"></span>
    <a href="#why-i-do-not-like-the-fixed-ip-hack" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Another suggestion I saw was manually adding IPs to local DNS, like Pi-hole, Unbound, <code>/etc/hosts</code>, dnsmasq, that sort of thing. Example: &ldquo;resolve <code>api.github.com</code> to this other IP that works.&rdquo;</p>
<p>That can work for a few hours. I don&rsquo;t like it.</p>
<p>GitHub is large infrastructure. IPs change. Routes change. Load balancing changes. Regions change. The IP that works for somebody in Miami may be bad for you in São Paulo. The IP that works today may fail tomorrow. And if the block is a blackhole route at the provider, changing DNS to another IP may only trade one hole for another.</p>
<p>Pi-hole is great for a home network. Unbound is great. <code>/etc/hosts</code> saves you in an emergency. But pinning the IP of a large service like GitHub is how you forget an invisible hack and lose two hours next month trying to understand why only your machine is broken.</p>
<p>Use alternate DNS as a test. Use fixed IP only as a temporary bandage and document where you changed it.</p>
<h2>Fallback: SOCKS proxy only for GitHub tools<span class="hx:absolute hx:-mt-20" id="fallback-socks-proxy-only-for-github-tools"></span>
    <a href="#fallback-socks-proxy-only-for-github-tools" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>My solution was the least invasive thing I could make: a wrapper that starts a local SOCKS proxy through Tor and runs only the command I choose through it. It does not route the whole system. It does not touch the browser. It does not affect package managers, Steam, banking, nothing. Just <code>gh</code>, or whatever tool I explicitly call.</p>
<p>In my case, the wrapper lives at <code>~/.config/zsh/bin/github-proxy</code>, and the alias in <code>~/.config/zsh/aliases</code> does 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="nb">alias</span> <span class="nv">gh</span><span class="o">=</span><span class="s1">&#39;$HOME/.config/zsh/bin/github-proxy gh&#39;</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 wrapper is 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="cp">#!/bin/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></span><span class="line"><span class="cl"><span class="nv">proxy_host</span><span class="o">=</span><span class="s2">&#34;127.0.0.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">proxy_port</span><span class="o">=</span><span class="s2">&#34;9050&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">tor_dir</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_RUNTIME_DIR</span><span class="k">:-</span><span class="p">/tmp</span><span class="si">}</span><span class="s2">/github-proxy-tor&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">tor_rc</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$tor_dir</span><span class="s2">/torrc&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">tor_log</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$tor_dir</span><span class="s2">/tor.log&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="nv">$#</span> -eq <span class="m">0</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;usage: github-proxy &lt;command&gt; [args...]&#34;</span> &gt;<span class="p">&amp;</span><span class="m">2</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">2</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> ! ss -ltn <span class="s2">&#34;sport = :</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span> <span class="p">|</span> grep -q <span class="s2">&#34;</span><span class="nv">$proxy_host</span><span class="s2">:</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  mkdir -p <span class="s2">&#34;</span><span class="nv">$tor_dir</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  : &gt;<span class="s2">&#34;</span><span class="nv">$tor_rc</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  tor -f <span class="s2">&#34;</span><span class="nv">$tor_rc</span><span class="s2">&#34;</span> --SocksPort <span class="s2">&#34;</span><span class="nv">$proxy_host</span><span class="s2">:</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span> --DataDirectory <span class="s2">&#34;</span><span class="nv">$tor_dir</span><span class="s2">/data&#34;</span> &gt;<span class="s2">&#34;</span><span class="nv">$tor_log</span><span class="s2">&#34;</span> 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">&amp;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">for</span> _ in <span class="o">{</span>1..60<span class="o">}</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> grep -q <span class="s1">&#39;Bootstrapped 100%&#39;</span> <span class="s2">&#34;</span><span class="nv">$tor_log</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">      <span class="nb">break</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> ! <span class="nb">kill</span> -0 <span class="s2">&#34;</span><span class="nv">$!</span><span class="s2">&#34;</span> 2&gt;/dev/null<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;github-proxy: tor exited before bootstrap; see </span><span class="nv">$tor_log</span><span class="s2">&#34;</span> &gt;<span class="p">&amp;</span><span class="m">2</span>
</span></span><span class="line"><span class="cl">      <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    sleep <span class="m">1</span>
</span></span><span class="line"><span class="cl">  <span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> ! grep -q <span class="s1">&#39;Bootstrapped 100%&#39;</span> <span class="s2">&#34;</span><span class="nv">$tor_log</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;github-proxy: timed out waiting for tor bootstrap; see </span><span class="nv">$tor_log</span><span class="s2">&#34;</span> &gt;<span class="p">&amp;</span><span class="m">2</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">HTTPS_PROXY</span><span class="o">=</span><span class="s2">&#34;socks5h://</span><span class="nv">$proxy_host</span><span class="s2">:</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">HTTP_PROXY</span><span class="o">=</span><span class="s2">&#34;socks5h://</span><span class="nv">$proxy_host</span><span class="s2">:</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">ALL_PROXY</span><span class="o">=</span><span class="s2">&#34;socks5h://</span><span class="nv">$proxy_host</span><span class="s2">:</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">exec</span> <span class="s2">&#34;</span><span class="nv">$@</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>Notice <code>socks5h</code>. That <code>h</code> matters. It makes DNS resolution happen through the proxy, not through your local network. If you use plain <code>socks5://</code>, depending on the library, DNS may still happen locally before the proxy is used. With <code>socks5h://</code>, the idea is: &ldquo;take the hostname to the other side and resolve it there.&rdquo;</p>
<h2>Installing on Arch<span class="hx:absolute hx:-mt-20" id="installing-on-arch"></span>
    <a href="#installing-on-arch" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>On Arch:</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 tor github-cli iproute2
</span></span><span class="line"><span class="cl">mkdir -p ~/.local/bin</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>Create the 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-bash" data-lang="bash"><span class="line"><span class="cl">nano ~/.local/bin/github-proxy</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>Paste the wrapper above and make it executable:</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">chmod +x ~/.local/bin/github-proxy</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>Test it directly:</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">~/.local/bin/github-proxy gh api rate_limit --jq .resources.core.limit</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>An authenticated account usually returns:</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">5000</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>That is what happened here. Direct timed out. Through the wrapper, <code>gh api rate_limit --jq .resources.core.limit</code> returned <code>5000</code>.</p>
<p>Now add an alias to your shell.</p>
<p>ZSH:</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">echo</span> <span class="s2">&#34;alias gh=&#39;</span><span class="nv">$HOME</span><span class="s2">/.local/bin/github-proxy gh&#39;&#34;</span> &gt;&gt; ~/.zshrc
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.zshrc</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>Bash:</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">echo</span> <span class="s2">&#34;alias gh=&#39;</span><span class="nv">$HOME</span><span class="s2">/.local/bin/github-proxy gh&#39;&#34;</span> &gt;&gt; ~/.bashrc
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.bashrc</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>Test:</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">gh api rate_limit --jq .resources.core.limit
</span></span><span class="line"><span class="cl">gh pr list</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>
<h2>Ubuntu<span class="hx:absolute hx:-mt-20" id="ubuntu"></span>
    <a href="#ubuntu" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>On Ubuntu, the Tor package is easy:</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 apt update
</span></span><span class="line"><span class="cl">sudo apt install tor iproute2 curl</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 GitHub CLI, if the <code>gh</code> package is available in your version:</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 apt install gh</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 it is not, follow the official GitHub CLI installation for Ubuntu/Debian. The rest is the same: create <code>~/.local/bin/github-proxy</code>, run <code>chmod +x</code>, and put the alias in <code>.bashrc</code> or <code>.zshrc</code>.</p>
<h2>Better alternatives than Tor<span class="hx:absolute hx:-mt-20" id="better-alternatives-than-tor"></span>
    <a href="#better-alternatives-than-tor" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tor worked and it is convenient, but it is not the only option. Sometimes it is slow. Sometimes a service dislikes Tor exits. For <code>gh api</code> and listing PRs, fine. For heavier use, you may want something more predictable.</p>
<p>Reasonable options:</p>
<ul>
<li>Normal VPN: Proton, Mullvad, IVPN, your own WireGuard. Route everything or use split tunneling if you know how to configure it.</li>
<li>Tailscale with an exit node outside Brazil: clean, if you have a trusted machine outside the country or a VPS.</li>
<li>SSH SOCKS to a VPS: <code>ssh -N -D 127.0.0.1:9050 user@your-vps</code>. Then use the same <code>HTTPS_PROXY=socks5h://127.0.0.1:9050</code>.</li>
<li>Corporate proxy: if your company has egress outside Brazil, configure only the tools that need it.</li>
</ul>
<p>The care here is not changing everything at the same time. First prove that direct <code>api.github.com</code> fails. Then prove that another path works. Only then automate.</p>
<h2>Adjusting your own tools: ghpending<span class="hx:absolute hx:-mt-20" id="adjusting-your-own-tools-ghpending"></span>
    <a href="#adjusting-your-own-tools-ghpending" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>This block also hit one of my utilities: <a href="https://github.com/akitaonrails/ghpending"target="_blank" rel="noopener">ghpending</a>. I already wrote about it in <a href="/en/2026/05/23/i-built-a-cli-to-check-my-github-pending-stuff-ghpending/">&ldquo;I Built a CLI to Check My GitHub Pending Stuff: ghpending&rdquo;</a>. It lists open issues and pull requests across my repositories, so I know where somebody is waiting for an answer.</p>
<p>The problem is obvious: it uses the GitHub API. If <code>api.github.com</code> falls into a hole, it falls with it.</p>
<p>So I added a proxy fallback. <code>ghpending</code> now reads <code>GITHUB_TOKEN</code> like before, but also tries to use a SOCKS proxy in this order:</p>
<ol>
<li><code>GHPENDING_GITHUB_PROXY</code>, if you want to force a specific proxy.</li>
<li><code>HTTPS_PROXY</code>, <code>https_proxy</code>, <code>ALL_PROXY</code>, or <code>all_proxy</code>, if they are <code>socks5://</code> or <code>socks5h://</code>.</li>
<li>A local SOCKS proxy at <code>127.0.0.1:9050</code>, if one is listening.</li>
<li>If none of that exists, it tries direct access.</li>
</ol>
<p>In the README, it 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="nv">GITHUB_TOKEN</span><span class="o">=</span><span class="k">$(</span>gh auth token<span class="k">)</span> ghpending</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 for proxy:</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="nv">GHPENDING_GITHUB_PROXY</span><span class="o">=</span>socks5h://127.0.0.1:9050 ghpending</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>Or just run it through the wrapper:</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">~/.local/bin/github-proxy ghpending</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 wrapper starts Tor on <code>127.0.0.1:9050</code>; <code>ghpending</code> detects that proxy and uses it for API calls. If you are not blocked, it keeps working directly. If you are blocked, it has an escape route.</p>
<p>This is the kind of fallback every Brazilian dev should now consider for tools that depend on foreign APIs. It is ugly, but the Brazilian internet is getting unpredictable.</p>
<h2>The political problem<span class="hx:absolute hx:-mt-20" id="the-political-problem"></span>
    <a href="#the-political-problem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>At the end of the thread, <a href="https://x.com/ayubio/status/2064880160893968643"target="_blank" rel="noopener">Ayub reminded everyone</a> that, because of these secret collateral blocks and their side effects, there would already be around 250 million addresses blocked on the Brazilian internet. He also linked a presentation about the state of blocking in Brazil. In another tweet in the same thread, he says authorities defend secrecy as the only alternative left to fight crime.</p>
<p>I do not buy that excuse.</p>
<p>Secret blocking of critical infrastructure, with no transparency, no public adversarial process, no auditable list, no explanation of scope, is operational censorship. In my read, it is illegal precisely because it dodges the public, auditable process that should exist when the State orders internet access to be broken. Maybe it started with pirate TV boxes. Maybe the speech sounds noble. But the mechanism is there: somebody presses a button, carriers apply the block, legitimate users only find out when a work tool stops.</p>
<p>Today, the collateral damage hit honest developers trying to work with GitHub. Tomorrow it hits a Linux package, a container registry, a CDN, a payment API, an authentication service. Then everybody runs to VPNs, Tor, proxies, alternate DNS, <code>/etc/hosts</code>, Pi-hole. Congratulations: little by little, we are rebuilding the survival manual for a censored internet.</p>
<p>No need to overdo the metaphor. Brazil is not China yet. But the smell is the same: centralized opaque blocking, collateral damage, and ordinary users having to learn bypasses to work.</p>
<p>If you were affected, document it. Run the commands. Save outputs. Open an issue with your provider. Complain with data. Share workarounds, but do not normalize this. A bypass is a bandage. The real problem is a State giving itself the power to silently break pieces of the internet.</p>
<p>And if all you wanted was to run <code>gh pr list</code> to review a pull request: welcome to Brazilian infrastructure in 2026.</p>
]]></content:encoded><category>ghpending</category><category>networking</category><category>security</category><category>linux</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>NW-Omarchy: Bringing Omarchy to X11 with XLibre</title><link>https://akitaonrails.github.io/en/2026/05/01/nw-omarchy-xlibre-inaugural/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/05/01/nw-omarchy-xlibre-inaugural/</guid><pubDate>Fri, 01 May 2026 18:00:00 -0300</pubDate><description>&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/launcher-drun.png" alt="NW-Omarchy launcher: super&amp;#43;space opens rofi with every .desktop on the system, icons rendered through the Papirus theme" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;This post inaugurates an experimental project I&amp;rsquo;ve been hacking on for the past few weeks: &lt;a href="https://github.com/akitaonrails/NW-Omarchy"target="_blank" rel="noopener"&gt;NW-Omarchy&lt;/a&gt;. The pitch is easy to explain and stubborn enough to be worth defending: take the opinionated, pretty look-and-feel of &lt;a href="https://omarchy.org/"target="_blank" rel="noopener"&gt;Omarchy&lt;/a&gt; (which is Hyprland, meaning Wayland) and bring all of that to the X11 world, as a parallel session you pick at SDDM. Your Hyprland session stays untouched. When you want to try X11, pick &lt;code&gt;nw-bspwm&lt;/code&gt; at login and land in something that looks like the same Omarchy with different parts underneath.&lt;/p&gt;</description><content:encoded><![CDATA[<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/launcher-drun.png" alt="NW-Omarchy launcher: super&#43;space opens rofi with every .desktop on the system, icons rendered through the Papirus theme"  loading="lazy" /></p>
<p>This post inaugurates an experimental project I&rsquo;ve been hacking on for the past few weeks: <a href="https://github.com/akitaonrails/NW-Omarchy"target="_blank" rel="noopener">NW-Omarchy</a>. The pitch is easy to explain and stubborn enough to be worth defending: take the opinionated, pretty look-and-feel of <a href="https://omarchy.org/"target="_blank" rel="noopener">Omarchy</a> (which is Hyprland, meaning Wayland) and bring all of that to the X11 world, as a parallel session you pick at SDDM. Your Hyprland session stays untouched. When you want to try X11, pick <code>nw-bspwm</code> at login and land in something that looks like the same Omarchy with different parts underneath.</p>
<p>The obvious question is &ldquo;why bother&rdquo;. Red Hat already announced in 2025 that upstream <code>xorg-server</code> is gone from RHEL 10, and the corporate Linux narrative shifted to &ldquo;use Wayland and move on&rdquo;. As usual, in open source we don&rsquo;t have to agree. There&rsquo;s XLibre, a fork of xorg-server picking up active maintenance, and there are plenty of people with old hardware, legacy drivers, or legacy software for which Wayland still isn&rsquo;t a viable path. NW-Omarchy is for that scenario: give that gear a graceful second wind and, while you&rsquo;re at it, make it pretty.</p>
<h2>What XLibre is and why it matters<span class="hx:absolute hx:-mt-20" id="what-xlibre-is-and-why-it-matters"></span>
    <a href="#what-xlibre-is-and-why-it-matters" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>XLibre is a fork of xorg-server that started in 2025, after it became clear upstream had no energy left to evolve. X.Org itself was only taking critical security patches, and the historical primary funder of new development (Red Hat) <a href="https://www.redhat.com/en/blog/rhel-10-plans-wayland-and-xorg-server"target="_blank" rel="noopener">announced it was stepping out</a> of new X-server work as part of the RHEL 10 plans. XLibre took the codebase and went back to cleanups, security fixes, modernization and driver-ABI bumps. The first public release was <a href="https://www.phoronix.com/news/XLibre-25.0-Released"target="_blank" rel="noopener">XLibre 25.0</a>, and the project lives at <a href="https://github.com/X11Libre/xserver"target="_blank" rel="noopener">github.com/X11Libre/xserver</a>.</p>
<p>XLibre&rsquo;s technical pitch is being a drop-in replacement for xorg-server. Same X11 protocol, same client API (libxcb/libx11), same dependency graph in pacman. Your xterm, your Firefox, your Steam, your favorite emulator for arcade machines from the 90s, none of them know the X server changed. Distros already shipping XLibre as default or tier-one:</p>
<ul>
<li><strong>Artix Linux</strong> (<a href="https://linuxiac.com/artix-linux-2026-04-released-with-xlibre-as-default-x-serve/"target="_blank" rel="noopener">2026.04+</a>) ships it as the default X server.</li>
<li><strong>Fedora</strong> has an <a href="https://fedoraproject.org/wiki/Changes"target="_blank" rel="noopener">open Change proposal</a> to migrate.</li>
<li><strong>Arch Linux</strong> has an official binary repo at <code>x11libre.net/repo/arch_based/</code>. That&rsquo;s exactly what NW-Omarchy uses.</li>
</ul>
<p>Comparing what&rsquo;s still alive:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>xorg-server</th>
          <th>XLibre</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Last non-security release</td>
          <td>21.1 (2022)</td>
          <td>25.0 (2025)</td>
      </tr>
      <tr>
          <td>Active codebase work</td>
          <td>none</td>
          <td>yes</td>
      </tr>
      <tr>
          <td>Driver ABI</td>
          <td>frozen</td>
          <td>bumped (<code>X-ABI-VIDEODRV_VERSION=28.0</code> in 25.0)</td>
      </tr>
      <tr>
          <td>Stated future</td>
          <td>&ldquo;use Wayland&rdquo;</td>
          <td>continued X server evolution</td>
      </tr>
      <tr>
          <td>Coexists with <code>xorg-xwayland</code></td>
          <td>yes</td>
          <td>yes</td>
      </tr>
  </tbody>
</table>
<p>If your reason for staying on X11 is any of the classics (legacy proprietary Nvidia driver, vintage Intel GPU, Optimus laptop with quirky muxing, dependency on <code>xdotool</code>/<code>wmctrl</code>/<code>xprop</code>/<code>xinput</code> for accessibility or remap tools, <code>ssh -X</code> to run a remote app, clipboard with no permission prompt, screen reader that needs to read input from any window), XLibre is the path that keeps that stack under active maintenance.</p>
<h2>Why this project exists<span class="hx:absolute hx:-mt-20" id="why-this-project-exists"></span>
    <a href="#why-this-project-exists" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I run Omarchy on my Thinkpad T14 Gen 6 (<a href="/en/2026/04/18/omarchy-on-thinkpad-t14-gen-6/">wrote about it here</a>) and I genuinely like it. DHH did serious curation work: a single coherent theme across everything (terminal, launcher, top bar, lockscreen), consistent shortcuts, visual choices that don&rsquo;t tire your eyes. It&rsquo;s opinionated the right way, and it spares you the fatigue of configuring everything from scratch.</p>
<p>The catch is that Omarchy is Hyprland, Hyprland is Wayland-only, and Wayland still has scenarios where it&rsquo;s the wrong tool for the job. Older machines with legacy Nvidia drivers, environments leaning on software with deep X11 integration, situations where you want <code>ssh -X</code> and don&rsquo;t want to learn <code>waypipe</code>, or just machines you want to keep running without swapping half the stack. For those cases, I wanted Omarchy&rsquo;s aesthetic and workflow on top of X11. It didn&rsquo;t exist. So I built it.</p>
<p>The name NW-Omarchy is short for &ldquo;Not Wayland Omarchy&rdquo;. It&rsquo;s deliberately a sidecar: runs alongside, doesn&rsquo;t replace. You install it on top of an existing Omarchy install, it creates a separate SDDM session called <code>nw-bspwm</code>, and nothing in your Hyprland setup gets touched. Want to go back to Hyprland? Pick Hyprland at SDDM next login. Want to uninstall? There&rsquo;s an uninstall script that reverses exactly what got installed, based on a manifest that tracks every action.</p>
<h2>What changes in the stack (Wayland → X11)<span class="hx:absolute hx:-mt-20" id="what-changes-in-the-stack-wayland--x11"></span>
    <a href="#what-changes-in-the-stack-wayland--x11" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Hyprland is compositor + WM in the same binary. In the X11 world, those two functions come as separate pieces, and each has a well-known substitute. The translation work was mapping every Omarchy component to its X11 equivalent, then making sure the keyboard shortcuts, theming, and behavior stayed as close as possible.</p>
<table>
  <thead>
      <tr>
          <th>Function</th>
          <th>Omarchy/Wayland</th>
          <th>NW-Omarchy/X11</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Window manager</td>
          <td>Hyprland</td>
          <td><a href="https://github.com/baskerville/bspwm"target="_blank" rel="noopener">bspwm</a></td>
      </tr>
      <tr>
          <td>Hotkey daemon</td>
          <td>Hyprland built-in</td>
          <td><a href="https://github.com/baskerville/sxhkd"target="_blank" rel="noopener">sxhkd</a></td>
      </tr>
      <tr>
          <td>Compositor</td>
          <td>Hyprland built-in</td>
          <td><a href="https://github.com/yshui/picom"target="_blank" rel="noopener">picom</a> (upstream v13)</td>
      </tr>
      <tr>
          <td>Top bar</td>
          <td>Waybar</td>
          <td><a href="https://github.com/polybar/polybar"target="_blank" rel="noopener">polybar</a></td>
      </tr>
      <tr>
          <td>App launcher</td>
          <td>Walker</td>
          <td><a href="https://github.com/davatorium/rofi"target="_blank" rel="noopener">rofi</a></td>
      </tr>
      <tr>
          <td>Notifications</td>
          <td>Mako</td>
          <td><a href="https://github.com/dunst-project/dunst"target="_blank" rel="noopener">dunst</a></td>
      </tr>
      <tr>
          <td>Idle/lock</td>
          <td>hypridle / hyprlock</td>
          <td>xidlehook + xss-lock + i3lock-color</td>
      </tr>
      <tr>
          <td>Nightlight</td>
          <td>hyprsunset</td>
          <td>redshift</td>
      </tr>
      <tr>
          <td>Clipboard history</td>
          <td>walker <code>-m clipboard</code></td>
          <td><a href="https://github.com/cdown/clipmenu"target="_blank" rel="noopener">clipmenu</a> (rofi-backed)</td>
      </tr>
      <tr>
          <td>Screenshots</td>
          <td>hyprshot</td>
          <td>maim + slop + xclip</td>
      </tr>
      <tr>
          <td>Color picker</td>
          <td>hyprpicker</td>
          <td>xcolor</td>
      </tr>
  </tbody>
</table>
<p><strong>bspwm</strong> is a minimalist tiling window manager. The WM itself only responds to commands over a socket, and <code>bspwmrc</code> is literally a shell script. Keybindings live in a separate daemon, <strong>sxhkd</strong>, which reads a plain text file. That separation is classic X11: each piece does one thing.</p>
<p><strong>picom</strong> is the compositor that gives you blur, shadows, rounded corners, fade-in/out, and per-window opacity rules. NW-Omarchy uses upstream v13 (released February 2026), not the old <code>picom-ftlabs-git</code> fork, <a href="https://github.com/yshui/picom/issues"target="_blank" rel="noopener">abandoned since 2024</a>. The visual effects Omarchy has under Hyprland (rounded corners, fade on open/close, blur on rofi and dunst) all come from picom in NW-Omarchy.</p>
<p><strong>polybar</strong> replaces waybar. Same visual layout: Omarchy logo on the left, date in the middle, audio/wifi/bluetooth/battery indicators on the right. Same nerd-font icons. Left click opens the same TUIs (wiremix, impala, bluetui), right click opens the GUI variants. Scroll on audio adjusts volume, middle click mutes. Practical parity.</p>
<p><strong>rofi</strong> replaces walker. <code>super + space</code> opens the full launcher with every <code>.desktop</code> on the system plus icons. <code>super + shift + space</code> opens a cheat-sheet of the pinned apps, with the chord shown next to each name. Useful for remembering &ldquo;what was the chord for Signal again&rdquo;.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/launcher-cheatsheet.png" alt="Pinned-apps cheat-sheet: super&#43;shift&#43;space opens rofi with each app and its chord, parsed straight from sxhkdrc"  loading="lazy" /></p>
<p>For a user who isn&rsquo;t going to crack open <code>~/.config</code> to customize anything, the difference between the two sessions is cosmetic at homeopathic levels. The same Omarchy shortcuts (over 70 bindings) are wired up, the same menus pop up, the same logo sits in the bar, the same nerd-font icons.</p>
<h2>The theme system is the same<span class="hx:absolute hx:-mt-20" id="the-theme-system-is-the-same"></span>
    <a href="#the-theme-system-is-the-same" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>This was the most fun part to build, and the part that surprises people most. Omarchy has a centralized theme system: each theme lives at <code>~/.local/share/omarchy/themes/&lt;name&gt;/</code>, ships a <code>colors.toml</code> with a universal palette (<code>accent</code>, <code>background</code>, <code>foreground</code>, <code>color0..15</code>), and <code>omarchy-theme-set &lt;name&gt;</code> re-renders each app&rsquo;s config to match. The active theme lives at <code>~/.config/omarchy/current/theme/</code>, and every app imports its colors from there.</p>
<p>NW-Omarchy plugs into that same pipeline. I added <code>.tpl</code> templates for bspwm, polybar, rofi and dunst that follow the Omarchy convention, and <code>omarchy-theme-set-templates</code> regenerates everything on every theme change. The result is that all 19 themes Omarchy ships work as-is in NW-Omarchy, with zero porting work. Catppuccin, gruvbox, kanagawa, nord, tokyo-night, all of them. The wallpaper changes along with the theme via an inotify daemon (<code>nw-omarchy-bg-watch</code>).</p>
<p>A few examples in action:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/theme-ristretto.png" alt="Ristretto theme: warm color curves on the wallpaper, polybar and bspwm border picking up the same palette"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/theme-lumon.png" alt="Lumon theme: Severance reference, cold corporate palette, “United in Severance”"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/theme-everforest.png" alt="Everforest theme: moss-green tones, wallpaper of misty tree-tops"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/theme-retro-82.png" alt="Retro-82 theme: synthwave, pink grid on dark background, neon details"  loading="lazy" /></p>
<p>When Omarchy adds a new theme via <code>omarchy-theme-update</code>, NW-Omarchy gets it for free. No extra work, no rebuild, nothing for me to republish.</p>
<h2>System menu and TUIs<span class="hx:absolute hx:-mt-20" id="system-menu-and-tuis"></span>
    <a href="#system-menu-and-tuis" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Omarchy&rsquo;s system menu (the <code>super + alt + space</code> one that opens the hierarchical menu with Hardware, Setup, Update, etc.) has a 1-to-1 port in NW-Omarchy. Same tree, same navigation. Where Omarchy calls <code>walker --dmenu</code>, I call <code>rofi -dmenu</code>. The rest of the code is identical, and most of the helpers the menu invokes (<code>omarchy-pkg-add</code>, <code>omarchy-theme-set</code>, <code>omarchy-update</code>, etc.) have nothing Wayland-specific in them and run straight on X11.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/system-menu.png" alt="nw-omarchy-menu open: the Omarchy system menu tree, arrow-key navigation, same look"  loading="lazy" /></p>
<p>The TUIs too: wiremix for audio, impala for wifi, bluetui for bluetooth, btop for system resources. They all open as a centered floating window, same as Omarchy under Hyprland. bspwm has a rule (<code>bspc rule -a</code>) that recognizes the <code>org.nw-omarchy.&lt;cmd&gt;</code> class and applies <code>state=floating center=on rectangle=900x600</code> automatically.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/wifi-tui.png" alt="Impala TUI running as a centered floating window via super&#43;ctrl&#43;w, palette following the active theme"  loading="lazy" /></p>
<h2>What&rsquo;s there and what isn&rsquo;t<span class="hx:absolute hx:-mt-20" id="whats-there-and-what-isnt"></span>
    <a href="#whats-there-and-what-isnt" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Honesty first: there are Hyprland features that are Wayland-only and don&rsquo;t come back. Per-monitor fractional scaling is gone, X11 only does integer scaling per output, so mixed-DPI setups look off. HDR doesn&rsquo;t exist on X11. Tear-free rendering by design needs Wayland; on X11 you need picom + vsync and a cooperative GPU, and even then you get close to Wayland without quite matching it. Workspace swipe with live preview during the gesture is also gone, because bspwm has no in-progress switch state, so the gesture fires a discrete command only on completion (you go to the next workspace, but you don&rsquo;t see the preview slide). And the Wayland-specific daemons (voxtype, walker preview pane, hyprsunset, hypridle) become <code>redshift</code> and <code>xss-lock</code>, with no preview pane in rofi.</p>
<p>If any of those points is a deal-breaker for you, stay on Omarchy/Hyprland. SDDM picks between the two sessions every login, so they coexist without resentment.</p>
<p>On the other hand, what X11 has going for it still applies. <code>xdotool</code>, <code>wmctrl</code>, <code>xprop</code> and <code>xinput</code> keep working for automation, key remapping, screen readers, AutoKey, kanata, xkeysnail. <code>xclip</code>, <code>xsel</code> and clipmenu do straightforward clipboard plumbing, no permission dialog. <code>ssh -X</code> keeps working: you run Firefox on a remote box and the window pops up locally. Legacy Nvidia, vintage Intel and quirky Optimus drivers are all first-class on X11. The tiling WM ecosystem is mature (bspwm, i3, awesome, openbox, xmonad, dwm) and configs port between them with little pain. And the predictable input model, where any client can read any event, is bad for security but excellent for accessibility and productivity tooling.</p>
<p>For older hardware, that&rsquo;s the difference between &ldquo;live machine&rdquo; and &ldquo;machine sitting in a corner&rdquo;.</p>
<h2>Install<span class="hx:absolute hx:-mt-20" id="install"></span>
    <a href="#install" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Prerequisite: Arch Linux with Omarchy already installed. NW-Omarchy runs on top of it, doesn&rsquo;t replace it.</p>
<p>One-liner 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">curl -fsSL https://raw.githubusercontent.com/akitaonrails/NW-Omarchy/master/boot.sh <span class="p">|</span> bash</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>boot.sh</code> runs preflight (checks Arch, Omarchy, git), clones the repo to <code>~/.local/share/nw-omarchy</code>, and runs the full install pipeline: packages, the <code>nw-bspwm</code> session, configs in <code>~/.config</code>, and the XLibre swap as the last step. It asks for confirmation once before touching anything. Skip the prompt with:</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">curl -fsSL https://raw.githubusercontent.com/akitaonrails/NW-Omarchy/master/boot.sh <span class="p">|</span> bash -s -- --yes</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>When it finishes, reboot. The X server swap (xorg-server → XLibre) only takes effect on the next session. At SDDM you&rsquo;ll see a session picker. We also swapped Omarchy&rsquo;s SDDM theme for one with a selector, because the vanilla Omarchy theme has the session name hard-coded in the QML with no UI to change it. Pick <code>nw-bspwm</code> and you&rsquo;re in.</p>
<p>If you&rsquo;d rather clone and run by hand to inspect first:</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">git clone https://github.com/akitaonrails/NW-Omarchy.git ~/.local/share/nw-omarchy
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/.local/share/nw-omarchy
</span></span><span class="line"><span class="cl">./install.sh           <span class="c1"># dry-run, prints what it would do</span>
</span></span><span class="line"><span class="cl">./install.sh --apply   <span class="c1"># actually run it</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>
<h2>Upgrade<span class="hx:absolute hx:-mt-20" id="upgrade"></span>
    <a href="#upgrade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>When a new release ships, the path is:</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">nw-omarchy-upgrade --check    <span class="c1"># checks current vs latest, exits</span>
</span></span><span class="line"><span class="cl">nw-omarchy-upgrade            <span class="c1"># yay -Syu, fetch latest tag, migrations, install</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 script runs <code>yay -Syu</code> (or <code>pacman -Syu</code> if you don&rsquo;t have yay), compares the local tag to the remote, and if there&rsquo;s a new release does <code>git fetch &amp;&amp; git checkout v&lt;latest&gt;</code>, runs the migrations between the current and new versions, and re-applies <code>install.sh --apply</code>. All idempotent. Re-running on an already-current machine is a no-op.</p>
<h2>Uninstall<span class="hx:absolute hx:-mt-20" id="uninstall"></span>
    <a href="#uninstall" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>If you tried it and didn&rsquo;t like it, uninstall this way:</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">~/.local/share/nw-omarchy/uninstall.sh --apply</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 uninstaller reads <code>~/.local/state/nw-omarchy/manifest.tsv</code> and replays it in reverse: removes only the packages it installed (<code>pkg-skip</code> rows are left alone; packages that were already on the system stay on the system), restores original configs from <code>~/.local/state/nw-omarchy/backups/</code>, deletes the <code>nw-bspwm</code> session entry, wipes the state dir. Your Hyprland session keeps working as before. Nothing under <code>~/.config/hypr/</code> or <code>~/.local/share/omarchy/</code> gets touched at any point during install or uninstall.</p>
<p>To revert specifically XLibre back to xorg-server (without uninstalling anything else), pacman handles it on its own:</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 xorg-server xorg-server-common xf86-input-libinput</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 package <code>provides</code>/<code>conflicts</code> graph handles the rest of the swap atomically.</p>
<h2>Status: experimental, feedback welcome<span class="hx:absolute hx:-mt-20" id="status-experimental-feedback-welcome"></span>
    <a href="#status-experimental-feedback-welcome" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The current version is at 1.0 but the project is still young, in polish phase. The big items are in place: binding parity, menu parity, theme parity, TUI parity, doctor for health-checking the install, manifest for clean uninstall. What&rsquo;s left is polish, edge cases, working through varied hardware, listening to feedback from anyone running it on a different machine than mine (Thinkpad T14 Gen 6).</p>
<p>If you try it and hit something (something that doesn&rsquo;t work, configuration that&rsquo;s missing, behavior that differs from Omarchy in a way that breaks flow), open an issue here:</p>
<p><a href="https://github.com/akitaonrails/NW-Omarchy/issues"target="_blank" rel="noopener">github.com/akitaonrails/NW-Omarchy/issues</a></p>
<p>Repros, stack traces, output of <code>nw-omarchy-doctor</code>, anything helps. PRs are welcome too.</p>
<p>Defending X11 in 2026 is a pragmatic argument. The transition to Wayland takes time, plenty of hardware and plenty of software still live comfortably on this side of the fence, and the open source community can support more than one path at a time. XLibre is the bet on keeping the X server alive a few more years. NW-Omarchy is my bet that you can make this older stack look as pretty as the newer one, without compromising any of what makes X11 still worth the trip.</p>
<p>If you&rsquo;re on a new machine, modern GPU, 4K HiDPI display, wanting HDR, stay on Omarchy/Hyprland. If you&rsquo;re on an older box, or you simply prefer the X11 ecosystem for any of the reasons above, give NW-Omarchy a shot.</p>
]]></content:encoded><category>omarchy</category><category>linux</category></item><item><title>My Favorite Retro Racing Games Running on My Distrobox</title><link>https://akitaonrails.github.io/en/2026/04/19/my-favorite-retro-racing-games-on-distrobox/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/04/19/my-favorite-retro-racing-games-on-distrobox/</guid><pubDate>Sun, 19 Apr 2026 20:00:00 -0300</pubDate><description>&lt;p&gt;I spent the whole Sunday on this, and it was one of the most productive Sundays I&amp;rsquo;ve had in a while. The mission was specific: close the loop on the simcade racing games that are hardest to emulate, the ones I&amp;rsquo;ve wanted to run properly for years, and only now got to a reliable state.&lt;/p&gt;
&lt;p&gt;Two monsters sit at the top of that list. First, &lt;strong&gt;&lt;a href="#driveclub-the-impossible-finally-possible"&gt;Driveclub&lt;/a&gt;&lt;/strong&gt; on shadPS4. It&amp;rsquo;s a PS4 exclusive, never got a port, Evolution Studios was shut down, no remaster. To play it outside the original PS4, emulation is the only option, and PS4 emulation is still the most immature part of the ecosystem. This is &lt;strong&gt;by far the hardest on the list&lt;/strong&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I spent the whole Sunday on this, and it was one of the most productive Sundays I&rsquo;ve had in a while. The mission was specific: close the loop on the simcade racing games that are hardest to emulate, the ones I&rsquo;ve wanted to run properly for years, and only now got to a reliable state.</p>
<p>Two monsters sit at the top of that list. First, <strong><a href="#driveclub-the-impossible-finally-possible">Driveclub</a></strong> on shadPS4. It&rsquo;s a PS4 exclusive, never got a port, Evolution Studios was shut down, no remaster. To play it outside the original PS4, emulation is the only option, and PS4 emulation is still the most immature part of the ecosystem. This is <strong>by far the hardest on the list</strong>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/19/distrobox-gaming/shadps4-launcher.png" alt="shadPS4 Qt Launcher showing DRIVECLUB CUSA00003 ready to run"  loading="lazy" /></p>
<p>Second, <strong><a href="#forza-motorsport-4-the-goat">Forza Motorsport 4 with Project Forza Plus</a></strong> on Xenia Canary. FM4 is the GOAT of the Xbox 360 era, Project Forza Plus is the community mod that consolidates patches and content, and getting the two to run together on Xenia with decent graphics, no audio crashes and no shadow bugs took serious hours of trial and error.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/19/distrobox-gaming/xenia-manager.png" alt="Xenia Manager with the Xbox 360 Forza collection installed"  loading="lazy" /></p>
<p>Around those two I organized the rest of what I already knew worked at some level, just needed consolidating into a reproducible setup: <strong><a href="#gran-turismo-4-spec-ii-mod">Gran Turismo 4 with Retexture 3.0 and HD HUD</a></strong>, <strong><a href="#gran-turismo-3-a-spec">Gran Turismo 3 with HD textures and widescreen</a></strong>, <strong><a href="#gran-turismo-2-spec-ii-mod">Gran Turismo 2 with the Spec II mod</a></strong>, the PS3 Gran Turismos (<a href="#gran-turismo-5">GT5</a> and <a href="#gran-turismo-6">GT6</a>), Forza Horizon <a href="#forza-horizon-1-with-xe-mod">1</a> and <a href="#forza-horizon-2">2</a>, the <a href="#project-gotham-racing-3-and-4">PGRs</a>, <a href="#ridge-racer-v">Ridge Racer</a>, <a href="#enthusia-professional-racing">Enthusia</a>, <a href="#colin-mcrae-rally-1-and-2">Colin McRae</a>. I had an idea how to make these work, I just needed a setup that doesn&rsquo;t break the next time I reinstall the system.</p>
<p>Before the details, two things to clarify.</p>
<p>First, on infra: I&rsquo;m not going to repeat here how the setup was built. I wrote that in detail in <a href="/en/2026/04/11/emulation-distrobox-with-claude-code/">An Emulation Distrobox with Claude Code</a>. Arch Linux in a distrobox with <code>--nvidia</code>, 17 Ansible roles, every emulator from the AUR, ES-DE as frontend, automated per-game configs, Python scripts for PS3 update checking and Xbox 360 title updates, Xenia via Wine. Go read that one. The repo is at <a href="https://github.com/akitaonrails/distrobox-gaming"target="_blank" rel="noopener">akitaonrails/distrobox-gaming</a> if you want to reproduce. Here I&rsquo;m going to talk about the games.</p>
<p>Second, on taste: I like simcade racing. Gran Turismo has been my declared addiction since PS1, and I wrote about the rest in the <a href="/en/2026/04/01/my-sim-racing-cockpit-formula-fx1/">Formula FX1 cockpit article</a>. Direct-drive wheel, load-cell pedal, triple monitor when I&rsquo;m feeling masochistic. That&rsquo;s the kind of racing I like.</p>
<h2>The warning for the &ldquo;well, actually&rdquo; crowd<span class="hx:absolute hx:-mt-20" id="the-warning-for-the-well-actually-crowd"></span>
    <a href="#the-warning-for-the-well-actually-crowd" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Yes, I know iRacing and the rest of the current sims and simcades. I have 12 TB of ISOs and ROMs on the NAS, I probably have one you&rsquo;d remember to mention. It&rsquo;s not for lack of options that I&rsquo;m focusing on emulation today.</p>
<p>On the new side, the games I&rsquo;m genuinely anticipating: <strong><a href="https://forza.net/"target="_blank" rel="noopener">Forza Horizon 6</a> in Tokyo</strong>, <strong><a href="https://www.assettocorsa.it/"target="_blank" rel="noopener">Assetto Corsa EVO</a></strong>, and <strong><a href="https://www.assettocorsa.it/"target="_blank" rel="noopener">Assetto Corsa Rally</a></strong>. I&rsquo;ve been playing the demos of both Assetto Corsa titles and enjoying their single-player campaigns a lot. When those ship, they&rsquo;ll become my main sim rotation. This article covers a different side of the collection, pulling classics out of oblivion that still don&rsquo;t have a remaster.</p>
<p>Today, here, I&rsquo;m interested in these specific games I listed above. Period. You do what you want, I do what I want. If you want iRacing, fire up Steam and go in peace. This article is about messing with emulators, preserving old games, and pulling these gems out of oblivion on a Linux setup. If you&rsquo;re here for current-sim reviews, you won&rsquo;t find them.</p>
<p>Now to what matters.</p>
<p><strong>A note on the videos:</strong> most of them have no audio because I forgot to turn on sound capture in OBS before recording and I was too lazy to re-record. Audio works perfectly on every emulator here; this is my OBS mistake, not a setup issue.</p>
<h2>Global settings per emulator<span class="hx:absolute hx:-mt-20" id="global-settings-per-emulator"></span>
    <a href="#global-settings-per-emulator" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Before the games, a quick consolidation of the global configs that apply across each emulator. This is automated in the repo, but if you&rsquo;re setting up by hand, here&rsquo;s the summary.</p>
<h3>DuckStation (PS1)<span class="hx:absolute hx:-mt-20" id="duckstation-ps1"></span>
    <a href="#duckstation-ps1" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Internal Resolution Scale:</strong> 8x (8K internal, downscaled to the monitor)</li>
<li><strong>Texture Filtering:</strong> JINC2 (preserves pixel art but smooths)</li>
<li><strong>PGXP:</strong> on (fixes the typical PS1 vertex wobble)</li>
<li><strong>Widescreen Hack:</strong> on as fallback, but I prefer per-game widescreen cheats</li>
<li><strong>Aspect Ratio:</strong> 16:9 with widescreen cheat, 4:3 without</li>
</ul>
<p>PS1 runs smoothly on DuckStation these days. It&rsquo;s the most mature emulator on the list. The attention goes to per-game choices (some games need a specific widescreen cheat).</p>
<h3>PCSX2 (PS2)<span class="hx:absolute hx:-mt-20" id="pcsx2-ps2"></span>
    <a href="#pcsx2-ps2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Upscale:</strong> 4x SSAA (1440p native → 4K internal)</li>
<li><strong>Anisotropic Filtering:</strong> 16x</li>
<li><strong>Post Processing:</strong> FXAA on, PCRTC antiblur on</li>
<li><strong>Deinterlacing:</strong> Automatic (some games need override)</li>
<li><strong>Controller:</strong> Xbox-style bindings (I use an 8BitDo)</li>
</ul>
<p>PCSX2 2.7.x is much better than the 2.6.3 still in Arch&rsquo;s official repo. The AUR&rsquo;s <code>pcsx2-latest-bin</code> tracks 2.7.x and that&rsquo;s where texture replacement, modern FXAA and PCRTC antiblur live. If you&rsquo;re on a different distro stuck on 2.6.3, get the official AppImage.</p>
<h3>RPCS3 (PS3)<span class="hx:absolute hx:-mt-20" id="rpcs3-ps3"></span>
    <a href="#rpcs3-ps3" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Resolution Scale:</strong> 300% (generally) or native (for games with upscale issues)</li>
<li><strong>Shader Precision:</strong> Ultra</li>
<li><strong>Force High Precision Z:</strong> on</li>
<li><strong>SPU XFloat:</strong> Accurate</li>
<li><strong>Multithreaded RSX:</strong> on</li>
<li><strong>Write Color Buffers (WCB) / Read Color Buffers (RCB):</strong> off by default (GT-series safety)</li>
</ul>
<p>RPCS3 is where the most traps are. Each game has a recommended preset in the <a href="https://rpcs3.net/compatibility"target="_blank" rel="noopener">official compatibility DB</a>. And a heads-up: per-game configs live in <code>~/.config/rpcs3/custom_configs/config_&lt;SERIAL&gt;.yml</code>. The <code>config_</code> prefix is mandatory. It cost me time to figure out. RPCS3 silently accepts the YAML and then ignores it if the name is wrong.</p>
<h3>Xenia Canary (Xbox 360)<span class="hx:absolute hx:-mt-20" id="xenia-canary-xbox-360"></span>
    <a href="#xenia-canary-xbox-360" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Build:</strong> Canary (<code>master</code> has been stagnant for months, Canary is where development lives)</li>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Render Target Path:</strong> <code>rtv</code> by default, <code>fsi</code> for specific games (PGR4)</li>
<li><strong>User Profile:</strong> created once, persists</li>
<li><strong>Wine Prefix:</strong> dedicated, managed by Xenia Manager</li>
</ul>
<p>Xenia on Linux runs via Wine, managed by <a href="https://github.com/xenia-manager/xenia-manager"target="_blank" rel="noopener">Xenia Manager</a>. There&rsquo;s no decent native build. It works well, but each game needs specific tuning, and Title Updates have to be pulled manually from archive.org (I wrote a script for that, details in the previous article).</p>
<h3>shadPS4 (PS4)<span class="hx:absolute hx:-mt-20" id="shadps4-ps4"></span>
    <a href="#shadps4-ps4" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Gets its own section at the end. TL;DR: works for some simple games, Driveclub is still a moving target. Still a shot in the dark.</p>
<h2>PS1 on DuckStation<span class="hx:absolute hx:-mt-20" id="ps1-on-duckstation"></span>
    <a href="#ps1-on-duckstation" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Gran Turismo (the first, from 1997)<span class="hx:absolute hx:-mt-20" id="gran-turismo-the-first-from-1997"></span>
    <a href="#gran-turismo-the-first-from-1997" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%201.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>This one is pure sentiment. GT1 was one of the first games I bought for the PS1 and I played it until the CD cracked. At the time, it was on another level. Real simulation on a machine with 128K of video RAM, decent physics, 140 cars when everybody else had 15, memorable soundtrack.</p>
<p>But let&rsquo;s be honest: you can&rsquo;t go back today. GT1&rsquo;s physics has exaggerated drift, the car slides more than it should on any corner above 80 km/h. It&rsquo;s part of the 1997 charm but gets annoying in 2026. The <strong>handling model was refined starting in GT2</strong>, and everything after only got better. Between GT1 and GT2, GT2 wins on content (650+ cars vs 140) and especially on drivability.</p>
<p>I keep GT1 in ES-DE to open in nostalgic moments, drive 10 minutes at Trial Mountain, and close it. For serious PS1-era gameplay, it&rsquo;s GT2.</p>
<h3>Gran Turismo 2 (Spec II Mod)<span class="hx:absolute hx:-mt-20" id="gran-turismo-2-spec-ii-mod"></span>
    <a href="#gran-turismo-2-spec-ii-mod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%202.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>The original PS1 Gran Turismo is a historical milestone. I played a lot of it back then. To replay today? No. GT2 exists, and GT2 is superior in everything. More cars (650+), more tracks, more modes, and most importantly, <strong>a much better handling model</strong>. GT1 has that exaggerated drift where the car slides more than it should, GT2 tightened that up to something closer to what the series would become starting on PS2. Between playing GT1 or GT2, always GT2.</p>
<p>And the GT2 I run today is the <strong>GT2 Spec II Mod</strong> by <a href="https://x.com/projectaspec?lang=en"target="_blank" rel="noopener">Project A-Spec</a>, a community project that merges the two regional variants (Arcade + Simulation) into a single game, brings back events that were cut from the final release, fixes physics bugs, adds native widescreen support, and ships quality-of-life menu updates. It&rsquo;s the definitive way to play GT2 in 2026. The project doesn&rsquo;t maintain its own website anymore, updates come through the author&rsquo;s Twitter.</p>
<p>To run it:</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disc</td>
          <td>GT2 Spec II Mod (patch applied over Simulation USA ISO)</td>
      </tr>
      <tr>
          <td>Serial</td>
          <td>SCUS-94455</td>
      </tr>
      <tr>
          <td>Widescreen cheat</td>
          <td>On</td>
      </tr>
      <tr>
          <td>8 MB RAM cheat</td>
          <td>On (fixes audio on crowded tracks)</td>
      </tr>
      <tr>
          <td>Texture Filter</td>
          <td>JINC2</td>
      </tr>
      <tr>
          <td>Resolution Scale</td>
          <td>8x</td>
      </tr>
  </tbody>
</table>
<p>The 8 MB RAM cheat matters for crowded tracks (Seattle in populated races for example) where audio starts clipping. It simulates the memory expansion of the Japanese version that never made it West.</p>
<h3>Colin McRae Rally 1 and 2<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-1-and-2"></span>
    <a href="#colin-mcrae-rally-1-and-2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Iconic PS1 games. CMR1 has that rough first-gen rally feel that has its charm. CMR2 is technically superior, better physics, more rallies, better graphics. But honestly: <strong>to play CMR2 today, it&rsquo;s worth looking for the original PC version</strong>. Higher resolution, native 60fps (the PS1 version is locked to 30), direct keyboard/wheel support. Codemasters repacks exist on the internet, a 40-second search will find them. I&rsquo;m not going to link them, you know where to look.</p>
<p>On PS1 via DuckStation they run smoothly, nostalgia guaranteed, but it&rsquo;s not where I spend my time.</p>
<h2>PS2 on PCSX2<span class="hx:absolute hx:-mt-20" id="ps2-on-pcsx2"></span>
    <a href="#ps2-on-pcsx2" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Gran Turismo 3 A-Spec<span class="hx:absolute hx:-mt-20" id="gran-turismo-3-a-spec"></span>
    <a href="#gran-turismo-3-a-spec" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%203.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT3 was a generational leap. Out of the blocky PS1 world into shaders, decent environment mapping, serious polygon car models, physics that finally started to feel like actual physics. The soundtrack with Feeder&rsquo;s &ldquo;Just a Day&rdquo; opening the game still gives me goosebumps.</p>
<p>The problem is that GT3 has LESS content than GT2. Polyphony was more cautious, cut modes to focus on polish. The A/B/S license tree is smaller, fewer cars, fewer tracks. But what&rsquo;s there is polished.</p>
<p>In 2026 on PCSX2 2.7.x, GT3 looks great. Community HD textures, widescreen pnach, anti-aliasing via SSAA, 16x AF. Feels like a re-release.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disc</td>
          <td>GT3 A-spec (SCUS-97102) or Bundle (PBPX-95503)</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>On</td>
      </tr>
      <tr>
          <td>Retexture pack</td>
          <td>Optional (off by default, enable if you want full HD)</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
      <tr>
          <td>Deinterlacing</td>
          <td>Automatic</td>
      </tr>
  </tbody>
</table>
<p>Caveat on the retexture pack: when enabled, some car-showcase cutscenes in the garage have momentary flicker due to NFS streaming. Doesn&rsquo;t bother in-race. If it bothers you in menus, turn it off.</p>
<p><strong>Reminder:</strong> for the widescreen pnach to take effect, you still need to go into the <strong>in-game</strong> options and switch the aspect ratio to 16:9. The pnach unlocks the option in the menu, it doesn&rsquo;t apply automatically.</p>
<h3>Gran Turismo 4 (Spec II Mod)<span class="hx:absolute hx:-mt-20" id="gran-turismo-4-spec-ii-mod"></span>
    <a href="#gran-turismo-4-spec-ii-mod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%204.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT4 is my GOAT in the series. 700+ cars, 50+ tracks, physics still respectable by 2026 standards, and that soundtrack mixing alternative rock, Japanese lounge, and Moby&rsquo;s infamous &ldquo;Sail Away&rdquo; on the main menu. It&rsquo;s the game I poured the most hours into back in 2004, and it&rsquo;s what I play the most today.</p>
<p>Like GT2, the GT4 I run today <strong>is not the original disc</strong>. It&rsquo;s the <a href="https://www.theadmiester.co.uk/specii/"target="_blank" rel="noopener"><strong>Gran Turismo 4: Spec II</strong></a> maintained by TheAdmiester — a massive community mod that&rsquo;s objectively the best way to play GT4 in 2026. It combines the final USA disc, the 2003 Beta prototype, and the original Japanese release into one game, restoring cut cars (including several that never left Japan), cancelled events, dozens of missing soundtrack tracks, adding 16:9 widescreen support, over 30 built-in cheats exposed as menu options (pro tuning, top speed, fuel economy), the option to switch between English and Japanese, and original bug fixes. The project is active, still gets patches, and ships its own installer that applies over a clean USA ISO.</p>
<p>In practice, Spec II is what GT4 should have been if Polyphony had six more months to polish. Playing it today feels like GT4 with all the content that got left out, plus modern quality-of-life. For anyone who enjoyed the original, it&rsquo;s mandatory.</p>
<p>On top of that goes the community&rsquo;s <strong>Retexture 3.0</strong> (HD texture pack) and the <strong>HD HUD</strong> by <a href="https://github.com/Silentwarior112/GT4-HD-HUD-Pack"target="_blank" rel="noopener">Silentwarior112</a>. With those three pieces together (Spec II Mod + Retexture + HD HUD), GT4 runs at 1440p SSAA on PCSX2 2.7.x with 16x AF, FXAA, and a look that&rsquo;s honestly indistinguishable from a new low-poly-style release. It&rsquo;s my current favorite to sit down and play long.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disc</td>
          <td>GT4 USA (SCUS-97328) or Spec II (SCUS-97436, CRC <code>4CE521F2</code>)</td>
      </tr>
      <tr>
          <td>HD HUD pack</td>
          <td>Installed via symlink (Silentwarior112)</td>
      </tr>
      <tr>
          <td>Retexture 3.0</td>
          <td>Installed</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>On (renamed to Spec II CRC if using Spec II)</td>
      </tr>
      <tr>
          <td>Silent&rsquo;s trigger/camera patches</td>
          <td>On</td>
      </tr>
      <tr>
          <td>Deinterlace (Spec II)</td>
          <td>Mode 8 Adaptive TFF</td>
      </tr>
      <tr>
          <td>ShadeBoost (Spec II)</td>
          <td>Saturation +10, Brightness +3, Contrast +2</td>
      </tr>
      <tr>
          <td>FXAA + PCRTC antiblur</td>
          <td>Global</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
  </tbody>
</table>
<p>Spec II users need to pay attention to the CRC: the vanilla GT4 HD packs were built for <code>77E61C8A</code> (USA), so on Spec II with CRC <code>4CE521F2</code> I symlink the assets into the CRC-suffixed folder that PCSX2 looks for. The widescreen pnach was also renamed to match the Spec II CRC. Without that, the game runs but the mods don&rsquo;t load.</p>
<p><strong>In-game reminders:</strong> same as GT3, the widescreen pnach only unlocks the option, you still need to enter Options in-game and switch to 16:9. And important: <strong>change the video mode to progressive 480p</strong> (also in Options). The default is interlaced 480i, which looks terrible on a modern screen. Spec II Mod has native progressive support, just flip it on.</p>
<p>If I could recommend a single GT in the series for someone starting today, it would be this one. If you like racing and never played GT4 in full, you&rsquo;re missing out.</p>
<h3>Enthusia Professional Racing<span class="hx:absolute hx:-mt-20" id="enthusia-professional-racing"></span>
    <a href="#enthusia-professional-racing" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/enthusia.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Enthusia is the forgotten Konami game from 2005. Launched in GT4&rsquo;s shadow and died on the shelves. It&rsquo;s a shame, because <strong>Enthusia&rsquo;s physics was more realistic than GT4&rsquo;s</strong>. Car weight, mass transfer, limit behavior, all closer to real-world behavior. The sim-racing community of the 2000s knew this and the game has a cult following today.</p>
<p>Enthusia&rsquo;s problem was the Enthusia Points system: you earned points for driving clean, lost them for crashing, and the game&rsquo;s progression depended on those points. A lot of people found it punitive. I found it perfect. It forced you to drive with your head.</p>
<p>On PCSX2 with retexture and widescreen, the game looks great. Strong recommendation for anyone who wants something different from Gran Turismo.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disc</td>
          <td>Enthusia Professional Racing (SLUS-20967)</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>On</td>
      </tr>
      <tr>
          <td>Retexture</td>
          <td>On</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
  </tbody>
</table>
<h3>Ridge Racer V<span class="hx:absolute hx:-mt-20" id="ridge-racer-v"></span>
    <a href="#ridge-racer-v" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/ridge%20racer%20v.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Ridge Racer is its own thing. Not simcade, pure arcade. Drift everything, cartoony grip, electronic soundtrack, saturated colors. Ridge Racer V was the Japanese PS2 launch title and it carries that &ldquo;show what the console can do&rdquo; vibe, with massive scenery and a sense of speed GT never attempted.</p>
<p>On PCSX2 it runs well, with a catch: car textures sometimes flicker on the hardware renderer (PCSX2 issues <a href="https://github.com/PCSX2/pcsx2/issues/3639"target="_blank" rel="noopener">#3639</a> and <a href="https://github.com/PCSX2/pcsx2/issues/13729"target="_blank" rel="noopener">#13729</a>). Software renderer fixes it but kills performance. I stay on hardware and accept the occasional flicker.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disc</td>
          <td>Ridge Racer V (SLUS-20002)</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>On</td>
      </tr>
      <tr>
          <td>No-interlace pnach</td>
          <td>On</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
  </tbody>
</table>
<h3>Colin McRae Rally 3, 4, 5 (PS2)<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-3-4-5-ps2"></span>
    <a href="#colin-mcrae-rally-3-4-5-ps2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>PS2 Colin McRae versions run well on PCSX2 with default configs. CMR3 and CMR04 are good. CMR05 (the last before the rebrand to Dirt) is the best of the three technically. But again: <strong>if you&rsquo;re on PC, look for the native version</strong>. The PS2 ports were relatively inferior to PC of the era, ran at lower resolution, and a modern PC with a repack will hit 144Hz easily.</p>
<p>I keep them on PCSX2 just for collection completeness.</p>
<h2>PS3 on RPCS3<span class="hx:absolute hx:-mt-20" id="ps3-on-rpcs3"></span>
    <a href="#ps3-on-rpcs3" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Gran Turismo 5<span class="hx:absolute hx:-mt-20" id="gran-turismo-5"></span>
    <a href="#gran-turismo-5" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%205.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT5 is controversial. Polyphony promised the world and delivered a somewhat fragmented game: gorgeous Premium models next to Standards that were basically HD GT4 cars, a level system some loved and others hated, online that went to mud when PSN service was cut, a Top Gear mode that was weird. The contemporary critical reception was mixed, and I remember entire communities fighting over whether the game was a disappointment or a masterpiece.</p>
<p>For me, it&rsquo;s a masterpiece. GT5 has the Course Maker (design your own track from real sections), real 24h Nurburgring with dynamic weather, the Moon Rover (yes, a drive-on-the-moon mode), Red Bull X-Challenge with Vettel, and the single-player endurance experience is ridiculously big. Anyone who went deep into the game knows there&rsquo;s content for a whole year.</p>
<p>On RPCS3 in 2026, GT5 runs <strong>really well</strong>. Stable, without the serious bugs from years past. It&rsquo;s the most stable GT I have emulated today.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Serial</td>
          <td>BCUS98114 (US) or BCES00569 (EU)</td>
      </tr>
      <tr>
          <td>Resolution Scale</td>
          <td>300%</td>
      </tr>
      <tr>
          <td>Shader Precision</td>
          <td>Ultra</td>
      </tr>
      <tr>
          <td>Force High Precision Z</td>
          <td>On</td>
      </tr>
      <tr>
          <td>SPU XFloat</td>
          <td>Accurate</td>
      </tr>
      <tr>
          <td>Multithreaded RSX</td>
          <td>On</td>
      </tr>
      <tr>
          <td>WCB / RCB</td>
          <td>Off</td>
      </tr>
  </tbody>
</table>
<p>The Shader Precision Ultra + Force High Precision Z combo kills the typical RSX dithering that made GT5 look grainy on RPCS3 in years past. Without those two, asphalt looks permanently noisy. With them, the game looks clean.</p>
<h3>Gran Turismo 6<span class="hx:absolute hx:-mt-20" id="gran-turismo-6"></span>
    <a href="#gran-turismo-6" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%206.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT6 is one of my favorites in the series. It came out in 2013 for an end-of-life PS3 while the competition was already looking at PS4. Sold poorly. A lot of people didn&rsquo;t play it. That&rsquo;s a shame, because GT6 took the GT5 base, cut the fat (Standards were reduced), added more Premiums, better Moon circuit sessions, dynamic weather on more tracks, physics refinement. It&rsquo;s basically GT5 polished.</p>
<p>On content, GT6 is comparable to GT4. A lot of old-guard players put GT4 above it because of the PS2-era charm. I stay between the two and go GT6 on a normal day, GT4 on a nostalgic day.</p>
<p>On RPCS3, GT6 has a serious catch: <strong>patches 1.06 and beyond regress visually</strong>. Black surfaces on cars in the garage menu, flicker in cockpit view, full-screen flashing on certain tracks. The version that works well in 2026 is <strong>pinned at v1.05</strong>. The repo&rsquo;s <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/scripts/extract_ps3_dlc.py"target="_blank" rel="noopener"><code>extract_ps3_dlc.py</code></a> script has a per-title version ceiling specifically to pin GT6 at 1.05 even when PSN serves 1.22 as &ldquo;most recent&rdquo;.</p>
<p>Also, Force CPU Blit is mandatory. Without it, full-screen flicker in the menu. The trade-off is the rear-view mirror stays permanently black. I prefer losing the mirror to having the flicker. Anyone who wants the mirror can enable Write Color Buffers, but then resolution drops to 720p native.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Serial</td>
          <td>BCES01893 + regional variants</td>
      </tr>
      <tr>
          <td>Version pinned</td>
          <td>v1.05 (patches 1.06+ cause black surfaces)</td>
      </tr>
      <tr>
          <td>Resolution Scale</td>
          <td>300% (menus) or 200% (heavy gameplay)</td>
      </tr>
      <tr>
          <td>Force CPU Blit</td>
          <td>On (mandatory, kills flicker)</td>
      </tr>
      <tr>
          <td>WCB</td>
          <td>Off (trade-off: black rear mirror)</td>
      </tr>
      <tr>
          <td>Shader Precision</td>
          <td>Ultra</td>
      </tr>
  </tbody>
</table>
<p>In 2026, with these settings, GT6 is in my rotation. Not perfect, but playable enough that I can spend an afternoon doing endurance.</p>
<h3>Ridge Racer 7<span class="hx:absolute hx:-mt-20" id="ridge-racer-7"></span>
    <a href="#ridge-racer-7" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Ridge Racer 7 was a PS3 launch title in 2006 and the last mainline in the franchise. Arcade like Ridge Racer V, drift-first, electronic soundtrack, cinematic camera. For a PS3 of that era it was a pretty tech demo.</p>
<p>On RPCS3 it runs, but with caveats: requires Write Color Buffers on to avoid lighting issues, and the recommended preset in the compat DB is different from my GT preset. I use a dedicated per-game config. Not my favorite, but it&rsquo;s there for completeness.</p>
<p>No video for this one, I&rsquo;ll record later.</p>
<h2>Xbox 360 on Xenia Canary<span class="hx:absolute hx:-mt-20" id="xbox-360-on-xenia-canary"></span>
    <a href="#xbox-360-on-xenia-canary" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>This is the part that caused me the most headaches this Sunday. Xbox 360 on Linux is hostile territory. Xenia runs via Wine, each game needs specific tweaks, title updates have to come from archive.org, and Forza Motorsport 4 specifically had serious problems until recently.</p>
<h3>Forza Motorsport 4 (the GOAT)<span class="hx:absolute hx:-mt-20" id="forza-motorsport-4-the-goat"></span>
    <a href="#forza-motorsport-4-the-goat" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20motorsport%204.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FM4 is, for a lot of people, the best Forza Motorsport ever released. Polyphony made Gran Turismo, Turn 10 made Forza, and FM4 is the peak of the 360 era. Beautiful car models for their time, well-designed tracks, solid physics without being a hard sim, Autovista mode with Jeremy Clarkson narration, epic soundtrack, and that feeling of the Xbox 360 being pushed to its limit.</p>
<p>FM4 on Xenia <strong>was a nightmare until recently</strong>. Severe shadow artifacts, constant texture pop-in on the car, grainy pixelated track textures, sky brightness completely wrong (it came out white-blown-out instead of the deep blue of the original), and worst: frequent audio crashes where XMA (the Xbox 360&rsquo;s proprietary codec) would freeze and the audio would turn into a high-pitched noise until the game hung. I spent hours this Sunday messing with configs, testing builds, searching the xenia-canary issue tracker, testing different title updates.</p>
<p>What unlocked it:</p>
<ul>
<li><strong>Latest Xenia Canary build</strong> (<code>master</code> is stagnant, Canary tracks active development)</li>
<li><strong>Render Target Path:</strong> <code>rtv</code> (the default works fine for FM4)</li>
<li><strong>Title Update 1.0.17.0</strong> installed via Xenia Manager (version matters, older ones regress shadows)</li>
<li><strong>Vulkan GPU with NVIDIA ICD forced</strong> (on my hybrid setup with AMD iGPU, I had to hardforce the 5090)</li>
</ul>
<p>After that, FM4 ran. Not perfectly. Still gets the occasional audio crash (<a href="https://github.com/xenia-canary/xenia-canary/issues/161"target="_blank" rel="noopener">xenia-canary issue #161</a> open). But it&rsquo;s playable, and I spent two hours of the Sunday at Top Gear Test Track just admiring the Koenigsegg CCX. Worth every hour of debugging.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Latest Xenia Canary</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td><code>rtv</code> (default)</td>
      </tr>
      <tr>
          <td>Title Update</td>
          <td>1.0.17.0</td>
      </tr>
      <tr>
          <td>Wine Prefix</td>
          <td>dedicated via Xenia Manager</td>
      </tr>
      <tr>
          <td>GPU</td>
          <td>NVIDIA via <code>VK_ICD_FILENAMES</code> hardforce</td>
      </tr>
  </tbody>
</table>
<h3>Forza Motorsport 3<span class="hx:absolute hx:-mt-20" id="forza-motorsport-3"></span>
    <a href="#forza-motorsport-3" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20motorsport%203.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FM3 is one of the solid entries in the series, sitting between FM2&rsquo;s rougher charm and FM4&rsquo;s technical peak. Getting it to emulate was surprisingly annoying, because I was using the wrong ISO.</p>
<p>There are two releases of FM3: the <strong>retail</strong> (original disc sold in stores) and the <strong>Ultimate Edition</strong> (all DLCs packaged on a second disc). I was trying to run the Ultimate, which combines both discs into a hybrid ISO that Xenia can&rsquo;t parse correctly. I swapped to the standard retail + DLCs installed separately via Title Update, and bang, the game ran first try.</p>
<p>Moral: if your FM3 isn&rsquo;t running on Xenia, check that it&rsquo;s retail. Ultimate breaks.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Version</td>
          <td>Retail (NOT Ultimate Edition)</td>
      </tr>
      <tr>
          <td>Build</td>
          <td>Latest Xenia Canary</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td><code>rtv</code></td>
      </tr>
      <tr>
          <td>DLCs</td>
          <td>Installed via Xenia Manager separately</td>
      </tr>
  </tbody>
</table>
<h3>Forza Motorsport 2<span class="hx:absolute hx:-mt-20" id="forza-motorsport-2"></span>
    <a href="#forza-motorsport-2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20motorsport%202.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FM2 is from 2007, first-gen 360. Less refined systems, more basic modeling, but that original Forza charm some prefer to FM4&rsquo;s sophistication. On Xenia it ran practically out of the box. No special tweak.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Xenia Canary</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td>default</td>
      </tr>
  </tbody>
</table>
<h3>Forza Horizon 2<span class="hx:absolute hx:-mt-20" id="forza-horizon-2"></span>
    <a href="#forza-horizon-2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20horizon%202.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FH2 was the most polished Forza Horizon of the 360 generation (there&rsquo;s also the Xbox One version, which is technically better, but the 360 has its charm because of the smaller map and more focused progression). Open world in southern Europe, iconic soundtrack, Horizon festival at its peak.</p>
<p>On Xenia it ran without asking for anything extra. Same as FM2.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Xenia Canary</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td>default</td>
      </tr>
  </tbody>
</table>
<h3>Forza Horizon 1 (with XE Mod)<span class="hx:absolute hx:-mt-20" id="forza-horizon-1-with-xe-mod"></span>
    <a href="#forza-horizon-1-with-xe-mod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20horizon%201%20xe%20mod.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Original FH1 runs OK on Xenia, but there&rsquo;s a community maintaining the <strong>Forza Horizon 1 XE Mod</strong>, which improves traffic AI, rebalances progression, adds cars that were cut, fixes known bugs, and brings quality-of-life tweaks to the UI. I tested the XE Mod this Sunday and it&rsquo;s worth it. The game feels more alive and progression feels fairer.</p>
<p>Install details for the XE Mod are on the project site. Basically it&rsquo;s a patch over the original ISO + a few custom title updates.</p>
<p>For comparison, here&rsquo;s FH1 <strong>without</strong> the mod, running vanilla:</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20horizon%201.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>The most visible difference is traffic and the variety of situations that come up, but the XE Mod has a lot of stuff you only notice after a few hours.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Xenia Canary</td>
      </tr>
      <tr>
          <td>XE Mod</td>
          <td>Applied over original ISO</td>
      </tr>
      <tr>
          <td>Title Updates</td>
          <td>Version pinned by XE Mod</td>
      </tr>
  </tbody>
</table>
<h3>Project Gotham Racing 3 and 4<span class="hx:absolute hx:-mt-20" id="project-gotham-racing-3-and-4"></span>
    <a href="#project-gotham-racing-3-and-4" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>PGR3 was a 360 launch title in 2005 and still looks gorgeous today. Urban tracks (New York, Tokyo, Nurburgring, Las Vegas), kudos system, cinematic camera. PGR4 added dynamic weather (rain and snow) and motorcycles (some people love them, I prefer to pretend they don&rsquo;t exist).</p>
<p>PGR3 on Xenia Canary runs reasonably well with default configs. PGR4 needs a specific tweak: <strong><code>render_target_path_vulkan = &quot;fsi&quot;</code></strong> in the config. Without it, some tracks break with severe visual artifacts (green bars across the screen in snow races).</p>
<p>And PGR4 on NVIDIA has a known audio bug: XMA decoding produces intermittent garbage (open xenia-canary issue, no resolution). Not game-breaking, but annoying.</p>
<table>
  <thead>
      <tr>
          <th>Game</th>
          <th>Relevant setting</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>PGR3</td>
          <td>Default</td>
      </tr>
      <tr>
          <td>PGR4</td>
          <td><code>render_target_path_vulkan = &quot;fsi&quot;</code>, accept the XMA audio bug</td>
      </tr>
  </tbody>
</table>
<h3>Ridge Racer 6<span class="hx:absolute hx:-mt-20" id="ridge-racer-6"></span>
    <a href="#ridge-racer-6" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Ridge Racer 6 was the Ridge Racer of the 360, skipping the 7 that stayed on PS3. Pure arcade, like Ridge Racer V of the PS2. On Xenia Canary it runs with default config, no special tweak. Fun for short sessions. No video for this one in the article, but it&rsquo;s in the catalog running.</p>
<h2>PS4 on shadPS4: the state of the emulator<span class="hx:absolute hx:-mt-20" id="ps4-on-shadps4-the-state-of-the-emulator"></span>
    <a href="#ps4-on-shadps4-the-state-of-the-emulator" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><a href="https://shadps4.net/"target="_blank" rel="noopener">shadPS4</a> <strong>still doesn&rsquo;t have a stable release</strong>. The project is under active development, the main branch changes several times a week, and &ldquo;what works today&rdquo; can regress on a rebase tomorrow. That said, <strong>many games already boot and run well enough</strong>. The community list is on the <a href="https://github.com/shadps4-compatibility/shadps4-game-compatibility/issues"target="_blank" rel="noopener">compatibility tracker</a> and grows every week.</p>
<p>The difference between shadPS4 and the other emulators in this article is the kind of effort. PCSX2, RPCS3, Xenia are mature projects where each game has a reasonably up-to-date wiki page. shadPS4 is the opposite: you read a Reddit guide, try to reproduce, discover the guide uses a specific fork (not the official main), or a two-month-old commit, or a firmware with <code>sys_modules</code> symlinked in a particular way, or XML patches applied in a specific order that the author didn&rsquo;t document. Reproducing a shadPS4 setup from a YouTube video is like trying to solve a Rubik&rsquo;s Cube blindfolded.</p>
<p>But that doesn&rsquo;t mean you shouldn&rsquo;t try. It means you need to treat shadPS4 as a hobby, not a workflow. And if you have the patience, you can pull off impressive results.</p>
<h2>Driveclub: the impossible, finally possible<span class="hx:absolute hx:-mt-20" id="driveclub-the-impossible-finally-possible"></span>
    <a href="#driveclub-the-impossible-finally-possible" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/driveclub.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Driveclub is my Moby Dick of emulation. It&rsquo;s the only PS4 game I really want to run. Released in 2014 by Evolution Studios, had a catastrophic launch (online servers collapsed on day one), got patched over two years until it became one of the best racing games of the generation, and was discontinued by Sony when Evolution was closed in 2016. No PC port. No remaster. No sequel. Only exists on PS4.</p>
<p>And today, after many hours of debugging, <strong>it&rsquo;s running here</strong>. Main menu loads, intro plays, races start, controller responds, audio works, daytime tracks visible, night tracks with headlights lighting the road. The video above is real gameplay, stable 30 FPS, v1.28 with DLC content accessible.</p>
<p>Getting here required dismantling several traps. Worth documenting, because most online guides don&rsquo;t mention any of them.</p>
<h3>Trap 1: the FMOD loop error that wasn&rsquo;t FMOD<span class="hx:absolute hx:-mt-20" id="trap-1-the-fmod-loop-error-that-wasnt-fmod"></span>
    <a href="#trap-1-the-fmod-loop-error-that-wasnt-fmod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>When you try to boot Driveclub on shadPS4 straight from the retail PKG, the log hits an infinite loop of:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/app0/audio/fmodstudio/masterbank.bank failed, file does not exist</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 first reaction is to investigate FMOD. Some people online spent hours messing with sceFont, libtbb, sceNgs2, convinced it was an audio or HLE lib problem. <strong>None of that is the cause</strong>. The <code>masterbank.bank</code> file literally doesn&rsquo;t exist on disk, because it&rsquo;s packed inside the <code>gameNNN.dat</code> files in a custom Evolution Studios archive format that shadPS4&rsquo;s VFS can&rsquo;t read. Same story for fonts, models, shaders. All the game&rsquo;s content lives inside the <code>.dat</code> files.</p>
<p>The only public tool that parses that format is <strong><a href="https://github.com/Nenkai/DriveClubFS"target="_blank" rel="noopener">Nenkai&rsquo;s DriveClubFS</a></strong>. It&rsquo;s a .NET app that reads <code>game.ndx</code> (the index) and decompresses the <code>.dat</code> files into loose files. Without that unpack, shadPS4 can&rsquo;t open anything.</p>
<p>Build detail: the DriveClubFS project targets <code>net9.0</code>, and Arch has <code>net8</code> and <code>net10</code>. You need to retarget the csproj to <code>net10.0</code> before building. The exact command is documented in the repo&rsquo;s <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener"><code>docs/driveclub-shadps4.md</code></a>.</p>
<h3>Trap 2: v1.28 is a patch, not a base installer<span class="hx:absolute hx:-mt-20" id="trap-2-v128-is-a-patch-not-a-base-installer"></span>
    <a href="#trap-2-v128-is-a-patch-not-a-base-installer" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Like every PS4 update, v1.28 is a patch that layers on top of a base install. The v1.28 PKG ships the updated eboot, new-content <code>.dat</code> files, and patch content (<code>sce_sys/about/right.sprx</code>), but <strong>no <code>param.sfo</code>, no <code>disc_info.dat</code>, no <code>keystone</code></strong>. Those metadata files live in the v1.00 base — on a real PS4 they&rsquo;d already be on disk from the original install.</p>
<p>And there&rsquo;s more: v1.28&rsquo;s <code>game.ndx</code> references both the base <code>.dat</code> files and the new ones. Running DriveClubFS against v1.28 alone makes the index point at files that aren&rsquo;t there.</p>
<p>The cruelest symptom hits later: the game boots, the Qt Launcher shows the tile, you click, the loading screen comes up. Then the game decides no content &ldquo;has been released yet&rdquo; and asks you to download. Download obviously isn&rsquo;t happening because PSN for PS4 isn&rsquo;t accessible. You get stuck thinking it&rsquo;s a network bug, when actually it&rsquo;s missing metadata.</p>
<p>The correct flow is to treat v1.28 as an overlay on top of the base. First, extract the v1.00 PKG into a dedicated dir (<code>CUSA00003.v100-working-backup/</code> works well, and doubles as a rollback snapshot if some experiment breaks the live install). Then extract v1.28 into staging and symlink the base&rsquo;s low-index <code>.dat</code> files into the staging dir so DriveClubFS sees the full set. Run the unpack. You get 8018 loose files, ~47 GB.</p>
<p>Move that into the live <code>CUSA00003/</code> dir, copy v1.28&rsquo;s updated eboot and new patch content on top, then pull <code>param.sfo</code>, <code>disc_info.dat</code>, and <code>keystone</code> from the v1.00 base. The base&rsquo;s <code>param.sfo</code> already has <code>APP_VER = 01.28</code> recorded (because it was the version that received the patch originally), so there&rsquo;s no version conflict. The v1.28 eboot reads those files and unlocks the content.</p>
<h3>Trap 3: the 60fps XML patch produces slow-motion<span class="hx:absolute hx:-mt-20" id="trap-3-the-60fps-xml-patch-produces-slow-motion"></span>
    <a href="#trap-3-the-60fps-xml-patch-produces-slow-motion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>There&rsquo;s a community <code>Driveclub.xml</code> patch that makes the game render at 60fps. Intuitively, it looks like pure upside. In practice, the patch rewrites the render rate but doesn&rsquo;t touch the game&rsquo;s fixed logic tickrate, which is locked at 30fps. On a real PS4 Pro, the hardware reconciles the mismatch. On shadPS4, it doesn&rsquo;t.</p>
<p>Result: the game renders smoothly at 60fps, but the physics, AI, audio, race timing all run in slow motion. You press the throttle and the car takes a perceptible two seconds to launch. Looks like a random bug until you understand the mismatch.</p>
<p>Disabling the XML patch is the move (just rename the file to something like <code>.disabled</code>). Without the patch, the game runs at native 30 FPS, same speed as a stock PS4 base. Which is what we want. If someone in the future produces a patch that moves both rates (render + logic), we&rsquo;ll flip it back on.</p>
<h3>Trap 4: pipeline cache has to be on<span class="hx:absolute hx:-mt-20" id="trap-4-pipeline-cache-has-to-be-on"></span>
    <a href="#trap-4-pipeline-cache-has-to-be-on" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>shadPS4&rsquo;s default ships with <code>pipeline_cache_enabled = false</code>. On a large game like Driveclub, which has hundreds of Vulkan shaders and pipelines, that means every cold launch recompiles everything. My first launch counted ~864 shaders and ~590 pipelines being compiled in real time. The menu animated at 2-3 fps. A minute later, with everything compiled, it snapped to normal fluidity. Next launch, same from-scratch compilation cycle.</p>
<p>Just enable <code>&quot;pipeline_cache_enabled&quot;: true</code> in the global <code>config.json</code>. The first session pays the one-time compilation cost, subsequent sessions read from cache and start up instantly.</p>
<h3>Trap 5: TOML silently ignored<span class="hx:absolute hx:-mt-20" id="trap-5-toml-silently-ignored"></span>
    <a href="#trap-5-toml-silently-ignored" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>shadPS4 migrated from TOML to JSON for per-game configs in late 2025. If you have an old <code>CUSA00003.toml</code> in the <code>custom_configs/</code> folder, it&rsquo;s silently ignored, no warning in the log. You think your config is active, in reality you&rsquo;re running all defaults.</p>
<p>Delete any TOML and use <code>CUSA00003.json</code>. The per-game configs that work for Driveclub are specific and detailed, all documented in the <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener"><code>docs/driveclub-shadps4.md</code></a> of the repo.</p>
<h3>Trap 6: controller not detected without hidapi hints<span class="hx:absolute hx:-mt-20" id="trap-6-controller-not-detected-without-hidapi-hints"></span>
    <a href="#trap-6-controller-not-detected-without-hidapi-hints" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>shadPS4&rsquo;s Qt Launcher calls an internal <code>Shadps4-sdl.AppImage</code>. Without specific SDL hidapi env vars, the controller (8BitDo Ultimate 2 in my case) isn&rsquo;t detected, even though it works in every other emulator. The fix is to wrap the AppImage in a shell that exports <code>SDL_JOYSTICK_HIDAPI=1</code> and platform-specific variants (PS4, PS5, Xbox), before executing the original.</p>
<p>Plugging the controller in <strong>before</strong> opening the Qt Launcher also helps. Hot-plug works during the game, not before.</p>
<h3>Trap 7: night tracks are pitch black on upstream<span class="hx:absolute hx:-mt-20" id="trap-7-night-tracks-are-pitch-black-on-upstream"></span>
    <a href="#trap-7-night-tracks-are-pitch-black-on-upstream" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>This one needed a code patch, not just config. On upstream shadPS4, Driveclub night tracks came out pitch black. The HUD rendered on top, but the track, the car, everything hidden in absolute black. Not even the car&rsquo;s own headlights lit anything up.</p>
<p>Investigation pointed at the log repeating 1325 times the message <code>ResolveDepthOverlap: Unimplemented depth overlap copy</code>. The cause: Driveclub uses a forward+ renderer, which writes scene depth into a 4x MSAA D32Sfloat buffer, then reads that same buffer as <code>R32G32B32A32Sfloat</code> 1x for effects like SSAO, volumetrics, and dynamic lighting (headlights). shadPS4 upstream has a path for MSAA → MSAA and for color → color as MSAA depth, but <strong>it didn&rsquo;t have a path for &ldquo;MSAA depth 4x becomes color 1x&rdquo;</strong> in that specific format. It falls into the final <code>else</code>, frees the buffer, and the shader reads uninitialized memory — i.e., black.</p>
<p>I implemented in my local shadPS4 fork a <code>ReinterpretMsDepthAsColor</code> symmetric to the existing <code>ReinterpretColorAsMsDepth</code>. It&rsquo;s a tiny fragment shader (<code>ms_depth_to_color.frag</code>) that does <code>texelFetch</code> of sample 0&rsquo;s depth and writes it as <code>vec4(depth, 0, 0, 1)</code> to the color attachment. Plus a <code>BlitHelper</code> helper and a new <code>else if</code> branch in <code>TextureCache::ResolveDepthOverlap</code>. With that, night tracks light up properly, opponent headlights appear, AO and volumetrics on day scenes get more precise too.</p>
<p>The fix lives on a <code>gamma-debug</code> branch of my fork, ready to become an upstream PR. Until it merges, anyone who wants to race at night on Driveclub on shadPS4 needs to build from that branch. Anyone sticking to daytime can use upstream nightly directly.</p>
<h3>What finally worked<span class="hx:absolute hx:-mt-20" id="what-finally-worked"></span>
    <a href="#what-finally-worked" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Component</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>shadPS4</td>
          <td>Upstream Pre-release (nightly) for daytime; <code>gamma-debug</code> fork for night (until MSAA depth fix lands upstream)</td>
      </tr>
      <tr>
          <td>Driveclub</td>
          <td><strong>v1.28 base</strong> (cumulative patch, ~47 GB, 8018 files)</td>
      </tr>
      <tr>
          <td>Extraction</td>
          <td><a href="https://github.com/shadps4-emu/ShadPKG"target="_blank" rel="noopener">ShadPKG</a> → <a href="https://github.com/Nenkai/DriveClubFS"target="_blank" rel="noopener">DriveClubFS</a> (retargeted to net10.0)</td>
      </tr>
      <tr>
          <td>Metadata</td>
          <td>Restore <code>param.sfo</code>, <code>disc_info.dat</code>, <code>keystone</code> from the v1.00 PKG</td>
      </tr>
      <tr>
          <td>Global config</td>
          <td><code>pipeline_cache_enabled: true</code>, <code>readbacks_mode: 0</code></td>
      </tr>
      <tr>
          <td>Per-game config</td>
          <td>JSON at <code>custom_configs/CUSA00003.json</code></td>
      </tr>
      <tr>
          <td><code>vblank_frequency</code></td>
          <td>60</td>
      </tr>
      <tr>
          <td><code>gpu_id</code></td>
          <td>0 (NVIDIA dGPU hardforced)</td>
      </tr>
      <tr>
          <td><code>Driveclub.xml</code> patch</td>
          <td><strong>Disabled</strong> (60fps produces slow-motion, root cause: fixed logic tickrate)</td>
      </tr>
      <tr>
          <td>Controller</td>
          <td>AppImage wrapper with <code>SDL_JOYSTICK_HIDAPI=1</code> + variants</td>
      </tr>
      <tr>
          <td>Qt Launcher</td>
          <td><code>checkForUpdates=false</code> to avoid GitHub rate-limit dialog</td>
      </tr>
  </tbody>
</table>
<p>All of this is encapsulated in <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener"><code>docs/driveclub-shadps4.md</code></a> in the repo, with the exact step-by-step for extraction, config, and wrapper. The full technical investigation (5 phases, detailed engineering log) lives in the fork&rsquo;s <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-v128-investigation.md"target="_blank" rel="noopener"><code>driveclub-v128-investigation.md</code></a>.</p>
<h3>How playable it is today<span class="hx:absolute hx:-mt-20" id="how-playable-it-is-today"></span>
    <a href="#how-playable-it-is-today" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Good. The video above is real gameplay, stable 30 FPS, physics responding, audio in sync, HUD correct, v1.28 with DLC accessible. Day races and night races both work (with the MSAA depth fork for night). What&rsquo;s left as limitation:</p>
<ul>
<li><strong>Daytime brightness sits a bit below what the PS4 delivered.</strong> Not an emulator bug. I spent hours testing tonemap, auto-exposure, gamma curves, ACES, various post-processing shaders, and in the end discovered the game <strong>writes already-correct SDR</strong> to the framebuffer. What was missing was the display-calibration pipeline that PS4 firmware 4.0+ applied on the outside, and that Linux doesn&rsquo;t have. The in-game brightness slider (which on shadPS4 maps to <code>sceVideoOutAdjustColor</code>) works, but only up to the point where the game expected the firmware to pick up the rest. The 5-second dim at the start of a race is also the game&rsquo;s own cinematic fade-in VFX, not an emulator fault. Accepted.</li>
<li><strong>Native 30 FPS.</strong> No viable 60fps patch (due to the fixed logic tickrate explained above). Stock PS4 speed.</li>
<li><strong>MSAA depth fix still on the fork.</strong> PR candidate ready, awaiting upstream merge.</li>
</ul>
<p><strong>&ldquo;Achievement Unlocked&rdquo;.</strong> The game runs, with sound, controller, gameplay, day and night, and I complete a whole race without crashing. That&rsquo;s a personal milestone. I spent years trying to get this game running on Linux and would always hit a point where I just gave up. Now it&rsquo;s part of my ES-DE, shortcut on the desktop, sitting there waiting for me to play.</p>
<p>If you want to replicate, the repo is public. Good luck. You&rsquo;re going to need patience.</p>
<h2>&ldquo;Achievement Unlocked&rdquo;<span class="hx:absolute hx:-mt-20" id="achievement-unlocked"></span>
    <a href="#achievement-unlocked" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;ve been trying to make these games work, at some level, since each respective emulator came out of alpha. I&rsquo;ve been running GT2 since ePSXe. I&rsquo;ve been messing with GT4 since PCSX2 0.9.6. I tried to get FM4 running on Xenia twice in past years and gave up both times. And I looked at shadPS4&rsquo;s face three times before this Sunday and swallowed hard.</p>
<p>Today it closed. The two monsters that opened this article (Driveclub on shadPS4 and FM4 with Project Forza Plus on Xenia) are running, along with the rest of the Gran Turismo, Forza, Ridge Racer, Colin McRae, and Enthusia collection. All in a single setup, reproducible, automated. I&rsquo;m now part of the <strong>very small group of people who brute-forced their way into emulating Driveclub on Linux</strong>, and that alone made the Sunday unforgettable.</p>
<p>The frustration of years wasn&rsquo;t lack of information. It was the OPPOSITE. Too much information, poorly indexed, scattered across dead forums, YouTube videos that went stale, Reddit threads with solutions for two-year-old emulator versions that don&rsquo;t apply today, wikis that are out of date on half the pages, and mods with VERY specific requirements of CRC, version, patch, path, setting. Every time I sat down to get one of these games running right, I spent three hours reading conflicting material before writing the first line of config. Worse still when the good material was buried under wrong diagnoses, like the Driveclub FMOD loop everyone investigated as if it was an audio problem when in reality it was Evolution Studios&rsquo; proprietary archive format.</p>
<p>What changed in the last year is that Claude Code can be my aggregator. I tell Claude to hit the Xenia issue tracker, the PCSX2 wiki, the RPCS3 subreddit, shadPS4 PRs, cross-reference all that info with the emulator version I have installed, cross-reference with the logs I&rsquo;m looking at, and give me back the combination that works today. It reads source code when needed, understands why <code>Force CPU Blit</code> changes behavior on GT6, finds which FM4 title update was the one that fixed the shadow bug, discovers that DriveClubFS needs a retarget to .NET 10 before compiling. What used to take three weekends of research now takes an afternoon of assisted execution.</p>
<p>This Sunday is the consolidated result of all that work. <a href="https://github.com/akitaonrails/distrobox-gaming"target="_blank" rel="noopener">distrobox-gaming</a> is that knowledge turned into code, reproducible, automated. Starting on a fresh machine, one <code>ansible-playbook site.yml</code> puts me back in this state in under two hours. Fewer than 10 commands separate &ldquo;vanilla Arch Linux&rdquo; from &ldquo;Driveclub running&rdquo;.</p>
<p>If anyone wants to contribute, test on different hardware, report a bug, or send a config that&rsquo;s missing, the repo is open. The more people test on different setups, the more resilient the project gets. If you improve the Driveclub situation (get the MSAA depth fix merged upstream, a 60fps patch that also fixes the logic tickrate, font dumps that don&rsquo;t need jailbroken hardware), send a PR and I&rsquo;ll embrace it.</p>
<p>For now, I&rsquo;m going to do a race in Iceland on Driveclub. Good Sunday night.</p>
]]></content:encoded><category>gaming</category><category>emulation</category><category>linux</category></item><item><title>Omarchy on the Thinkpad T14 Gen 6: Mini-Review and Full Setup</title><link>https://akitaonrails.github.io/en/2026/04/18/omarchy-on-thinkpad-t14-gen-6/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/04/18/omarchy-on-thinkpad-t14-gen-6/</guid><pubDate>Sat, 18 Apr 2026 08:30:00 -0300</pubDate><description>&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/closed-lid.jpg" alt="Thinkpad T14 Gen 6 closed, next to its USB-C charger" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;I bought a Lenovo Thinkpad T14 Gen 6 and installed Omarchy on it. It&amp;rsquo;s not my main machine, and it&amp;rsquo;s not supposed to be. It&amp;rsquo;s a companion: a notebook I can open on the 3D printing office desk, SSH into the desktop, fire up Claude Code, access files on the NAS, debug the network through ethernet without hunting for a USB dongle and a long cable. This article covers the hardware choice, the Omarchy setup on top, the customizations specific to a laptop and to this Thinkpad in particular, and the architecture decisions that might not be obvious.&lt;/p&gt;</description><content:encoded><![CDATA[<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/closed-lid.jpg" alt="Thinkpad T14 Gen 6 closed, next to its USB-C charger"  loading="lazy" /></p>
<p>I bought a Lenovo Thinkpad T14 Gen 6 and installed Omarchy on it. It&rsquo;s not my main machine, and it&rsquo;s not supposed to be. It&rsquo;s a companion: a notebook I can open on the 3D printing office desk, SSH into the desktop, fire up Claude Code, access files on the NAS, debug the network through ethernet without hunting for a USB dongle and a long cable. This article covers the hardware choice, the Omarchy setup on top, the customizations specific to a laptop and to this Thinkpad in particular, and the architecture decisions that might not be obvious.</p>
<h2>Mini-review of the Thinkpad T14 Gen 6<span class="hx:absolute hx:-mt-20" id="mini-review-of-the-thinkpad-t14-gen-6"></span>
    <a href="#mini-review-of-the-thinkpad-t14-gen-6" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/open-lid-turned-off.jpg" alt="Thinkpad T14 Gen 6 open with the screen off, showing the keyboard and the Intel Core Ultra 5 sticker"  loading="lazy" /></p>
<p>Let&rsquo;s get this out of the way: this isn&rsquo;t the notebook of my dreams. If I were picking by looks, I&rsquo;d grab an Asus Zenbook S 14 with an OLED screen. If I were picking by portability, the Thinkpad T14s with the aluminum shell, which is lighter and much better looking. The regular T14 has a 14&quot; 1920x1200 IPS panel, 400 nits, 60Hz, no HDR. It gets the job done, but it&rsquo;s a far cry from the Zenbook&rsquo;s screen. The shell is plastic with a rubberized finish, which makes it scratch-resistant but not premium. Reviews from <a href="https://www.notebookcheck.net/AMD-Ryzen-AI-meets-classic-ThinkPad-Lenovo-ThinkPad-T14-Gen-6-AMD-laptop-review.1222690.0.html"target="_blank" rel="noopener">NotebookCheck</a> and <a href="https://www.xda-developers.com/lenovo-thinkpad-t14-gen-6-review/"target="_blank" rel="noopener">XDA Developers</a> land on the same verdict: good price, good connectivity, fine performance for office work, but the screen is dated.</p>
<p>What makes up for it:</p>
<ul>
<li>Port selection. Full-size HDMI. Gigabit ethernet. USB-A, USB-C Thunderbolt 4, charges over USB-C (not a proprietary charger). For a debug companion, this is exactly what I wanted. The Zenbook S 14, being thinner, cuts ports.</li>
<li>Fingerprint sensor that actually works on Linux (Goodix MOC, works with libfprint on kernel 6.11+). I use this, details later.</li>
<li>Thinkpad keyboard. 1.5mm key travel, trackpoint, classic layout. It&rsquo;s not the best keyboard in the world in 2026, but it&rsquo;s reliable and durable.</li>
<li>Rugged shell. It will fall, it will scratch, it will travel in a backpack. If I put a Zenbook OLED or a Macbook Pro on the 3D printing office desk next to the printer with PLA dust in the air, I&rsquo;d be nervous. The Thinkpad can take a beating.</li>
</ul>
<p>If I wanted a gaming machine, I&rsquo;d get an Asus Zephyrus G14, my favorite gaming notebook. If I wanted a creative work machine, I&rsquo;d get the <a href="https://www.asus.com/laptops/for-home/zenbook/zenbook-duo-ux8406/"target="_blank" rel="noopener">Asus Zenbook Duo (UX8406)</a> with two vertical OLED screens, great for video editing and 3D modeling. I can afford any Macbook Pro or Mac Studio, and I chose not to go that way. I&rsquo;ll explain in the next section.</p>
<h2>My use case<span class="hx:absolute hx:-mt-20" id="my-use-case"></span>
    <a href="#my-use-case" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/open-lid-fastfetch.jpg" alt="Thinkpad open on a 3D printing office bench, with fastfetch showing Omarchy in the terminal"  loading="lazy" /></p>
<p>My main PC is a desktop with Ryzen 9 7950X3D, 96 GB of RAM, RTX 5090 with 32 GB. That&rsquo;s where I work, experiment with local models, run containers, edit the blog. For gaming, I have a separate mini-PC with an RTX 4090. Those two cover 100% of what I need to do at home.</p>
<p>The notebook exists to cover the remaining 1%: sitting on the couch, taking it to the 3D printing office, taking it to the kitchen, short trips. It&rsquo;s not meant to replace the desktop. It&rsquo;s meant to give me remote access to the desktop when I&rsquo;m away from it.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/ssh-to-remote-desktop-on-left.png" alt="Hyprland with two terminals side by side: left is an SSH session on the main desktop (96 GB, RTX 5090), right is the Thinkpad itself. Same UI, two machines."  loading="lazy" /></p>
<p>In practice it looks like this: open the notebook, split the Hyprland layout, left pane SSHs into the main desktop, right pane is the notebook itself. Same Omarchy on both, same keybindings, same bash. The notebook becomes an extension of the desktop, not a parallel environment I have to re-learn in my head every time I switch.</p>
<h3>SSH from outside the house: Tailscale<span class="hx:absolute hx:-mt-20" id="ssh-from-outside-the-house-tailscale"></span>
    <a href="#ssh-from-outside-the-house-tailscale" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Inside the local network, SSH is trivial, the notebook talks to the desktop through the internal IP. Outside the house is another story. My home IP is dynamic, opening port 22 to the internet is a terrible idea, and even with DDNS and port forwarding you&rsquo;re putting SSH on the public internet for any scanner to find.</p>
<p>The solution I use is <a href="https://tailscale.com/"target="_blank" rel="noopener">Tailscale</a>. For those who don&rsquo;t know: Tailscale is a mesh VPN built on WireGuard that creates a private network between your devices (the &ldquo;tailnet&rdquo;). Each machine runs the agent, authenticates once, and gets a fixed IP on the private network (something like 100.x.y.z). Traffic between your own devices goes peer-to-peer, encrypted by WireGuard. It doesn&rsquo;t route through Tailscale&rsquo;s central server, they only coordinate NAT traversal. Result: from my notebook in a café anywhere in the world, I run <code>ssh hal9000</code> and land on my home desktop as if I were on the same network.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/tailscale-machines.png" alt="Tailscale admin panel showing my two tailnet machines, hal9000 (desktop) and hal9666 (thinkpad), both with SSH enabled"  loading="lazy" /></p>
<p>More sophisticated options exist: <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/"target="_blank" rel="noopener">Cloudflare Tunnel</a> with Zero Trust to expose services publicly with SSO auth, self-hosted headscale, raw WireGuard with manual config, Nebula, OpenVPN. Each has its use case. If you need to expose services to third parties, control granular per-identity access, or run the whole infrastructure at home without depending on anyone, those options win. In my case, it&rsquo;s just notebook talking to desktop, for short periods (it&rsquo;s not a full week of work, it&rsquo;s a weekend debug session), so free Tailscale covers it. The free tier accepts up to 100 devices and 3 users, way more than I need.</p>
<p>Setup is as simple as it gets:</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"># On Arch/Omarchy</span>
</span></span><span class="line"><span class="cl">sudo pacman -S tailscale
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now tailscaled.service
</span></span><span class="line"><span class="cl">sudo tailscale up --ssh</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>--ssh</code> flag enables <a href="https://tailscale.com/kb/1193/tailscale-ssh/"target="_blank" rel="noopener">Tailscale SSH</a>, which authenticates via tailnet identity instead of a local SSH key. Once you log in through the browser to tailscale, each registered machine can SSH into the others based on ACL policy defined in the admin panel. Zero key management.</p>
<p>I repeat the same on the desktop, log in with the same account, done. Both machines show up in the panel (hal9000 and hal9666 in the screenshot above, both with SSH enabled). From the notebook: <code>ssh hal9000</code>. From the desktop to the notebook: <code>ssh hal9666</code>. No port forwarding, no public IP, no port 22 exposed to the internet. If the notebook is stolen, I remove it from the tailnet with one click.</p>
<p>A practical detail: since the tailnet gives stable names, I added entries to <code>~/.ssh/config</code> to use these short names:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Host hal9000
  HostName hal9000
  User 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>Now <code>ssh hal9000</code> works from anywhere that has Tailscale connected. It&rsquo;s the closest thing to &ldquo;it just works&rdquo; I&rsquo;ve seen for remote SSH.</p>
<h3>Why not a Mac<span class="hx:absolute hx:-mt-20" id="why-not-a-mac"></span>
    <a href="#why-not-a-mac" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I have no use for macOS. As a dev, I live better in native Linux. Every tool I need has a first-class Linux version, and on macOS I&rsquo;d get a second-class version via Homebrew. I don&rsquo;t do iOS, so I don&rsquo;t need XCode. For mobile I use Flutter or Hotwire Native, which run on any OS. iTerm2 and Ghostty on Mac are fine, but Alacritty, Kitty and Ghostty itself on Linux work just as well. Every good piece of software lands on Linux first and gets ported afterwards. Arch with AUR covers everything in a single <code>yay</code>.</p>
<p>For creative work, I haven&rsquo;t done it professionally in years. DaVinci Resolve Studio on Linux is better than Final Cut Pro. Krita or Affinity Photo replace Photoshop for most cases. Clip Studio Paint on Android is better than Procreate. I simply don&rsquo;t have a workflow that depends on Apple, and the App Store annoys me.</p>
<p>For gaming, Mac is terrible. Apple Silicon has a decent GPU for some things, but the native macOS game library is pathetic compared to Windows or Linux. Game Porting Toolkit exists, CrossOver exists, but for anyone who games seriously, it doesn&rsquo;t cut it. I won&rsquo;t be gaming on the Thinkpad, I have the main desktop and the mini-PC with the RTX 4090 for that. But if I ever feel like running Hollow Knight or some indie on a train, I just install Steam and let Proton handle it. Linux became a real gaming platform in the last few years, with Proton/DXVK running almost the entire Steam catalog (check <a href="https://www.protondb.com/"target="_blank" rel="noopener">ProtonDB</a>). I recently wrote about <a href="/en/2026/04/11/emulation-distrobox-with-claude-code/">running my emulation library in distrobox</a> without polluting the host system. With Mac, those options don&rsquo;t exist.</p>
<p>Another argument that always shows up: &ldquo;but a Mac Mini M4 or a Mac Studio with 128GB of unified memory runs large models locally, it&rsquo;s a ChatGPT replacement.&rdquo; I already tested that thesis and wrote a <a href="/en/2026/04/05/testing-llms-open-source-and-commercial-can-anyone-beat-claude-opus/">detailed benchmark</a>. The conclusion: expensive local hardware to run LLMs is a weekend hobby, not a production tool. The open source models that fit don&rsquo;t deliver the quality Claude Opus delivers. I have a <a href="/en/2026/03/31/migrating-my-home-server-with-claude-code/">home server with AMD Strix Halo and 96 GB of unified RAM</a>, I ran models for dozens of hours, and in my real coding flow they&rsquo;re fine for simple tasks. For complex tasks, Claude Opus. Before spending $4000 on a Mac Studio justifying it with local models, actually test it first. You&rsquo;ll probably end up paying for Opus again.</p>
<h2>Why Omarchy<span class="hx:absolute hx:-mt-20" id="why-omarchy"></span>
    <a href="#why-omarchy" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;ve been using Omarchy on the desktop for months and documented the path in a <a href="/en/tags/omarchy/">series of articles</a>. <a href="/en/2025/08/29/new-omarchy-2-0-install/">Omarchy 2.0</a> has its own installer with LUKS, Btrfs, Limine, snapper and SDDM already configured. I wrote about <a href="/en/2025/09/12/omarchy-2-0-install-with-the-omarchy-iso/">using the official ISO</a>, about <a href="/en/2025/09/07/omarchy-2-0-zsh-configs/">ZSH customizations</a> with atuin, starship, secrets properly organized, about <a href="/en/2025/09/07/omarchy-2-0-mise-to-organize-dev-environments/">Mise for multiple languages</a>, about <a href="/en/2025/09/07/omarchy-2-0-lazyvim-lazyextras/">LazyVim and LazyExtras</a>, about <a href="/en/2025/09/09/omarchy-2-0-understanding-ssh-and-yubikeys/">SSH and Yubikeys</a>, about <a href="/en/2025/09/09/omarchy-2-0-tuis/">modern TUIs</a>. There&rsquo;s also <a href="/en/2026/01/21/omarchy-3-dual-gpu-setup-with-amd-and-nvidia/">Omarchy 3 with dual GPU AMD + NVIDIA</a> and <a href="/en/2026/01/09/omarchy-3-one-of-the-best-coding-agents-crush/">Crush</a> as a coding agent.</p>
<p>For those who don&rsquo;t know: Omarchy is plain Arch Linux with a cosmetic layer on top of Hyprland/Wayland. Pre-configured with sane defaults. I could build all of it from scratch, I&rsquo;ve done it several times in my life, but why redo work someone already did well? I install Omarchy, I stack the tweaks that are mine on top, and I have customized Arch in a fraction of the time.</p>
<p>A point I raise every time I recommend Omarchy: the documentation is excellent. There&rsquo;s an <a href="https://manuals.omamix.org/2/the-omarchy-manual"target="_blank" rel="noopener">official manual</a> covering everything from installation to theme customization, keybindings, Hyprland, Waybar, the works. If you&rsquo;re coming from Ubuntu or Fedora and are wary of Arch, this manual handles most of the doubts. If you&rsquo;ve never touched Hyprland, open it and read around. It&rsquo;s not a README dumped on GitHub, it&rsquo;s a real manual with chapters and an index.</p>
<p>The story of this article starts in a previous thread. I recently migrated my home server, swapping an old Ubuntu box for a <a href="/en/2026/03/31/migrating-my-home-server-with-claude-code/">Minisforum MS-S1 running openSUSE MicroOS configured with Claude Code</a>. That was the first serious experiment of letting Claude Code drive an entire infra migration, with containers, NFS, services, networking. It worked. And it left an idea in the air: why not use the same approach to configure a new notebook?</p>
<p>That&rsquo;s what I did. I grabbed the Thinkpad, downloaded the latest Omarchy ISO, burned it to a flash drive, installed it over Windows. From zero to working desktop in under an hour. From there on, it&rsquo;s all tweaking, which I documented as I went.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/open-lid-claude-terminal.jpg" alt="Claude Code running on the Thinkpad terminal, ready to start configuring the notebook"  loading="lazy" /></p>
<p>I&rsquo;ll detail the two layers of customization: what any notebook needs, and what&rsquo;s specific to this Thinkpad.</p>
<h2>Laptop-specific configs<span class="hx:absolute hx:-mt-20" id="laptop-specific-configs"></span>
    <a href="#laptop-specific-configs" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A notebook has problems a desktop doesn&rsquo;t: battery, suspend, lid, brightness, trackpad. Those are the parts Omarchy default doesn&rsquo;t cover the way I want.</p>
<h3>Power management with TLP<span class="hx:absolute hx:-mt-20" id="power-management-with-tlp"></span>
    <a href="#power-management-with-tlp" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Omarchy defaults to <code>power-profiles-daemon</code>. I swapped it for TLP, which gives granular control over CPU scaling, battery thresholds and dynamic profiles based on AC vs battery.</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 tlp tlp-rdw
</span></span><span class="line"><span class="cl">sudo systemctl mask power-profiles-daemon.service
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now tlp.service</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>mask</code> is necessary because upower pulls <code>power-profiles-daemon</code> back in if you just <code>disable</code> it.</p>
<p>Charge thresholds: 60% to start, 85% to stop. The notebook spends most of its time plugged in on the office desk, and keeping the battery at 100% 24/7 wrecks capacity over time. With 60/85, the battery spends most of its time in the healthy lithium range and still has decent usable capacity.</p>
<p>Profiles: <code>balanced</code> with <code>balance_power</code> on battery, <code>performance</code> on AC. TLP&rsquo;s <code>low-power</code> was too aggressive on the Core Ultra 5 235U, window and terminal response became noticeable. Balanced gives the best consumption/responsiveness ratio for normal use.</p>
<p>There&rsquo;s a gotcha: <code>tlp auto</code> after <code>tlp ac</code> doesn&rsquo;t always re-apply the battery profile. I wrote a small script bound to <code>Super+Ctrl+P</code> that reads the current state and calls <code>tlp bat</code> or <code>tlp ac</code> explicitly:</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">#!/bin/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></span><span class="line"><span class="cl"><span class="nv">profile</span><span class="o">=</span><span class="k">$(</span>cat /sys/firmware/acpi/platform_profile 2&gt;/dev/null <span class="o">||</span> <span class="nb">echo</span> unknown<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">on_ac</span><span class="o">=</span><span class="k">$(</span>cat /sys/class/power_supply/AC*/online 2&gt;/dev/null <span class="p">|</span> head -1 <span class="o">||</span> <span class="nb">echo</span> 0<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$profile</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;performance&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$on_ac</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;1&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    sudo /usr/bin/tlp auto &gt;/dev/null
</span></span><span class="line"><span class="cl">    <span class="nv">label</span><span class="o">=</span><span class="s2">&#34;Plugged in — back to AC auto&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="cl">    sudo /usr/bin/tlp bat &gt;/dev/null
</span></span><span class="line"><span class="cl">    <span class="nv">label</span><span class="o">=</span><span class="s2">&#34;On battery — normal profile&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl">  notify-send -t <span class="m">2000</span> <span class="s2">&#34;Performance mode: off&#34;</span> <span class="s2">&#34;</span><span class="nv">$label</span><span class="s2">&#34;</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">true</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  sudo /usr/bin/tlp ac &gt;/dev/null
</span></span><span class="line"><span class="cl">  notify-send -t <span class="m">2000</span> <span class="s2">&#34;Performance mode: on&#34;</span> <span class="s2">&#34;Forcing AC profile&#34;</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">true</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</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>In waybar, a custom module <code>custom/perf</code> shows the current state (󰓅 PERF, 󰌪 ECO, or empty for balanced) and accepts a click to toggle. The output script is tiny:</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">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="nv">p</span><span class="o">=</span><span class="k">$(</span>cat /sys/firmware/acpi/platform_profile 2&gt;/dev/null<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="s2">&#34;</span><span class="nv">$p</span><span class="s2">&#34;</span> in
</span></span><span class="line"><span class="cl">  performance<span class="o">)</span> <span class="nb">printf</span> <span class="s1">&#39;󰓅 PERF&#39;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="cl">  low-power<span class="o">)</span>   <span class="nb">printf</span> <span class="s1">&#39;󰌪 ECO&#39;</span>  <span class="p">;;</span>
</span></span><span class="line"><span class="cl">  balanced<span class="p">|</span><span class="s2">&#34;&#34;</span><span class="o">)</span> <span class="nb">printf</span> <span class="s1">&#39;&#39;</span>       <span class="p">;;</span>
</span></span><span class="line"><span class="cl">  *<span class="o">)</span>           <span class="nb">printf</span> <span class="s1">&#39;%s&#39;</span> <span class="s2">&#34;</span><span class="nv">$p</span><span class="s2">&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="cl"><span class="k">esac</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>
<h3>Suspend, hibernate, and the lid<span class="hx:absolute hx:-mt-20" id="suspend-hibernate-and-the-lid"></span>
    <a href="#suspend-hibernate-and-the-lid" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>This is what changed compared to the notebooks I used in 2015. The T14 Gen 6 closes the lid, suspends, and wakes up when you open it. No bug, no weird delay, no needing to log in again mid graphical session. Hyprlock kicks in after the suspend, accepts fingerprint or password, and brings me back to the desktop in seconds. This is the behavior Apple has had for years and that on Linux used to be an adventure. In 2026, on modern hardware with kernel 6.11+, it just works.</p>
<p>Mem sleep mode is s2idle (no deep sleep). Hibernation is enabled via a ~30 GB Btrfs swapfile and <code>resume=/dev/mapper/root resume_offset=...</code> on the Limine kernel cmdline. I rarely use it, but it&rsquo;s there.</p>
<p>Hypridle has aggressive timeouts for a laptop:</p>
<ul>
<li>2.5 min → screensaver</li>
<li>5 min → lock</li>
<li>5.5 min → DPMS off + keyboard backlight off</li>
</ul>
<p>After lock, another 5 min and the screen turns off completely. On unlock, it restores screen and keyboard brightness to the previous level. These timings are much shorter than on the desktop (20-40 min there). The battery difference over a day is measurable.</p>
<h3>Brightness and keyboard backlight<span class="hx:absolute hx:-mt-20" id="brightness-and-keyboard-backlight"></span>
    <a href="#brightness-and-keyboard-backlight" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><code>brightnessctl</code> controls both. Fn+Space toggles the keyboard backlight across three levels. Hypridle saves the current level before turning off and restores it on resume. Command I use in the 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-bash" data-lang="bash"><span class="line"><span class="cl">brightnessctl -sd <span class="s1">&#39;*::kbd_backlight&#39;</span> <span class="nb">set</span> <span class="m">0</span>    <span class="c1"># save and turn off</span>
</span></span><span class="line"><span class="cl">brightnessctl -rd <span class="s1">&#39;*::kbd_backlight&#39;</span>          <span class="c1"># restore</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>
<h3>Touchpad<span class="hx:absolute hx:-mt-20" id="touchpad"></span>
    <a href="#touchpad" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>In <code>hypr/input.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>touchpad {
    natural_scroll = true
    clickfinger_behavior = true
    disable_while_typing = true
    scroll_factor = 0.4
}
gesture = 3, horizontal, workspace</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><code>clickfinger_behavior</code> swaps the 2-finger click as right-click (more comfortable than hitting the lower-right zone). <code>disable_while_typing</code> is basic palm rejection. Three-finger horizontal swipes switch workspaces, the most useful gesture in Hyprland.</p>
<h2>Thinkpad-specific configs<span class="hx:absolute hx:-mt-20" id="thinkpad-specific-configs"></span>
    <a href="#thinkpad-specific-configs" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>This is what&rsquo;s specific to this model. Some things modern Linux handles on its own, others need explicit configuration.</p>
<h3>Fingerprint sensor<span class="hx:absolute hx:-mt-20" id="fingerprint-sensor"></span>
    <a href="#fingerprint-sensor" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Goodix MOC <code>27c6:6594</code>, works with libfprint 1.94.9+ on kernel 6.11+. Package is <code>fprintd</code>.</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 fprintd
</span></span><span class="line"><span class="cl">fprintd-enroll                        <span class="c1"># right index finger by default</span>
</span></span><span class="line"><span class="cl">fprintd-enroll -f left-index-finger   <span class="c1"># other finger</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>To make <code>sudo</code> accept fingerprint, I added <code>auth sufficient pam_fprintd.so</code> above the <code>pam_unix.so</code> line in <code>/etc/pam.d/sudo</code>. With <code>sufficient</code>, if the fingerprint passes, it authenticates directly. If it fails or I hit ESC, it falls back to the password prompt. This is genuinely worth it: dozens of times a day, <code>sudo pacman -Syu</code> or <code>sudo systemctl restart something</code>, and I just touch the sensor.</p>
<p>On hyprlock, I use hyprlock&rsquo;s native configuration, not PAM:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>auth {
    fingerprint {
        enabled = true
        ready_message = Scan fingerprint or type password
        present_message = Scanning...
        retry_delay = 250
    }
}</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 PAM path gives a double prompt. Configured natively, hyprlock accepts fingerprint or password, whichever succeeds first unlocks.</p>
<p>On SDDM (initial login), I kept password only. The reason: the login password unlocks the GNOME keyring, and the fingerprint can&rsquo;t provide plaintext for that. Once the keyring is unlocked, hyprlock can use fingerprint without issues.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/sudo-asks-for-fingerprinting.png" alt="sudo prompt asking for fingerprint read"  loading="lazy" /></p>
<p>In practice, sudo looks like that. Touch the sensor, authenticate, move on.</p>
<h3>Brazilian Thinkpad keyboard<span class="hx:absolute hx:-mt-20" id="brazilian-thinkpad-keyboard"></span>
    <a href="#brazilian-thinkpad-keyboard" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The Brazilian Thinkpad keyboard has an annoying quirk. The <code>/?</code> key sits where the right Ctrl would be (keycode 97), not in the traditional ABNT2 AB11 position (keycode 89). If you use the standard <code>br(abnt2)</code> layout, that key is inaccessible. It literally prints nothing.</p>
<p>The solution is the <code>br(thinkpad)</code> variant that exists in <code>/usr/share/X11/xkb/symbols/br</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>xkb_symbols &#34;thinkpad&#34; {
    include &#34;br(abnt2)&#34;
    name[Group1]=&#34;Portuguese (Brazil, IBM/Lenovo ThinkPad)&#34;;
    key &lt;RCTL&gt; { [ slash, question, degree, questiondown ] };
};</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>In <code>hypr/input.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>kb_layout = br
kb_variant = thinkpad
kb_model = thinkpad60</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 system-wide for TTY/X11:</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 localectl set-keymap br-abnt2
</span></span><span class="line"><span class="cl">sudo localectl set-x11-keymap br thinkpad thinkpad60</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>I wrote a small Python that reads raw scancodes from <code>/dev/input/event*</code> to diagnose these quirks. Useful when a key decides not to work and you need to find out if it&rsquo;s hardware, kernel, or xkb.</p>
<h3>Input method: fcitx5<span class="hx:absolute hx:-mt-20" id="input-method-fcitx5"></span>
    <a href="#input-method-fcitx5" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Alongside the layout I install fcitx5. Input method, for those who don&rsquo;t know, is the layer that turns a key sequence into characters. It handles deadkeys (tilde for nasalization, acute accent, circumflex), composing characters that aren&rsquo;t on the keyboard (ç, Ç, uppercase accented letters), emoji support. In Qt or GTK apps, the input method also drives context menus for cedilla and accents.</p>
<p>Packages:</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 --needed fcitx5 fcitx5-configtool fcitx5-gtk fcitx5-qt</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 environment variables for toolkits to find fcitx5. I created <code>~/.config/environment.d/fcitx.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>INPUT_METHOD=fcitx
QT_IM_MODULE=fcitx
XMODIFIERS=@im=fcitx
SDL_IM_MODULE=fcitx</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>systemd&rsquo;s <code>environment.d</code> is loaded before graphical sessions, so Brave, Alacritty, VS Code and any GTK/Qt app pick it up automatically. I enable autostart in Hyprland:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>exec-once = fcitx5 -d</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>Typing <code>~a</code> produces <code>ã</code>, <code>'e</code> produces <code>é</code>, <code>ç</code> works as it should in every app. On a Brazilian Thinkpad keyboard, this is the difference between typing Portuguese naturally or hunting for each character.</p>
<h3>SOF audio<span class="hx:absolute hx:-mt-20" id="sof-audio"></span>
    <a href="#sof-audio" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Realtek ALC3306/ALC287 codec via Sound Open Firmware. Without <code>sof-firmware</code>, the kernel module loads but the DSP never boots and PipeWire silently falls back to <code>auto_null</code>. Result: you think the speaker is on mute, but actually PipeWire has no device at all.</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 --needed sof-firmware alsa-ucm-conf pipewire pipewire-pulse wireplumber</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 you need to force SOF instead of legacy HDA:</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">echo</span> <span class="s2">&#34;options snd-intel-dspcfg dsp_driver=3&#34;</span> <span class="p">|</span> sudo tee /etc/modprobe.d/alsa.conf</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>Reload without reboot:</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 modprobe -r snd_sof_pci_intel_mtl
</span></span><span class="line"><span class="cl">sudo modprobe snd_sof_pci_intel_mtl
</span></span><span class="line"><span class="cl">systemctl --user restart wireplumber pipewire pipewire-pulse</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>
<h3>Firmware updates via fwupd<span class="hx:absolute hx:-mt-20" id="firmware-updates-via-fwupd"></span>
    <a href="#firmware-updates-via-fwupd" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/bios-update.jpg" alt="Lenovo firmware update (ME Corp) running on boot"  loading="lazy" /></p>
<p><code>fwupdmgr update</code> works, but with Limine there&rsquo;s a gotcha: fwupd tries to write to <code>/boot/EFI/systemd/</code> or <code>/boot/EFI/arch/</code>, which don&rsquo;t exist. The workaround:</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 mkdir -p /boot/EFI/arch
</span></span><span class="line"><span class="cl">sudo fwupdmgr update -y --no-reboot-check
</span></span><span class="line"><span class="cl">fwupdmgr get-history  <span class="c1"># should show &#34;Success&#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>
<h3>HiDPI / fractional scaling<span class="hx:absolute hx:-mt-20" id="hidpi--fractional-scaling"></span>
    <a href="#hidpi--fractional-scaling" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>14&quot; panel at 1920x1200, Hyprland&rsquo;s free resolution. Omarchy&rsquo;s auto 1.5x felt too chunky. I pinned it at 1.333x:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>env = GDK_SCALE,1
monitor=,preferred,auto,1.3333,vrr,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>Effective 1440x900. GTK with <code>GDK_SCALE=1</code> renders 1:1 with Hyprland (no double magnification). VRR mode 2 only in fullscreen, because LCD panels tend to flicker on static content with VRR active.</p>
<p>Brave and Chromium flags to render well at this scale:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>--ozone-platform=wayland
--enable-features=WaylandFractionalScaleV1,UseOzonePlatform,VaapiVideoDecoder,VaapiVideoEncoder
--enable-gpu-rasterization</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>VAAPI makes a real difference on YouTube battery life.</p>
<h2>Tuning Omarchy to be mine<span class="hx:absolute hx:-mt-20" id="tuning-omarchy-to-be-mine"></span>
    <a href="#tuning-omarchy-to-be-mine" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Omarchy comes with good defaults. What I adjust stacks on top of them, without touching <code>~/.local/share/omarchy/</code> (which is clobbered by <code>omarchy-update</code>). All customization lives in <code>~/.config/</code>.</p>
<h3>Infra: Btrfs, snapshots, Snapper<span class="hx:absolute hx:-mt-20" id="infra-btrfs-snapshots-snapper"></span>
    <a href="#infra-btrfs-snapshots-snapper" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Omarchy already ships with Btrfs and separate subvolumes:</p>
<table>
  <thead>
      <tr>
          <th>Subvolume</th>
          <th>Mount</th>
          <th>In snapshots?</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>@</code></td>
          <td><code>/</code></td>
          <td>yes</td>
      </tr>
      <tr>
          <td><code>@home</code></td>
          <td><code>/home</code></td>
          <td>no</td>
      </tr>
      <tr>
          <td><code>@log</code></td>
          <td><code>/var/log</code></td>
          <td>no</td>
      </tr>
      <tr>
          <td><code>@pkg</code></td>
          <td><code>/var/cache/pacman/pkg</code></td>
          <td>no</td>
      </tr>
  </tbody>
</table>
<p><code>@home</code> separated means <code>~/.cache</code>, <code>~/.config/BraveSoftware</code>, etc. don&rsquo;t bloat root snapshots. Snapshot is for system, not profile.</p>
<p>Swap: 4 GB zram at priority 100 (hit first), plus a 30 GB swapfile at priority 0 (enables hibernation, sized to match RAM).</p>
<p>The snapshot stack: Snapper takes a snapshot before and after every <code>pacman -Syu</code> via snap-pac. <code>limine-snapper-sync</code> writes those snapshots into the Limine menu, so you can boot into a previous snapshot to roll back. If something breaks after an update, you hold a key on boot, pick the pre-update snapshot, boot read-only to verify, and if it&rsquo;s good, run <code>snapper rollback</code>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/btrfs-snapshots-config.png" alt="Btrfs subvolumes separated so root snapshots don’t balloon with user cache"  loading="lazy" /></p>
<p>Omarchy leaves <code>snapper-cleanup.timer</code> and <code>snapper-boot.timer</code> disabled by default. I enabled both and configured retention to fit on a 1 TB SSD without blowing up:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>TIMELINE_LIMIT_HOURLY=10
TIMELINE_LIMIT_DAILY=0
TIMELINE_LIMIT_WEEKLY=1
TIMELINE_LIMIT_MONTHLY=1
NUMBER_LIMIT=50</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>A detail that costs debug time if you forget: Docker and Ollama write gigabytes into <code>/var/lib/docker</code> and <code>/var/lib/ollama</code>. If that falls inside <code>@</code>, snapshots go catastrophic. Each Docker image or Ollama model downloaded triples in size. I created nested subvolumes for both, with <code>chattr +C</code> to disable CoW:</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 btrfs subvolume create /var/lib/docker
</span></span><span class="line"><span class="cl">sudo chattr +C /var/lib/docker
</span></span><span class="line"><span class="cl">sudo btrfs subvolume create /var/lib/ollama</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 to be done BEFORE Docker or Ollama writes any data. If they already have data there, you need to migrate.</p>
<h3>NFS to the Synology NAS<span class="hx:absolute hx:-mt-20" id="nfs-to-the-synology-nas"></span>
    <a href="#nfs-to-the-synology-nas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>My Synology exposes three volumes. On the desktop, I mount them the usual way. On the notebook, it has to be more defensive. The notebook moves around, forgets networks, connects to public WiFi. Notebook&rsquo;s fstab:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>nfs4  _netdev,noauto,nofail,x-systemd.automount,x-systemd.idle-timeout=10min,x-systemd.mount-timeout=15s,noatime,nodiratime,nconnect=4,actimeo=10,soft,timeo=30,retrans=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>Critical difference vs the desktop: <code>soft</code> with short timeouts (doesn&rsquo;t hang forever), <code>x-systemd.idle-timeout=10min</code> (auto-unmount when idle), no <code>network-online.target</code> in the require (doesn&rsquo;t slow boot). Practical result: <code>cd /mnt/gigachad</code> at home mounts lazily, away from home it fails fast without locking the shell.</p>
<p>Another important detail: my user on the notebook has UID 1026, which matches the share permissions on the Synology. Linux defaults to creating users at 1000, the Synology enforces identity via UID on the wire. If the UIDs don&rsquo;t match, you can&rsquo;t read the files, or worse, you write as nobody. I ran the <code>usermod</code>/<code>groupmod</code> from a TTY (with the user logged out) to remap the user to 1026/1026 and did <code>chown -R</code> on <code>/home</code>.</p>
<h3>Public WiFi hardening<span class="hx:absolute hx:-mt-20" id="public-wifi-hardening"></span>
    <a href="#public-wifi-hardening" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The notebook will leave the house. In an airport or café, I don&rsquo;t want to announce hostname, don&rsquo;t want a trackable MAC, don&rsquo;t want a service listening on an open port.</p>
<p><code>/etc/NetworkManager/conf.d/00-macrandomize.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[device]
wifi.scan-rand-mac-address=yes

[connection]
wifi.cloned-mac-address=stable
ethernet.cloned-mac-address=stable
connection.stable-id=${CONNECTION}/${BOOT}

ipv6.ip6-privacy=2
ipv6.addr-gen-mode=stable-privacy</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>Random MAC per scan (passive anti-fingerprinting). Stable cloned MAC per SSID (captive portals don&rsquo;t re-prompt you every time) but different MACs between networks. IPv6 with temporary addresses and interface ID derived from a stable secret, not from the MAC (no EUI-64 leak).</p>
<p><code>/etc/systemd/resolved.conf.d/hardening.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Resolve]
LLMNR=no
MulticastDNS=no</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>Kills hostname broadcast on LLMNR and mDNS. I also disable avahi:</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 systemctl disable --now avahi-daemon.service avahi-daemon.socket</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>UFW firewall:</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 ufw default deny incoming
</span></span><span class="line"><span class="cl">sudo ufw default allow outgoing
</span></span><span class="line"><span class="cl">sudo ufw --force enable</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>SSH server is already off by default on Omarchy. Nothing listening. Even if the firewall leaks, there&rsquo;s no surface.</p>
<p>All of it rolls into a single initial setup ritual.</p>
<h3>SSH agent persistence (keyring + keychain)<span class="hx:absolute hx:-mt-20" id="ssh-agent-persistence-keyring--keychain"></span>
    <a href="#ssh-agent-persistence-keyring--keychain" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>If you use SSH seriously, you want to type the passphrase once per boot and have <code>ssh</code>, <code>git push</code>, <code>scp</code> stay silent for the rest of the day. On current Arch there&rsquo;s a catch: <code>gnome-keyring</code> 50 dropped its SSH component, and the replacement (<code>gcr-ssh-agent</code>) is a plain in-memory agent with no passphrase persistence. The &ldquo;remember this key&rdquo; checkbox you saw in old guides simply doesn&rsquo;t exist anymore.</p>
<p>The combination that works has three pieces:</p>
<ol>
<li><code>gcr-ssh-agent.socket</code> managed by systemd serves <code>SSH_AUTH_SOCK</code> at <code>$XDG_RUNTIME_DIR/gcr/ssh</code></li>
<li><code>pam_gnome_keyring</code> on SDDM login unlocks the keyring with the login password (used by GUI apps like Brave, not by SSH)</li>
<li><code>keychain</code> (wrapper) keeps an <code>ssh-agent</code> alive across logouts, overrides <code>SSH_AUTH_SOCK</code> to point at that persistent agent, and caches the PID in <code>~/.keychain/&lt;host&gt;-sh</code></li>
</ol>
<p>First, install the keyring:</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 --needed gnome-keyring seahorse keychain</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 edit <code>/etc/pam.d/sddm</code> so the login password unlocks the keyring automatically. Add at the top:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>-auth      optional    pam_gnome_keyring.so</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 at the end:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>-session   optional    pam_gnome_keyring.so    auto_start</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 leading hyphen (<code>-auth</code>, <code>-session</code>) marks it optional, so if the module fails to load, login doesn&rsquo;t break.</p>
<p>Pin <code>SSH_AUTH_SOCK</code> for graphical and TTY sessions via <code>~/.config/environment.d/ssh-agent.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>SSH_AUTH_SOCK=%t/gcr/ssh</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><code>%t</code> resolves to <code>$XDG_RUNTIME_DIR</code>, something like <code>/run/user/1026</code>. Enable the gcr socket and user linger (so user daemons survive logout):</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">systemctl --user <span class="nb">enable</span> --now gcr-ssh-agent.socket
</span></span><span class="line"><span class="cl">sudo loginctl enable-linger <span class="nv">$USER</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>Finally, in <code>~/.config/bash/init.sh</code>, keychain starts or reuses the agent:</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="k">if</span> <span class="nb">command</span> -v keychain <span class="p">&amp;</span>&gt;/dev/null <span class="o">&amp;&amp;</span> <span class="o">[[</span> -r ~/.ssh/id_ed25519 <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span>keychain --eval --quiet ~/.ssh/id_ed25519<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</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>--eval</code> emits shell assignments (<code>SSH_AUTH_SOCK</code>, <code>SSH_AGENT_PID</code>) pointing to keychain&rsquo;s agent, overriding the gcr path set by environment.d. <code>--quiet</code> silences the banner once the key is loaded.</p>
<p>Flow in practice: boot → first terminal → keychain prompts for the passphrase once → key stays loaded. Logout → login again → new shells reattach to the same agent (via <code>~/.keychain/&lt;host&gt;-sh</code>) → zero prompts. <code>ssh-add -l</code> confirms the key is there, <code>echo $SSH_AUTH_SOCK</code> confirms it&rsquo;s at keychain&rsquo;s path.</p>
<p>When you&rsquo;ll re-enter the passphrase: reboot, <code>ssh-add -D</code>, or <code>keychain --clear</code>. In normal use, once a day.</p>
<h3>Bash instead of ZSH<span class="hx:absolute hx:-mt-20" id="bash-instead-of-zsh"></span>
    <a href="#bash-instead-of-zsh" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>On the desktop I use ZSH. On the notebook I went with Bash to align with Omarchy&rsquo;s default, without having to maintain a parallel stack of modular layers. <code>~/.bashrc</code> is a symlink to <code>~/.config/bash/bashrc</code>, which sources Omarchy&rsquo;s defaults first and then stacks my customizations:</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">source</span> ~/.local/share/omarchy/default/bash/rc
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.config/bash/envs.sh
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.config/bash/aliases.sh
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.config/bash/mounts.sh
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.config/bash/init.sh
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.config/bash/secrets    <span class="c1"># gitignored, chmod 600</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>envs.sh</code> has what&rsquo;s mine: OpenRouter base URL, Ollama pointing to the LAN GPU box (192.168.0.14), AWS region, Hugo analytics, zoxide and SSH agent configs. <code>aliases.sh</code> has TLP shortcuts, an alias for <code>shell-gpt</code> via Docker, and functions to harden PATH when running <code>makepkg</code> or <code>yay</code> (prevents binary injection via a malicious user config).</p>
<p><code>init.sh</code> does the integration work. Atuin with a manual bind for Ctrl-R (so the up arrow keeps bash&rsquo;s default history-search, which I use more). Keychain loading <code>~/.ssh/id_ed25519</code> once per boot and reusing it across shells (no need to re-authenticate SSH every time). Blesh if installed (ZSH-style autosuggestions for Bash). And a function that has PROMPT_COMMAND set the window title to the current pwd and the running 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">__title_idle<span class="o">()</span> <span class="o">{</span> <span class="nb">printf</span> <span class="s1">&#39;\033]2;%s\007&#39;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PWD</span><span class="p">/#</span><span class="nv">$HOME</span><span class="p">/~</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">__title_busy<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">local</span> <span class="nv">cmd</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASH_COMMAND</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$cmd</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;__title_&#34;</span>* <span class="o">||</span> <span class="s2">&#34;</span><span class="nv">$cmd</span><span class="s2">&#34;</span> <span class="o">==</span> *<span class="s2">&#34;PROMPT_COMMAND&#34;</span>* <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="k">return</span>
</span></span><span class="line"><span class="cl">  <span class="nb">printf</span> <span class="s1">&#39;\033]2;%s — %s\007&#39;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PWD</span><span class="p">/#</span><span class="nv">$HOME</span><span class="p">/~</span><span class="si">}</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$cmd</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROMPT_COMMAND</span><span class="p">-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nv">PROMPT_COMMAND</span><span class="o">=</span><span class="s2">&#34;__title_idle; </span><span class="si">${</span><span class="nv">PROMPT_COMMAND</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nv">PROMPT_COMMAND</span><span class="o">=</span><span class="s2">&#34;__title_idle&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="nb">trap</span> <span class="s1">&#39;__title_busy&#39;</span> DEBUG</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>Idle, the title shows just the pwd. With a command running, <code>trap DEBUG</code> catches the <code>BASH_COMMAND</code> and updates the title. Waybar&rsquo;s <code>hyprland/window</code> picks that up and displays it.</p>
<p>For the modern Rust toolbelt, the list:</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 --needed <span class="se">\
</span></span></span><span class="line"><span class="cl">  eza bat fd ripgrep sd git-delta dust procs bottom duf tokei hyperfine <span class="se">\
</span></span></span><span class="line"><span class="cl">  zoxide atuin tldr starship</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>eza</code> replaces <code>ls</code>. <code>bat</code> replaces <code>cat</code> with syntax highlighting. <code>fd</code> replaces <code>find</code>. <code>ripgrep</code> replaces <code>grep</code>. <code>sd</code> replaces <code>sed</code>. <code>delta</code> plugs into git for colored side-by-side diff. <code>dust</code> for visual <code>du</code>. <code>procs</code> for <code>ps</code>. <code>btm</code> (bottom) for <code>top</code>. <code>duf</code> for <code>df</code>. <code>tokei</code> counts lines of code. <code>hyperfine</code> for command benchmarking. <code>zoxide</code> is <code>cd</code> with memory (very useful). <code>atuin</code> is shell history with encrypted sync (I point it to my home server via <code>sync_address = &quot;http://192.168.0.90:8888&quot;</code>). <code>starship</code> is the prompt. <code>tldr</code> is a man page in 10 lines.</p>
<p>The <code>atuin key</code> has to be backed up offline. If you lose it, you lose the encrypted history. I saved mine in my self-hosted Bitwarden (Vaultwarden), documented in the <a href="/en/2025/09/10/omarchy-2-0-bitwarden-self-hosted-vaultwarden/">Bitwarden self-hosted article</a>.</p>
<p>Git pipes diff through delta:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[core]        pager = delta
[interactive] diffFilter = delta --color-only
[delta]       navigate side-by-side line-numbers hyperlinks, light=false
[merge]       conflictstyle = zdiff3</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>
<h3>Hyprland and Waybar<span class="hx:absolute hx:-mt-20" id="hyprland-and-waybar"></span>
    <a href="#hyprland-and-waybar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Visual and UX customizations on top of the default.</p>
<p>In <code>hypr/looknfeel.conf</code>, smaller gaps (2/5 vs the default 5/10), slide animation between workspaces, VFR on (reduces consumption when the screen is static), <code>allow_session_lock_restore</code> (if hyprlock crashes, it goes back to the lock screen instead of dumping you on the desktop).</p>
<p>In <code>hypr/bindings.conf</code>:</p>
<table>
  <thead>
      <tr>
          <th>Binding</th>
          <th>Action</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Super+B</code></td>
          <td>Brave</td>
      </tr>
      <tr>
          <td><code>Super+L</code></td>
          <td>Lock screen (default was layout toggle)</td>
      </tr>
      <tr>
          <td><code>Super+Ctrl+L</code></td>
          <td>Layout toggle (moved here)</td>
      </tr>
      <tr>
          <td><code>Super+Ctrl+P</code></td>
          <td>Toggle TLP perf mode</td>
      </tr>
  </tbody>
</table>
<p>On Waybar, three custom modules I recommend:</p>
<p><strong>Window title</strong>: the <code>hyprland/window</code> module shows what&rsquo;s focused. In bash, PROMPT_COMMAND updates the title to something like &ldquo;~/Projects/blog — hugo server&rdquo;. So in waybar the directory and running command show up, without me needing to look at the terminal.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/hypr---window-title-on-waybar.png" alt="Waybar showing workspaces on the left and the focused window title with the pwd and the running command"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/hypr---another-window-title-on-waybar.png" alt="Another example of the window title on waybar, this time showing “Claude Code”"  loading="lazy" /></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-jsonc" data-lang="jsonc"><span class="line"><span class="cl"><span class="s2">&#34;hyprland/window&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;{title}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;max-length&#34;</span><span class="p">:</span> <span class="mi">50</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;rewrite&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;(.{47}).+&#34;</span><span class="p">:</span> <span class="s2">&#34;$1…&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><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><strong>Perf mode</strong>: <code>custom/perf</code> runs a script that reads TLP state and shows an icon:</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-jsonc" data-lang="jsonc"><span class="line"><span class="cl"><span class="s2">&#34;custom/perf&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;exec&#34;</span><span class="p">:</span> <span class="s2">&#34;~/.config/scripts/perf-waybar.sh&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;interval&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format&#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="nt">&#34;on-click&#34;</span><span class="p">:</span> <span class="s2">&#34;~/.config/scripts/perf-toggle.sh&#34;</span>
</span></span><span class="line"><span class="cl"><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>Empty for balanced, 󰓅 PERF for performance, 󰌪 ECO for low-power. Click toggles. Useful to quickly see what mode the machine is in.</p>
<p><strong>Claudebar</strong>: I integrated <a href="https://github.com/alfredopiquet/claudebar"target="_blank" rel="noopener">claudebar</a> as a custom module that shows the Claude Code session usage % and extra spend inline in waybar. No need to open another window.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/hypr---power-profile-and-claudebar-next-to-traybar.png" alt="Claudebar showing 9%, 1h16m remaining and $1120.74 extra spend, followed by the PERF indicator and the tray"  loading="lazy" /></p>
<p>The clock:</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-jsonc" data-lang="jsonc"><span class="line"><span class="cl"><span class="s2">&#34;clock&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;{:L%A %d %B - %H:%M}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format-alt&#34;</span><span class="p">:</span> <span class="s2">&#34;{:L%A W%V %Y - %H:%M}&#34;</span>
</span></span><span class="line"><span class="cl"><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>Shows &ldquo;Thursday 17 April - 14:32&rdquo; by default, click toggles to &ldquo;Thursday W16 2026 - 14:32&rdquo; with the ISO week number, useful for planning articles and tasks.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/hypr---better-date-time-on-waybar-center.png" alt="Waybar clock in the center with date and time"  loading="lazy" /></p>
<h3>Self-hosted Atuin<span class="hx:absolute hx:-mt-20" id="self-hosted-atuin"></span>
    <a href="#self-hosted-atuin" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Atuin runs on my home server at 192.168.0.90:8888. I made a separate account for the notebook (<code>akitaonrails-thinkpad</code>), I don&rsquo;t mix history with the desktop on purpose. <code>atuin sync</code> runs every 5 minutes and all history is encrypted before going to the server. The server doesn&rsquo;t see the commands, only encrypted bytes.</p>
<h2>Conclusion: picking a notebook for Linux<span class="hx:absolute hx:-mt-20" id="conclusion-picking-a-notebook-for-linux"></span>
    <a href="#conclusion-picking-a-notebook-for-linux" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>One thing I learned on this journey: picking a notebook to run Linux comfortably is not trivial. You have to check the <a href="https://wiki.archlinux.org/title/Laptop"target="_blank" rel="noopener">ArchWiki</a> for your specific model before buying. The <a href="https://wiki.archlinux.org/title/Lenovo_ThinkPad_T14s_%28AMD%29_Gen_6"target="_blank" rel="noopener">T14s Gen 6 AMD page</a>, for example, catalogs every hardware quirk and how to work around it. Without that reference, you discover the problems during install.</p>
<p>Rule I follow: never buy a freshly-released model. Let 6 to 12 months go by after launch. That gives the community time to iron out bugs, drivers to land mainline, the ArchWiki to have a decent page, libfprint to include the fingerprint sensor, and the kernel to cover WiFi and the IR camera. Buying on day one means signing up as a beta tester.</p>
<p>Brands with a decent Linux track record: Lenovo (especially Thinkpad, they ship Ubuntu and Fedora pre-loaded), Dell (the XPS and Latitude lines), Asus (Zenbook and ROG have reasonable support), Framework (built for Linux from the factory). Any recent Mac is cut off from Linux mainline or runs through Asahi with caveats.</p>
<p>The Thinkpad T14 Gen 6 isn&rsquo;t the prettiest notebook I could have bought. But it&rsquo;s rugged, it has the right ports, the fingerprint sensor works, and the plastic shell lets it fall, get scratched, travel in a backpack without making me nervous. To serve as a remote debug companion, it&rsquo;s what I needed. If that&rsquo;s your use case too, I recommend it. If you want OLED and metal, go Zenbook or T14s. Every premium comes with a compromise, no Linux notebook in 2026 is perfect.</p>
<p>Everything I described here is versioned in a private repo of mine. If I need to reinstall tomorrow, I run half a dozen steps in order and I&rsquo;m back at the same place. That&rsquo;s the whole point of keeping config in Git: notebook is commodity, config is mine.</p>
]]></content:encoded><category>omarchy</category><category>linux</category><category>hardware</category><category>reviews</category></item><item><title>Omarchy 3: Dual GPU Setup with AMD and NVIDIA</title><link>https://akitaonrails.github.io/en/2026/01/21/omarchy-3-dual-gpu-setup-with-amd-and-nvidia/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/01/21/omarchy-3-dual-gpu-setup-with-amd-and-nvidia/</guid><pubDate>Wed, 21 Jan 2026 15:29:35 -0300</pubDate><description>&lt;p&gt;Another post just so I don&amp;rsquo;t forget these details when configuring &lt;a href="https://omarchy.org/"target="_blank" rel="noopener"&gt;Omarchy&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hyprland doesn&amp;rsquo;t play nice with NVIDIA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can use it. But there are several little problems, particularly with &lt;a href="https://github.com/basecamp/omarchy/issues/1147"target="_blank" rel="noopener"&gt;Hyprlock&lt;/a&gt;, and particularly with DPMS where I leave my computer idle, it locks itself, and after a timeout turns off the monitors to save power, avoid burn-in, etc.&lt;/p&gt;
&lt;p&gt;When I try to wake it from sleep, Hyprlock crashed and I have to jump to another TTY to restart it. I even wrote a &lt;code&gt;~/.local/bin/hypr_unlock.sh&lt;/code&gt; script like this:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Another post just so I don&rsquo;t forget these details when configuring <a href="https://omarchy.org/"target="_blank" rel="noopener">Omarchy</a>:</p>
<blockquote>
  <p>Hyprland doesn&rsquo;t play nice with NVIDIA</p>

</blockquote>
<p>You can use it. But there are several little problems, particularly with <a href="https://github.com/basecamp/omarchy/issues/1147"target="_blank" rel="noopener">Hyprlock</a>, and particularly with DPMS where I leave my computer idle, it locks itself, and after a timeout turns off the monitors to save power, avoid burn-in, etc.</p>
<p>When I try to wake it from sleep, Hyprlock crashed and I have to jump to another TTY to restart it. I even wrote a <code>~/.local/bin/hypr_unlock.sh</code> script 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">hyprctl --instance <span class="m">0</span> <span class="s1">&#39;keyword misc:allow_session_lock_restore 1&#39;</span>
</span></span><span class="line"><span class="cl">hyprctl --instance <span class="m">0</span> <span class="s1">&#39;dispatch exec hyprlock&#39;</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>But then I unlock and see that my Brave crashed too. NVIDIA really doesn&rsquo;t like the monitor being turned off while it&rsquo;s active. People said this happened in Hyprlock 0.9.1, but I&rsquo;m already on 0.9.2 and it still crashes. I tried enabling services like nvidia-suspend/resume/hibernate, but nothing worked either.</p>
<p>At the end of the day I decided it&rsquo;s not worth fighting with this: I have an <strong>AMD 7850X3D</strong>, and this CPU has an iGPU (integrated GPU), an AMD Raphael, which for things like rendering a window manager is more than powerful enough.</p>
<p>First, detect the GPUs:</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">❯ ls /dev/dri/by-path
</span></span><span class="line"><span class="cl">Permissions Size User Date Modified Name
</span></span><span class="line"><span class="cl">lrwxrwxrwx     - root <span class="m">21</span> Jan 15:22   pci-0000:01:00.0-card -&gt; ../card1
</span></span><span class="line"><span class="cl">lrwxrwxrwx     - root <span class="m">21</span> Jan 15:22   pci-0000:01:00.0-render -&gt; ../renderD128
</span></span><span class="line"><span class="cl">lrwxrwxrwx     - root <span class="m">21</span> Jan 15:22   pci-0000:1a:00.0-card -&gt; ../card2
</span></span><span class="line"><span class="cl">lrwxrwxrwx     - root <span class="m">21</span> Jan 15:22   pci-0000:1a:00.0-render -&gt; ../renderD129</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 find out which is AMD and which is NVIDIA, just 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">❯ lspci <span class="p">|</span> grep -i vga
</span></span><span class="line"><span class="cl">01:00.0 VGA compatible controller: NVIDIA Corporation GB202 <span class="o">[</span>GeForce RTX 5090<span class="o">]</span> <span class="o">(</span>rev a1<span class="o">)</span>
</span></span><span class="line"><span class="cl">1a:00.0 VGA compatible controller: Advanced Micro Devices, Inc. <span class="o">[</span>AMD/ATI<span class="o">]</span> Raphael <span class="o">(</span>rev c9<span class="o">)</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>Done. From the PCI ID we know card1 is NVIDIA and card2 is AMD. Now I need to configure my <code>~/.config/hypr/hyprland.conf</code>:</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></span><span class="line"><span class="cl"><span class="c1"># NVIDIA environment variables</span>
</span></span><span class="line"><span class="cl"><span class="nv">env</span> <span class="o">=</span> NVD_BACKEND,direct
</span></span><span class="line"><span class="cl"><span class="c1">#env = LIBVA_DRIVER_NAME,nvidia</span>
</span></span><span class="line"><span class="cl"><span class="nv">env</span> <span class="o">=</span> LIBVA_DRIVER_NAME,radeonsi
</span></span><span class="line"><span class="cl"><span class="c1">#env = __GLX_VENDOR_LIBRARY_NAME,nvidia</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Comment these out when using AMD as primary</span>
</span></span><span class="line"><span class="cl"><span class="c1">#env = __NV_PRIME_RENDER_OFFLOAD,1</span>
</span></span><span class="line"><span class="cl"><span class="c1">#env = __VK_LAYER_NV_optimus,NVIDIA_only</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># card1=nvidia, card2=amdgpu (verify with: cat /sys/class/drm/card*/device/uevent)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># AMD first for iGPU primary:</span>
</span></span><span class="line"><span class="cl"><span class="nv">env</span> <span class="o">=</span> AQ_DRM_DEVICES,/dev/dri/card2:/dev/dri/card1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">env</span> <span class="o">=</span> ELECTRON_OZONE_PLATFORM_HINT,auto</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>I left the NVIDIA-specific settings commented out; none of them can be active. But the most important line is <code>AQ_DRM_DEVICES</code>, which lists the order for Hyprland to pick the GPU. Card2/AMD has to come first. Done, just reboot and move the HDMI cables to the iGPU output.</p>
<p>With that, I can now choose to offload only some apps to the NVIDIA using Prime. For that, I first created the script <code>~/.local/bin/nvidia-load.sh</code>:</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">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="nv">__NV_PRIME_RENDER_OFFLOAD</span><span class="o">=</span><span class="m">1</span> <span class="nv">__VK_LAYER_NV_optimus</span><span class="o">=</span>NVIDIA_only <span class="nv">__GLX_VENDOR_LIBRARY_NAME</span><span class="o">=</span>nvidia <span class="s2">&#34;</span><span class="nv">$@</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>Finally, I configured some bindings in <code>~/.config/hypr/bindings.conf</code>:</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></span><span class="line"><span class="cl"><span class="nv">bindd</span> <span class="o">=</span> SUPER SHIFT, B, Blender, exec, ~/.local/bin/nvidia-load.sh blender
</span></span><span class="line"><span class="cl"><span class="nv">bindd</span> <span class="o">=</span> SUPER SHIFT, L, Bambu Studio, exec, ~/.local/bin/nvidia-load.sh bambu-studio
</span></span><span class="line"><span class="cl"><span class="nv">bindd</span> <span class="o">=</span> SUPER SHIFT, P, Blender, exec, ~/.local/bin/nvidia-load.sh plasticity
</span></span><span class="line"><span class="cl">...</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 way, only Blender, Plasticity, and Bambu Studio, which primarily use the NVIDIA to render 3D in real time. All the other apps open on the AMD iGPU.</p>
<p>AI tools like ComfyUI, Ollama, or LM Studio automatically recognize the NVIDIA and pick it. And with that I also have more free VRAM to load LLMs.</p>
<p>If you&rsquo;re going to use XLibre/X.org, you can stay on NVIDIA just fine. But if you&rsquo;re going to use Wayland, NVIDIA still isn&rsquo;t fully stable, particularly with Hyprland. It&rsquo;s less of a headache to drop in a second AMD GPU just for this.</p>
]]></content:encoded><category>omarchy</category><category>hardware</category><category>linux</category></item><item><title>AI Agents: Locking Down Your System</title><link>https://akitaonrails.github.io/en/2026/01/10/ai-agents-locking-down-your-system/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/01/10/ai-agents-locking-down-your-system/</guid><pubDate>Sat, 10 Jan 2026 11:50:36 -0300</pubDate><description>&lt;p&gt;These days we have dozens of AI coding agents — Cursor, Claude Code, Windsurf, Copilot, OpenCode, Crush and more. But there&amp;rsquo;s a problem that&amp;rsquo;s already turned into a meme: the risk of them trashing your system or shipping your private data off to someone on the internet (malware-style behavior).&lt;/p&gt;
&lt;p&gt;The thing is, to be useful these tools need access to your filesystem so they can poke around your project files and analyze your code. And more: they need to run things like compilers, linters, and the everyday stuff like grep, sed, awk, ls, and other system commands.&lt;/p&gt;</description><content:encoded><![CDATA[<p>These days we have dozens of AI coding agents — Cursor, Claude Code, Windsurf, Copilot, OpenCode, Crush and more. But there&rsquo;s a problem that&rsquo;s already turned into a meme: the risk of them trashing your system or shipping your private data off to someone on the internet (malware-style behavior).</p>
<p>The thing is, to be useful these tools need access to your filesystem so they can poke around your project files and analyze your code. And more: they need to run things like compilers, linters, and the everyday stuff like grep, sed, awk, ls, and other system commands.</p>
<p>Eventually, an LLM might fire off some random <code>rm -rf /</code> and start wiping things from your system, for example. The code itself, if it accidentally deletes something it shouldn&rsquo;t, in theory you should be safe because your code should at the bare minimum be in a Git repo (GitHub, GitLab, etc.), so you&rsquo;d just revert the change. But if the agent runs an <code>rm</code> outside your project directory, you can be in for all kinds of trouble.</p>
<p>It doesn&rsquo;t happen often and all these tools have checks for it, including asking whether you really want to run a command. At the same time, they also have an option to let any command run during the session without asking — because at some point it gets tedious to &ldquo;ok&rdquo; every individual command. Everyone eventually gets lazy and says <em>&ldquo;screw it, run whatever you want&rdquo;</em>.</p>
<p>And you might be thinking <em>&ldquo;oh, my favorite tool is well-behaved, it always asks before running anything, so I&rsquo;m in control&rdquo;</em>. Don&rsquo;t forget: <strong>SUPPLY-CHAIN ATTACKS</strong> exist. Every other day some open source library gets compromised with malware and the programs that depend on those libs end up infected without anyone noticing. This has happened many times, in many NPM libraries for example. There&rsquo;s no way to guarantee that even open source software is trustworthy. Always assume everything is insecure until proven otherwise, and treat these programs accordingly: with maximum restrictions.</p>
<p>To make sure these agents really aren&rsquo;t going to do anything outside the project directory, the best option is to build a <strong>JAIL</strong>. Here&rsquo;s the command for that:</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">bwrap --ro-bind /usr /usr <span class="se">\
</span></span></span><span class="line"><span class="cl">      --ro-bind /bin /bin <span class="se">\
</span></span></span><span class="line"><span class="cl">      --ro-bind /lib /lib <span class="se">\
</span></span></span><span class="line"><span class="cl">      --ro-bind /lib64 /lib64 <span class="se">\
</span></span></span><span class="line"><span class="cl">      --dev /dev <span class="se">\
</span></span></span><span class="line"><span class="cl">      --proc /proc <span class="se">\
</span></span></span><span class="line"><span class="cl">      --bind <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span> <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      --chdir <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      --unshare-all <span class="se">\
</span></span></span><span class="line"><span class="cl">      --share-net <span class="se">\
</span></span></span><span class="line"><span class="cl">      bash</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>bwrap</code> is <strong>Bubblewrap</strong>, one of the components that make things like <strong>Flatpak</strong> possible. It creates a sandbox. The Bash that fires up when you run the command above is essentially in &ldquo;jail&rdquo; — locked in. Inside it we hand out read-only access to a few system directories in case it needs libraries and tools. That&rsquo;s what <code>--ro-bind</code> is for.</p>
<p>But only the current directory <code>pwd</code> has write permission. Any command that runs inside the jail has no permission to write anywhere else on your system. Look at the example in the screenshot below where I try to delete a file in my <code>$HOME</code>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260110120225_screenshot-2026-01-10_12-02-14.png" alt="sandbox"  loading="lazy" /></p>
<p>It can&rsquo;t even list the files in my home directory. Everything is invisible inside the sandbox. This is one of the upsides of apps running under Flatpak: they&rsquo;re much more isolated than apps installed directly.</p>
<p>Inside this sandboxed bash we can now run <code>opencode</code>, <code>crush</code>, <code>cursor</code> or any other agent and be confident that they aren&rsquo;t going to do anything stupid to your system.</p>
<p>To make life easier, I&rsquo;d recommend creating a script like <code>~/.local/bin/ai-jail</code>:</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">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ai-jail — bubblewrap sandbox for AI coding agents</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Mounts the project dir read-write, auto-discovers home dotfiles with a</span>
</span></span><span class="line"><span class="cl"><span class="c1"># deny-list for sensitive dirs, and isolates namespaces.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Usage: ai-jail [--map PATH]... COMMAND [ARGS...]</span>
</span></span><span class="line"><span class="cl"><span class="c1">#        ai-jail claude</span>
</span></span><span class="line"><span class="cl"><span class="c1">#        ai-jail bash</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">PROJECT_DIR</span><span class="o">=</span><span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">TEMP_HOSTS</span><span class="o">=</span><span class="k">$(</span>mktemp /tmp/bwrap-hosts.XXXXXX<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">trap</span> <span class="s1">&#39;rm -f &#34;$TEMP_HOSTS&#34;&#39;</span> EXIT
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Mise discovery ─────────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">REAL_MISE_BIN</span><span class="o">=</span><span class="k">$(</span><span class="nb">type</span> -p mise 2&gt;/dev/null <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Localhost fix (Go resolver needs /etc/hosts) ───────────────</span>
</span></span><span class="line"><span class="cl"><span class="nb">printf</span> <span class="s1">&#39;127.0.0.1 localhost ai-sandbox\n::1       localhost ai-sandbox\n&#39;</span> &gt; <span class="s2">&#34;</span><span class="nv">$TEMP_HOSTS</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Parse --map / --rw-map arguments ─────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">EXTRA_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">==</span> --map <span class="o">||</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">==</span> --rw-map <span class="o">]]</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nv">FLAG</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">MAP_PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$2</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">[</span> -e <span class="s2">&#34;</span><span class="nv">$MAP_PATH</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$FLAG</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;--rw-map&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">            <span class="nv">EXTRA_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$MAP_PATH</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$MAP_PATH</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">            <span class="nv">EXTRA_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--ro-bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$MAP_PATH</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$MAP_PATH</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;Warning: Path </span><span class="nv">$MAP_PATH</span><span class="s2"> not found, skipping.&#34;</span> &gt;<span class="p">&amp;</span><span class="m">2</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="nb">shift</span> <span class="m">2</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Mise init command ──────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -n <span class="s2">&#34;</span><span class="nv">$REAL_MISE_BIN</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nv">MISE_INIT</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$REAL_MISE_BIN</span><span class="s2"> trust &amp;&amp; eval \&#34;\$(</span><span class="nv">$REAL_MISE_BIN</span><span class="s2"> activate bash)\&#34; &amp;&amp; eval \&#34;\$(</span><span class="nv">$REAL_MISE_BIN</span><span class="s2"> env)\&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="nv">MISE_INIT</span><span class="o">=</span><span class="s2">&#34;true&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Dotfile deny-list (never mounted — sensitive data) ─────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOTDIR_DENY</span><span class="o">=(</span>.gnupg .aws .mozilla .basilisk-dev .sparrow<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Subdirs of ~/.config to hide (tmpfs over rw config mount)</span>
</span></span><span class="line"><span class="cl"><span class="nv">CONFIG_DENY</span><span class="o">=(</span>BraveSoftware Bitwarden<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Subdirs of ~/.cache to hide (sensitive browser/app caches)</span>
</span></span><span class="line"><span class="cl"><span class="nv">CACHE_DENY</span><span class="o">=(</span>BraveSoftware basilisk-dev chromium spotify nvidia mesa_shader_cache<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dotdirs requiring read-write access</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOTDIR_RW</span><span class="o">=(</span>.claude .crush .codex .aider .config .cargo .cache .docker<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Helper functions ───────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl">is_denied<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">local</span> <span class="nv">name</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> d in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DOTDIR_DENY</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">do</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;</span><span class="nv">$d</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="k">return</span> 0<span class="p">;</span> <span class="k">done</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">is_rw<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">local</span> <span class="nv">name</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> d in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DOTDIR_RW</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">do</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;</span><span class="nv">$d</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="k">return</span> 0<span class="p">;</span> <span class="k">done</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Auto-discover dot-directories in $HOME ─────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Only directories — regular dotfiles (e.g. .claude.json) are NOT mounted.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># The tmpfs $HOME is writable so tools can create dotfiles as needed.</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOTFILE_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> entry in <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">&#34;</span>/.*<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$entry</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">||</span> <span class="k">continue</span>
</span></span><span class="line"><span class="cl">    <span class="nv">name</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="nv">$entry</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;.&#34;</span> <span class="o">||</span> <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;..&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="k">continue</span>
</span></span><span class="line"><span class="cl">    is_denied <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="k">continue</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> is_rw <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nv">DOTFILE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$entry</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/</span><span class="nv">$name</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="nv">DOTFILE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--ro-bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$entry</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/</span><span class="nv">$name</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Explicit dotfile mounts (regular files) ────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -f <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.gitconfig&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DOTFILE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--ro-bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.gitconfig&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.gitconfig&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -f <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.claude.json&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DOTFILE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.claude.json&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.claude.json&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Hide sensitive subdirs inside ~/.config (after rw mount) ───</span>
</span></span><span class="line"><span class="cl"><span class="nv">CONFIG_HIDE_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> denied in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CONFIG_DENY</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config/</span><span class="nv">$denied</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">CONFIG_HIDE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--tmpfs&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config/</span><span class="nv">$denied</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Hide sensitive subdirs inside ~/.cache (after rw mount) ──</span>
</span></span><span class="line"><span class="cl"><span class="nv">CACHE_HIDE_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> denied in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CACHE_DENY</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.cache/</span><span class="nv">$denied</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">CACHE_HIDE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--tmpfs&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.cache/</span><span class="nv">$denied</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Override ~/.local subdirs as rw (parent .local is ro) ──────</span>
</span></span><span class="line"><span class="cl"><span class="nv">LOCAL_OVERRIDES</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/state&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">LOCAL_OVERRIDES</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/state&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/state&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> rw_share in zoxide crush opencode atuin mise yarn flutter kotlin NuGet pipx ruby-advisory-db uv<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/share/</span><span class="nv">$rw_share</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">LOCAL_OVERRIDES</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/share/</span><span class="nv">$rw_share</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/share/</span><span class="nv">$rw_share</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── GPU device mounts (NVIDIA + DRM) ─────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">GPU_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> dev in /dev/nvidia* /dev/dri<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -e <span class="s2">&#34;</span><span class="nv">$dev</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">GPU_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--dev-bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$dev</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$dev</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Docker socket ────────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_MOUNT</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -S /var/run/docker.sock <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DOCKER_MOUNT</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;/var/run/docker.sock&#34;</span> <span class="s2">&#34;/var/run/docker.sock&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Shared memory (Chromium needs large /dev/shm) ───────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">SHM_MOUNT</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -d /dev/shm <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">SHM_MOUNT</span><span class="o">+=(</span><span class="s2">&#34;--dev-bind&#34;</span> <span class="s2">&#34;/dev/shm&#34;</span> <span class="s2">&#34;/dev/shm&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Display passthrough (X11 + Wayland) ─────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">DISPLAY_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="nv">DISPLAY_ENV</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># X11 / XWayland socket</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -d /tmp/.X11-unix <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DISPLAY_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;/tmp/.X11-unix&#34;</span> <span class="s2">&#34;/tmp/.X11-unix&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DISPLAY</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DISPLAY_ENV</span><span class="o">+=(</span><span class="s2">&#34;--setenv&#34;</span> <span class="s2">&#34;DISPLAY&#34;</span> <span class="s2">&#34;</span><span class="nv">$DISPLAY</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">XAUTHORITY</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">DISPLAY_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--ro-bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$XAUTHORITY</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$XAUTHORITY</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="nv">DISPLAY_ENV</span><span class="o">+=(</span><span class="s2">&#34;--setenv&#34;</span> <span class="s2">&#34;XAUTHORITY&#34;</span> <span class="s2">&#34;</span><span class="nv">$XAUTHORITY</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Wayland socket (lives in XDG_RUNTIME_DIR, typically /run/user/UID)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_RUNTIME_DIR</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nv">DISPLAY_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="nv">DISPLAY_ENV</span><span class="o">+=(</span><span class="s2">&#34;--setenv&#34;</span> <span class="s2">&#34;XDG_RUNTIME_DIR&#34;</span> <span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WAYLAND_DISPLAY</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DISPLAY_ENV</span><span class="o">+=(</span><span class="s2">&#34;--setenv&#34;</span> <span class="s2">&#34;WAYLAND_DISPLAY&#34;</span> <span class="s2">&#34;</span><span class="nv">$WAYLAND_DISPLAY</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Assemble and launch ───────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Jail Active: </span><span class="nv">$PROJECT_DIR</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">bwrap <span class="se">\
</span></span></span><span class="line"><span class="cl">  --ro-bind /usr /usr <span class="se">\
</span></span></span><span class="line"><span class="cl">  --symlink usr/bin /bin <span class="se">\
</span></span></span><span class="line"><span class="cl">  --symlink usr/lib /lib <span class="se">\
</span></span></span><span class="line"><span class="cl">  --symlink usr/lib /lib64 <span class="se">\
</span></span></span><span class="line"><span class="cl">  --ro-bind /etc /etc <span class="se">\
</span></span></span><span class="line"><span class="cl">  --ro-bind <span class="s2">&#34;</span><span class="nv">$TEMP_HOSTS</span><span class="s2">&#34;</span> /etc/hosts <span class="se">\
</span></span></span><span class="line"><span class="cl">  --ro-bind /opt /opt <span class="se">\
</span></span></span><span class="line"><span class="cl">  --ro-bind /sys /sys <span class="se">\
</span></span></span><span class="line"><span class="cl">  --dev /dev <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">GPU_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">SHM_MOUNT</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --proc /proc <span class="se">\
</span></span></span><span class="line"><span class="cl">  --tmpfs /tmp <span class="se">\
</span></span></span><span class="line"><span class="cl">  --tmpfs /run <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DOCKER_MOUNT</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DISPLAY_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --tmpfs <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DOTFILE_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CONFIG_HIDE_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CACHE_HIDE_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">LOCAL_OVERRIDES</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">EXTRA_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --bind <span class="s2">&#34;</span><span class="nv">$PROJECT_DIR</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$PROJECT_DIR</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --chdir <span class="s2">&#34;</span><span class="nv">$PROJECT_DIR</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --die-with-parent <span class="se">\
</span></span></span><span class="line"><span class="cl">  --unshare-pid <span class="se">\
</span></span></span><span class="line"><span class="cl">  --unshare-uts <span class="se">\
</span></span></span><span class="line"><span class="cl">  --unshare-ipc <span class="se">\
</span></span></span><span class="line"><span class="cl">  --hostname <span class="s2">&#34;ai-sandbox&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DISPLAY_ENV</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --setenv PS1 <span class="s2">&#34;(jail) \w \$ &#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --setenv _ZO_DOCTOR <span class="m">0</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  bash -c <span class="s2">&#34;</span><span class="nv">$MISE_INIT</span><span class="s2"> &amp;&amp; </span><span class="si">${</span><span class="p">*</span><span class="k">:-</span><span class="nv">bash</span><span class="si">}</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>This version is heavily customized. Read it through carefully because it has a deny-list so that only the directories you actually need inside the Jail are available. Some are read-write because Claude needs to be able to write to <code>~/.claude</code> or Codex needs <code>~/.codex</code>; several directories should never be inside the Jail like a Brave password store or BitWarden. Everything else you might need should be read-only. This script handles all of that.</p>
<p>Just <code>chmod +x ~/.local/bin/ai-jail</code>. Now you can run it directly or pass the command you want to run inside the sandbox:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>~/.local/bin/ai-jail crush</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>In this version it&rsquo;ll create a temporary <code>$HOME</code> directory in case it needs to write something temporary there. And I also set it up to map your <code>~/.config</code> so the agents have access to your settings. If you need more stuff like environment variables and the like, modify it to map those too — but this is the bare minimum to put together a safe sandbox.</p>
<p>Be very careful with programs that run random commands on your system. Nothing is 100% safe, especially an LLM that can hallucinate at any moment and decide to delete things, or shady tools you downloaded without being sure of where they came from that might be malware in disguise. Always run truly suspicious things only in a VM, or at the very least, inside a jail like this one.</p>
<p>This isn&rsquo;t just for AI agents, obviously. Any time you&rsquo;re about to run any command you&rsquo;re not sure about or that looks fishy, run it inside this jail.</p>
<p>As a bonus, note that I also map <a href="https://akitaonrails.com/en/2025/09/07/omarchy-2-0-mise-for-organizing-dev-environments/"target="_blank" rel="noopener">Mise</a> into the container. If you&rsquo;re on Omarchy, you should be using Mise too.</p>
<p>Specifically for Claude Code, which is what I use the most, I don&rsquo;t like to leave everything authorized in automatic mode with <code>--allow-dangerously-skip-permissions</code>, but I also don&rsquo;t like having to keep authorizing dumb stuff like every single domain it wants to search the web for (just searching is no problem at all) or any command that just lists things like <code>ls</code> or <code>grep</code>. So you can improve things by configuring directly in <code>~/.claude/settings.json</code>:</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-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;permissions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;allow&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git add *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git commit *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git status *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git diff *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git log *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git branch *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git checkout *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git switch *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git stash *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git show *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git fetch *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git pull *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git merge *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git tag *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git remote -v*)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git rev-parse *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git ls-files *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(ls *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(find *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(grep *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(wc *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(cat *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(head *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(tail *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(file *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(which *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(echo *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(pwd)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(cd *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(ps *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(env *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(printenv *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(mkdir *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(cp *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(mv *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(touch *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(npm *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(npx *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(yarn *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(pnpm *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(bun *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(node *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(ruby *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(bundle *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rails *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rake *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(go *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(cargo *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rustc *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(python *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(pip *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker compose *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker ps *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker logs *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker images *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(make *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(gh *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(curl *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(jq *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(sed *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(awk *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(sort *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(uniq *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(diff *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(* --version)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(* --help)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;WebSearch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;WebFetch&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;deny&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rm -rf *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rm -r *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(sudo *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(chmod 777 *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git reset --hard *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git clean *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git push --force *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git push -f *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker rm *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker rmi *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker system prune *)&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;ask&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git push *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git rebase *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git branch -D *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git branch -d *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rm *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(kamal *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker run *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker exec *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker stop *)&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;defaultMode&#34;</span><span class="p">:</span> <span class="s2">&#34;acceptEdits&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;enabledPlugins&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;gopls-lsp@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;typescript-lsp@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;pyright-lsp@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;rust-analyzer-lsp@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;github@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;pr-review-toolkit@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;frontend-design@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;alwaysThinkingEnabled&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><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>Look up the documentation (or just ask Claude itself) about more customizations you can do in this file to better fit your project type or workflow.</p>
<p>This is basically how a Docker or Podman container works too. I explained that in my <a href="https://akitaonrails.com/2023/03/02/akitando-139-entendendo-como-containers-funcionam/"target="_blank" rel="noopener">video about Docker</a>. If you haven&rsquo;t watched it yet, I recommend it.</p>
]]></content:encoded><category>ai-jail</category><category>coding-agents</category><category>security</category><category>linux</category></item><item><title>Omarchy 2.0 - Recommended for Beginners?</title><link>https://akitaonrails.github.io/en/2025/09/12/omarchy-2-0-recommended-for-beginners/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/09/12/omarchy-2-0-recommended-for-beginners/</guid><pubDate>Fri, 12 Sep 2025 12:00:00 -0300</pubDate><description>&lt;p&gt;The most important video on my &lt;a href="https://www.youtube.com/@Akitando"target="_blank" rel="noopener"&gt;YouTube channel&lt;/a&gt; is &amp;ldquo;Don&amp;rsquo;t Outsource Your Decisions&amp;rdquo;:&lt;/p&gt;
&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/D3L8IOncLkg"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;The questions I get the most, literally every single day, come in this format: &lt;em&gt;&amp;ldquo;Akita, should I go to college?&amp;rdquo;&lt;/em&gt;, &lt;em&gt;&amp;ldquo;Akita, what do you think I should study?&amp;rdquo;&lt;/em&gt; and variations of that.&lt;/p&gt;
&lt;p&gt;Dude, I don&amp;rsquo;t know you, I will never know nor will I ever be interested in knowing who you are, what you aim for, your aspirations, ambitions, tastes, preferences. Nothing. Neither me nor &lt;strong&gt;ANYONE ELSE&lt;/strong&gt;. Only you know what&amp;rsquo;s best for you. If you don&amp;rsquo;t know, go find out. That&amp;rsquo;s what &lt;strong&gt;LIVING&lt;/strong&gt; means.&lt;/p&gt;</description><content:encoded><![CDATA[<p>The most important video on my <a href="https://www.youtube.com/@Akitando"target="_blank" rel="noopener">YouTube channel</a> is &ldquo;Don&rsquo;t Outsource Your Decisions&rdquo;:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/D3L8IOncLkg"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>The questions I get the most, literally every single day, come in this format: <em>&ldquo;Akita, should I go to college?&rdquo;</em>, <em>&ldquo;Akita, what do you think I should study?&rdquo;</em> and variations of that.</p>
<p>Dude, I don&rsquo;t know you, I will never know nor will I ever be interested in knowing who you are, what you aim for, your aspirations, ambitions, tastes, preferences. Nothing. Neither me nor <strong>ANYONE ELSE</strong>. Only you know what&rsquo;s best for you. If you don&rsquo;t know, go find out. That&rsquo;s what <strong>LIVING</strong> means.</p>
<p>What you won&rsquo;t admit is that you want someone you consider more important or relevant to approve your choice and tell you that you got it right.</p>
<p>Never expect that from me, I will never validate you.</p>
<p>I will always say: <strong>FIGURE IT OUT</strong>. That&rsquo;s how I became who I am. I never followed anyone, I never asked anyone what I should do. I just went there and did it. Fell on my face, got up, tried again. That&rsquo;s life.</p>
<p>Let me show you what actually <strong>BOTHERS</strong> me:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912124608_screenshot-2025-09-12_12-45-47.png" alt="pergunta idiota"  loading="lazy" /></p>
<p>Let&rsquo;s check the <a href="https://learn.omacom.io/2/the-omarchy-manual/93/security"target="_blank" rel="noopener">manual</a>?</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912124657_screenshot-2025-09-12_12-46-45.png" alt="Manual"  loading="lazy" /></p>
<p>DHH took the trouble to write a manual. It has a section called &ldquo;Security&rdquo;. A quick Google search would have found it. Or this other gem:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912124836_screenshot-2025-09-12_12-48-24.png" alt="devops"  loading="lazy" /></p>
<p>How do you want to become a &ldquo;DEVOPS&rdquo; (who has to know EVERYTHING about Linux) starting like this? Already off to a bad start. Park your butt in the chair, roll up your sleeves, and test them one by one, and learn.</p>
<h3>Linux for Beginners?<span class="hx:absolute hx:-mt-20" id="linux-for-beginners"></span>
    <a href="#linux-for-beginners" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>That said, watch my video. But since I&rsquo;ve been talking a lot about <a href="/en/tags/omarchy/">Omarchy</a> already, I&rsquo;ll say that yes, this is what I use in my day to day and it&rsquo;s the only one I&rsquo;ll recommend for the near future (I may change my mind later). Having used distros ever since Linux was invented in the early 90s, I know there are dozens of distros and even I don&rsquo;t have the patience to try them all.</p>
<p><em>Why Omarchy?</em></p>
<p>First, because it made the right choice of base distro: <strong>Arch Linux</strong>. I consider it the best distro out there today for two reasons: it has very little &ldquo;bloat&rdquo; (it doesn&rsquo;t install a bunch of stuff you&rsquo;ll never use, like Ubuntu, Fedora, Mint, Deepin, or any of those other &ldquo;beginner-friendly&rdquo; distros).</p>
<p>Second, Arch Linux has the AUR, which is a repository of user-made packages that has basically everything. You don&rsquo;t need to go hunting for third-party repositories or install things manually. With a single command, <code>yay</code>, you install everything. There&rsquo;s none of that <code>apt-repository add</code> nonsense or having to <code>make install</code> or anything like that. The AUR manages all of it for you.</p>
<p>Third, it installs Hyprland (which I was already using and liked), with very good configs, and a very good package selection. Everything I would install manually myself. So it&rsquo;s a thousand times easier to get what would take me much more work to do on my own. Check my <a href="/en/tags/omarchy/">series of posts about Omarchy</a> to see those options.</p>
<h3>Linux for Beginners?<span class="hx:absolute hx:-mt-20" id="linux-for-beginners-1"></span>
    <a href="#linux-for-beginners-1" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><em>Beginner at what?</em></p>
<p>I hate this question. If you&rsquo;re really an amateur user, not a programmer, maybe you&rsquo;re an accountant, maybe you&rsquo;re just a teenage gamer. The first question I ask is: <em>why do you want to use Linux?</em></p>
<p>Let&rsquo;s say it&rsquo;s because you heard it&rsquo;s more secure (and it actually is). Then it doesn&rsquo;t matter, any distro will do. Install Ubuntu, install Manjaro, install Garuda. It makes no difference, because you&rsquo;ll never deal with anything under the hood. You&rsquo;ll open Chrome, open Steam, and that&rsquo;s it. The rest is going to be web apps.</p>
<p>Let&rsquo;s say it&rsquo;s just for gaming, then install <a href="https://bazzite.gg/"target="_blank" rel="noopener">Bazzite</a> on your mini-PC or handheld. Or just install your games on a separate Windows box just for gaming like I did. Whatever.</p>
<p>Or just stick with Windows or macOS. If you&rsquo;re a total amateur, you&rsquo;re probably going to use the same weak password for everything, and click on WhatsApp links without checking who they&rsquo;re from. It literally makes no difference which OS you use. No security from any OS will protect you from your bad habits and ignorance.</p>
<p>If you&rsquo;re a beginner at programming or tech, then I have no mercy: learn Linux properly. You want to be a professional, be a professional, learn the right way: learn to have 100% control over your own machine. There&rsquo;s no step-by-step or tutorial for that. It&rsquo;s something you&rsquo;re going to do continuously for the rest of your life. I&rsquo;ve been in this field for over 30 years and I still learn new things every day.</p>
<h3>Hardware<span class="hx:absolute hx:-mt-20" id="hardware"></span>
    <a href="#hardware" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The <strong>BIGGEST</strong> problem anyone is going to face, with ANY distro, is going to be hardware support. And that&rsquo;s why you shouldn&rsquo;t just follow what someone recommends to you:</p>
<p><strong>Nobody has the same PC as you.</strong></p>
<p>What&rsquo;s your notebook? A Lenovo? A Dell? A Positivo? From what year? Which model?</p>
<p>Everything varies: which Wi-Fi and Bluetooth device? Which graphics card? Which firmware is running? With which power profile support? Do you need FIDO2 devices for things like a fingerprint reader? Hardware keys? Does your machine have TPU? Will you need Secure Boot?</p>
<p>I don&rsquo;t know. You are the one who has to know.</p>
<p>A practical example, I have an <strong>Asus Zephyrus G14</strong> notebook that I use with Windows because it only lives in my workshop to run Bambu Studio to control my X1C 3D Printer. I haven&rsquo;t gotten around to installing Omarchy on it, but let&rsquo;s say I wanted to. Would it work?</p>
<p>Fortunately there&rsquo;s <a href="https://wiki.archlinux.org/title/ASUS_ROG_Zephyrus_G16_%282023%29_GU603"target="_blank" rel="noopener">a page on the ArchWiki</a> dedicated to this model. It looks like everything works &ldquo;out-of-the-box&rdquo;. But if it were the <a href="https://wiki.archlinux.org/title/ASUS_ROG_Zephyrus_G14_%282022%29_GA402"target="_blank" rel="noopener">previous year&rsquo;s version</a> I&rsquo;d have to check extra items. There are Wi-Fi issues, for example.</p>
<p>Each different machine will have different problems.</p>
<h3>Live Env and Ventoy<span class="hx:absolute hx:-mt-20" id="live-env-and-ventoy"></span>
    <a href="#live-env-and-ventoy" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><em>&ldquo;So what do I do, you [censored]?&rdquo;</em></p>
<p>That&rsquo;s why pretty much every good distro offers an installation ISO that comes with a live environment. I&rsquo;ve seen a lot of people on Windows who have trouble knowing what to do with an ISO. It&rsquo;s very simple.</p>
<p><img src="https://etcher.balena.io/images/Etcher_steps.gif" alt="Balena Etcher"  loading="lazy" /></p>
<p>The easiest program to use is <a href="https://etcher.balena.io/"target="_blank" rel="noopener">Balena Etcher</a>. You download the file with the <code>.iso</code> extension, whether from Ubuntu, Mint, Elementary, etc (ALWAYS FROM THE OFFICIAL SITE!!!!). Open Balena and there are only 2 steps: select the image (the ISO file), select the drive (your pen drive, careful not to pick your HD!), and done.</p>
<p>Now just boot your PC and go into the BIOS to select the pen drive to boot from, or use a key like F12 (varies from BIOS to BIOS) to have UEFI select your pen drive.</p>
<p>That will boot the Live Environment straight from the pen drive.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912120806_try-ubuntu-install-ubuntu-800x530.jpeg" alt="Try Ubuntu"  loading="lazy" /></p>
<p>Most distros start by opening a &ldquo;Welcome&rdquo; screen similar to the one above. This installer app is called <a href="https://calamares.io/"target="_blank" rel="noopener"><strong>Calamares</strong></a> and everyone with a graphical installer uses it. That&rsquo;s why it looks similar across all of them. It&rsquo;s the same step-by-step. If you&rsquo;ve installed one distro, you&rsquo;ve installed them all.</p>
<p>But you don&rsquo;t have to follow Calamares. You can close it. And now you&rsquo;re already in the graphical environment of the distro you chose to install. <strong>USE IT.</strong> Try to configure your Wi-Fi, try to pair your Bluetooth headphones or mouse, try to turn on the webcam, put in a micro-SD card and see if it shows up. Try to browse the web and feel the performance.</p>
<p>If at this point you can do all the basic things, it means the chances that everything will work are high. If at this point you already start having problems, like not being able to connect to your Wi-Fi, that distro is probably going to give you a headache. Start by going to Google and see if somebody has already solved the problem for your notebook or PC model, <strong>before</strong> continuing with Calamares.</p>
<p>Let&rsquo;s say you decided to take the day to test several distros to see which one runs best on your machine. Great call, but you&rsquo;ve probably figured out that it&rsquo;s going to be a huge pain to have several pen drives, one for each distro. Or having to keep going back to Windows and using Balena to overwrite a new distro on your only pen drive.</p>
<p>You don&rsquo;t need to do that.</p>
<p>You just have to first download and install <a href="https://www.ventoy.net/en/index.html"target="_blank" rel="noopener"><strong>Ventoy</strong></a> on your pen drive. Check the <a href="https://www.ventoy.net/en/doc_start.html"target="_blank" rel="noopener">documentation</a> on their site, but it&rsquo;s ridiculously simple.</p>
<p><img src="https://www.ventoy.net/static/img/screen/screen_uefi_en.png?v=4" alt="Ventoy Menu"  loading="lazy" /></p>
<p>After installing it on your pen drive, now just copy <strong>ALL THE ISOS</strong> you want at once onto the pen drive. When you boot from it, you&rsquo;ll enter the Ventoy menu, which lets you pick any of the ISOs you copied there. If you didn&rsquo;t like the current distro, just reboot into Ventoy again and pick another ISO. It&rsquo;s that simple.</p>
<p><img src="https://www.ventoy.net/static/img/ventoy2disk_en.png" alt="Ventoy"  loading="lazy" /></p>
<h3>Distrobox<span class="hx:absolute hx:-mt-20" id="distrobox"></span>
    <a href="#distrobox" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Let&rsquo;s say you&rsquo;re a programmer, or a devops, or any tech professional. Your concern isn&rsquo;t so much the graphical interface (which you can test with Ventoy), but whether the OS infrastructure is going to work with your development requirements.</p>
<p>Let&rsquo;s say you use Fedora. I wonder, if I go to Mint, will it have the K8s packages the way I need them? Does it support the Java version I use? If I build a <code>.deb</code> package on my Fedora, will it install properly on Ubuntu?</p>
<p>Having to dual boot or install a VM feels too heavy, too much work, just to test 2 command lines, for example.</p>
<p><img src="https://user-images.githubusercontent.com/598882/144294862-f6684334-ccf4-4e5e-85f8-1d66210a0fff.png" alt="Distrobox"  loading="lazy" /></p>
<p>That&rsquo;s what <a href="https://distrobox.it/"target="_blank" rel="noopener"><strong>Distrobox</strong></a> is for. It needs Docker or Podman already installed on your machine. But after that you just do:</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">distrobox create -n debian-test --init --image debian:latest --additional-packages <span class="s2">&#34;build-essential&#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>Once the box is created you just enter it:</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">distrobox enter debian-test</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>Done, you&rsquo;re inside Debian, super fast. Since it&rsquo;s a Docker container, you can map directories on your local machine into the container, so everything stays integrated. And the most important thing: it loads super fast, because it&rsquo;s just a Docker container.</p>
<p>The <a href="https://distrobox.it/#quick-start"target="_blank" rel="noopener">Quick Start</a> guide explains all of that and has this video with a tour to demonstrate how it works:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/Q2PrISAOtbY"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Seriously. With Distrobox there&rsquo;s no excuse not to test another distro in 2 minutes. Stop wasting time sending DMs to &ldquo;influencers&rdquo;. See for yourself! If you&rsquo;re not capable of doing that, maybe you&rsquo;re in the wrong profession.</p>
<p>Every &ldquo;dev&rdquo; or tech professional, whether devops, cybersec, data analyst, blah blah blah, whatever the trendy title is these days, <strong>MUST</strong> have control over their own machine. <strong>MUST</strong> know how to deal with security (that&rsquo;s why I wrote posts about Vaultwarden, Cloudflare, SSH, etc).</p>
<p><strong>IT&rsquo;S YOUR RESPONSIBILITY!</strong></p>
<p>Don&rsquo;t outsource your responsibility to others. Don&rsquo;t look for validation. Be <strong>promiscuous</strong> and test whichever distros you want, and do whatever you want on those distros: they are yours! There&rsquo;s no such thing as a &ldquo;distro for beginners&rdquo;, &ldquo;distro for devs&rdquo;, &ldquo;distro for devops&rdquo;. A distro is a distro. Linux is Linux. It&rsquo;s all the same, what varies is <strong>your skill</strong>. If you don&rsquo;t have the skill: earn it! Learn! Break your head over it! Pull all-nighters tuning configs! Read the official documentation! Don&rsquo;t waste time thinking you&rsquo;re going to learn on social media. You won&rsquo;t.</p>
<p>I&rsquo;ve been using Linux since the early 90s and I don&rsquo;t stick with Slackware just because it was the first one I learned and I feel a need to validate my first choice. I use whichever ones I want. Right now, I had my own Hyprland setup, and I decided I wanted to learn and use Omarchy. I spent the week banging my head against it, and wrote half a dozen blog posts. And now I&rsquo;ve learned. I know Omarchy: one more for the collection.</p>
]]></content:encoded><category>omarchy</category><category>linux</category><category>tutorials</category></item><item><title>Omarchy 2.0 - Install with the Omarchy ISO</title><link>https://akitaonrails.github.io/en/2025/09/12/omarchy-2-0-install-with-the-omarchy-iso/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/09/12/omarchy-2-0-install-with-the-omarchy-iso/</guid><pubDate>Fri, 12 Sep 2025 10:00:00 -0300</pubDate><description>&lt;p&gt;To wrap up the &lt;a href="https://akitaonrails.github.io/en/tags/omarchy/"&gt;Omarchy&lt;/a&gt; series for now, I think I still owe a quick word on installing via the official Omarchy ISO.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912093827_screenshot-2025-09-12_09-38-05.png" alt="ISO" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Just head to the &lt;a href="https://omarchy.org/"target="_blank" rel="noopener"&gt;official site&lt;/a&gt; and click &amp;ldquo;ISO&amp;rdquo; to download. Use Rufus or Balena Etcher on Windows (or &lt;a href="https://github.com/ifd3f/caligula"target="_blank" rel="noopener"&gt;Caligula&lt;/a&gt; on Linux) to burn it to a USB stick and boot from it. From there, follow the step-by-step and in less than 5 minutes you&amp;rsquo;ll have everything installed.&lt;/p&gt;</description><content:encoded><![CDATA[<p>To wrap up the <a href="/en/tags/omarchy/">Omarchy</a> series for now, I think I still owe a quick word on installing via the official Omarchy ISO.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912093827_screenshot-2025-09-12_09-38-05.png" alt="ISO"  loading="lazy" /></p>
<p>Just head to the <a href="https://omarchy.org/"target="_blank" rel="noopener">official site</a> and click &ldquo;ISO&rdquo; to download. Use Rufus or Balena Etcher on Windows (or <a href="https://github.com/ifd3f/caligula"target="_blank" rel="noopener">Caligula</a> on Linux) to burn it to a USB stick and boot from it. From there, follow the step-by-step and in less than 5 minutes you&rsquo;ll have everything installed.</p>
<p><img src="https://learn.omacom.io/u/configurator-iGH96F.png" alt="Omarchy install"  loading="lazy" /></p>
<p>As I explained in the <a href="https://akitaonrails.com/en/2025/08/29/new-omarchy-2-0-install/"target="_blank" rel="noopener">first article</a>, I did it differently: I installed pure <a href="https://archlinux.org/download/"target="_blank" rel="noopener">Arch Linux</a>, Desktop style, with Hyprland + SDDM. Then I installed Omarchy using the manual command line:</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">curl -fsSL https://omarchy.org/install <span class="p">|</span> bash</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>You can do it either way. The second gives you more control, but after gaining more experience and reading the install scripts, I recommend downloading the Omarchy ISO itself. It really is easier.</p>
<h3>Limine vs Grub<span class="hx:absolute hx:-mt-20" id="limine-vs-grub"></span>
    <a href="#limine-vs-grub" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The reason I went the manual route was because I wasn&rsquo;t sure if Omarchy supported BTRFS rollback via snapshots. I had heard that the <a href="https://codeberg.org/Limine/Limine"target="_blank" rel="noopener">Limine</a> bootloader didn&rsquo;t support that. But I was wrong: Omarchy installs Limine with <strong>Snapper</strong> support (which is similar to Timeshift, the one I usually use). Read <a href="https://wiki.archlinux.org/title/Limine"target="_blank" rel="noopener">ArchWiki section 6.2</a>. So this should be a superior option to my GRUB+Timeshift setup that I talked about in the first article.</p>
<p>Since I already installed it manually, I&rsquo;ll keep things as they are because in practice it&rsquo;s the same thing. But if I were installing again, I&rsquo;d go with Limine+Snapper just to try it out.</p>
<p>This means there&rsquo;s support for snapshotting your entire filesystem with BTRFS before a package update, and if something goes wrong, you can reboot and roll back to a previous snapshot. That gives you peace of mind so you don&rsquo;t lose a whole day trying to fix something if an important system package ships buggy or corrupted. That&rsquo;s the most important feature.</p>
<h3>SDDM vs UWSM<span class="hx:absolute hx:-mt-20" id="sddm-vs-uwsm"></span>
    <a href="#sddm-vs-uwsm" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Another thing I noticed was the login manager. On pure Arch, it installs Hyprland by default with <a href="https://wiki.archlinux.org/title/SDDM"target="_blank" rel="noopener">SDDM</a>, the same one KDE uses.</p>
<p>Omarchy uses UWSM, which auto-logs you in with your user at boot. So there&rsquo;s no login screen, it just opens Hyprland automatically.</p>
<p><em>&ldquo;But isn&rsquo;t that insecure??&rdquo;</em></p>
<p>No, because the Omarchy ISO pre-configures your SSD with LUKS, below the filesystem.</p>
<p>For those who don&rsquo;t know, <a href="https://wiki.archlinux.org/title/Dm-crypt/Encrypting_an_entire_system"target="_blank" rel="noopener">LUKS</a> stands for <strong>Linux Unified Key Setup</strong>, which provides full-disk encryption. Its advantage over Windows BitLocker is that it&rsquo;s <strong>filesystem-agnostic</strong>. In other words, it works with BTRFS or EXT4 or any other, because in the boot sequence, after the bootloader (whether Limine or Grub), it loads <strong>before</strong> the filesystem. It&rsquo;s a block-level encryptor.</p>
<p><img src="https://learn.omacom.io/u/arch-encryption-urjrDm.png" alt="install luks"  loading="lazy" /></p>
<p>So when you boot your system, it first asks for the passphrase to decrypt your disk. Then the filesystem opens and the Linux kernel can continue the normal boot with the disk unlocked. So you are <strong>always</strong> forced to type your strong user passphrase at boot to decrypt the disk.</p>
<p>That&rsquo;s why typing a password again at a login manager would be redundant and unnecessary, and I imagine that&rsquo;s why they chose UWSM instead of SDDM.</p>
<p>Especially on a laptop, or in situations where other people live with you, it&rsquo;s <strong>essential</strong> to make sure your disk is encrypted, no matter the operating system. Say you go out to work or travel and your laptop gets stolen. Without encryption, it&rsquo;s game over, regardless of whether you have a login manager. It doesn&rsquo;t protect your data. Anyone can just pull the SSD from your laptop, plug it into another computer, and boom, they can copy everything by booting as root.</p>
<p>With encryption, without the passphrase to decrypt it, accessing your data is impossible. Truly impossible, because on any modern computer from the last 15 years, the CPU ships with hardware-accelerated instructions for AES 256 encryption.</p>
<p>Getting a bit more nerdy: the decryption key <strong>is not your passphrase</strong>. During the install process, a new stronger and more secure key is generated using a <strong>key derivation</strong> process (KDF - Key Derivation Function), nowadays using Argon2id, previously PBKDF2. I explain this in this video:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/HCHqtpipwu4"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>So when you type your passphrase, what actually happens is it decrypts that secure key, and it&rsquo;s with that secure key that your disk is encrypted. That&rsquo;s why you can&rsquo;t brute-force decrypt your data.</p>
<p>Plus, AES 256 is one of those encryption algorithms that&rsquo;s <strong>quantum resistant</strong>. Even if a quantum computer were possible (it isn&rsquo;t), not even that could decrypt your disk. That&rsquo;s the level of security.</p>
<p><em>&ldquo;But won&rsquo;t it be slower?&rdquo;</em></p>
<p>No. It&rsquo;s practically imperceptible, because AES, as I said, is hardware-accelerated by your CPU and doesn&rsquo;t depend on operating system software. In day-to-day use, you&rsquo;ll barely notice. Performance is practically the same. There&rsquo;s no reason not to enable encryption, especially on a laptop.</p>
<p>In my personal case, where I installed it on a desktop PC that I control myself on a secure local network, because I know how to implement network security, it&rsquo;s not an issue. My most important data lives on my NAS (which is encrypted), in backups (which are encrypted). I don&rsquo;t have sensitive data on this PC, and the little I do have sits in files encrypted by <a href="https://veracrypt.io/en/Downloads.html"target="_blank" rel="noopener">Veracrypt</a> on a separate sub-volume.</p>
<p>That&rsquo;s why I didn&rsquo;t enable LUKS on my PC. And I kept the SDDM that pure Arch installed. Except it&rsquo;s <strong>REALLY</strong> ugly by default, so here&rsquo;s a small tip: install either the <a href="https://github.com/MarianArlt/sddm-sugar-dark"target="_blank" rel="noopener">sddm-sugar-dark</a> theme or <a href="https://github.com/simonesestito/dark-arch-sddm"target="_blank" rel="noopener">dark-arch-sddm</a>. I prefer the second one because it&rsquo;s more minimalist and looks like this:</p>
<p><img src="https://github.com/simonesestito/dark-arch-sddm/raw/master/base/screenshot.png" alt="dark arch sddm"  loading="lazy" /></p>
<p>I customized it and swapped the Arch logo for the Omarchy one, and that was it.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I&rsquo;ll say it again: only after installing manually did I see how Omarchy installs things, and today I prefer the Omarchy way. If I were installing from scratch, I&rsquo;d use the Omarchy ISO and keep it with Limine+Snapper and Hyprland+UWSM, with snapshot rollback support, full disk encryption, and automatic login into Hyprland.</p>
<p>You can tell the people behind Omarchy have experience and have already picked components that make sense to work together in the most convenient and secure way possible. I was worried that, being a new distro, maybe they hadn&rsquo;t given this much thought, but I&rsquo;m happy to say I was wrong.</p>
<p>Use the official Omarchy ISO.</p>
]]></content:encoded><category>omarchy</category><category>linux</category><category>tutorials</category></item><item><title>Omarchy 2.0 - Understanding SSH and Yubikeys</title><link>https://akitaonrails.github.io/en/2025/09/09/omarchy-2-0-understanding-ssh-and-yubikeys/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/09/09/omarchy-2-0-understanding-ssh-and-yubikeys/</guid><pubDate>Tue, 09 Sep 2025 12:00:00 -0300</pubDate><description>&lt;p&gt;This post has nothing directly to do with Omarchy, but I decided to include it in the &lt;a href="https://akitaonrails.github.io/en/tags/omarchy/"&gt;same series&lt;/a&gt; because it&amp;rsquo;s an important tip for programmers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every programmer needs to know how to manage their SSH keys.&lt;/strong&gt; At the very least because every GitHub account requires one.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-09_12-19-38.png" alt="GitHub SSH" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Every time you run &lt;code&gt;git push&lt;/code&gt; to one of your repositories, it will ask for the passphrase of that key:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❯ git push origin master
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Enter passphrase &lt;span class="k"&gt;for&lt;/span&gt; key &lt;span class="s1"&gt;&amp;#39;/home/akitaonrails/.ssh/id_ed25519&amp;#39;&lt;/span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;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"&gt;
&lt;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"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;What does this mean?&lt;/p&gt;</description><content:encoded><![CDATA[<p>This post has nothing directly to do with Omarchy, but I decided to include it in the <a href="/en/tags/omarchy/">same series</a> because it&rsquo;s an important tip for programmers.</p>
<p><strong>Every programmer needs to know how to manage their SSH keys.</strong> At the very least because every GitHub account requires one.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-09_12-19-38.png" alt="GitHub SSH"  loading="lazy" /></p>
<p>Every time you run <code>git push</code> to one of your repositories, it will ask for the passphrase of that key:</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">❯ git push origin master
</span></span><span class="line"><span class="cl">Enter passphrase <span class="k">for</span> key <span class="s1">&#39;/home/akitaonrails/.ssh/id_ed25519&#39;</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>What does this mean?</p>
<p>SSH is a set of tools, both client and server, to create a secure, encrypted connection over the internet. It has several uses and I explained how to do SSH Proxy in this video:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/T-jHuFnxZ2k"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>The whole process starts by installing SSH on your machine, and on Omarchy/ArchLinux you just do 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">yay -S openssh</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 you&rsquo;ve never created a key pair (yes, it&rsquo;s a pair), just run this command:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-keygen -t ed25519 -a 100 -C &#34;your_email@example.com&#34; -f ~/.ssh/id_ed25519</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><strong>ED25519</strong> is a public-key algorithm, meaning it creates a pair of keys: a private one that <strong>must never leave your machine</strong>, and a public one that can be exposed on the open internet (the one you register on your GitHub account, for example). I explained public keys in this other video:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/HCHqtpipwu4"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>ED25519 is a more modern algorithm than the old RSA (which I think is still the default in ssh-keygen if you don&rsquo;t make an explicit choice — it&rsquo;s still secure, but elliptic curve is even more secure).</p>
<p>It&rsquo;s based on elliptic curve cryptography (ECC). For the paranoid, yes, it&rsquo;s even considered <strong>quantum-resistant</strong>, meaning that even if a quantum computer became feasible, it wouldn&rsquo;t be able to break an ED25519 key.</p>
<p>Two files will be created after running <code>ssh-keygen</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>.rw-------   464 akitaonrails 14 Nov  2017  󰌆 id_ed25519
.rw-r--r--   108 akitaonrails 14 Nov  2017  󰷖 id_ed25519.pub</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>Pay attention to the permissions of these files. It&rsquo;s important that the <code>~/.ssh</code> directory and the private key files inside it always have <code>600</code> permission, meaning read-write only for your user, and no permission for groups or any other user. Nobody else should even be able to list the directory.</p>
<p>The <code>*.pub</code> files, which are the public keys, have <code>644</code> permission, which is read-write for you and read-only for other groups and users. As I said, the public key can be exposed publicly. Mine, for example, is this one:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ cat id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEWG5bHK02sMqilMiYu67xqdaBsk3TtCQ564bcJibDiO akitaonrails@Miner42.local</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>ED25519 has another advantage: it&rsquo;s orders of magnitude more secure than RSA and still provides a much shorter public key, which makes copy-pasting it around much easier.</p>
<p>A private key has an envelope in this format:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>-----BEGIN RSA PRIVATE KEY-----
MIIEpA ...
...
XfI0X....
...
... nQ5w==
-----END RSA PRIVATE KEY-----</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>Repeating: <strong>THIS FILE MUST NEVER LEAVE YOUR MACHINE</strong>. Never leave it lying around on a pen drive, for example. And always make sure the permission is <code>chmod 600</code> as I explained.</p>
<h3>Yubikey 5<span class="hx:absolute hx:-mt-20" id="yubikey-5"></span>
    <a href="#yubikey-5" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The most secure form, one that many companies even enforce, is the use of a <strong>HARDWARE-KEY</strong>. The private key is never written to your local machine; instead, it&rsquo;s generated inside secure hardware from which it&rsquo;s impossible to read that private key. Examples are Yubikey or FIDO2.</p>
<p>About Yubikey, be careful, you want the newer <strong>Yubikey Series 5</strong> models:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/yubikey.webp" alt="Yubikey 5"  loading="lazy" /></p>
<p>If you have the old one — usually the blue one — it&rsquo;s the Security Key v4.3.1 and only supports <strong>U2F (FIDO U2F)</strong> but not <strong>FIDO2/WebAuthn</strong>, and no <strong>PIV/OpenPGP/OTP</strong> applets. To be sure, install these packages on your Arch:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -S yubikey-manager libfido2</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 check with this 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">❯ ykman info
</span></span><span class="line"><span class="cl">WARNING: PC/SC not available. Smart card <span class="o">(</span>CCID<span class="o">)</span> protocols will not <span class="k">function</span>.
</span></span><span class="line"><span class="cl">Device type: FIDO U2F Security Key
</span></span><span class="line"><span class="cl">Firmware version: 4.3.1
</span></span><span class="line"><span class="cl">Enabled USB interfaces: FIDO
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">Applications
</span></span><span class="line"><span class="cl">Yubico OTP   Not available
</span></span><span class="line"><span class="cl">FIDO U2F     Enabled
</span></span><span class="line"><span class="cl">FIDO2        Not available
</span></span><span class="line"><span class="cl">OATH         Not available
</span></span><span class="line"><span class="cl">PIV          Not available
</span></span><span class="line"><span class="cl">OpenPGP      Not available
</span></span><span class="line"><span class="cl">YubiHSM Auth Not available</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>See how it finds the Yubikey connected via USB but everything shows up as &ldquo;Not available&rdquo;. That&rsquo;s the wrong model — this Yubikey only works as Two-Factor Authentication on websites. It&rsquo;s not useless, but for our case it doesn&rsquo;t work because:</p>
<ul>
<li>it has no PIV applet, so it can&rsquo;t act as a smartcard</li>
<li>it has no OpenPGP applet, so it can&rsquo;t store GPG or SSH private keys</li>
<li>it has no FIDO2, so you can&rsquo;t run <code>ssh-keygen -t ed25519-sk</code>.</li>
</ul>
<p>A new one, on Mercado Livre today, costs around BRL 800. If you&rsquo;re going to use it for work per the company&rsquo;s security policy, they should provide it or reimburse you. But even for personal use, I recommend investing a bit more in security.</p>
<p>With a Yubikey 5 in hand, the procedure is as follows. Start by making sure you have the packages on your machine:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -S yubikey-manager yubico-piv-tool opensc</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>Initialize the PIV application by changing the PIN:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ykman piv reset                # wipes PIV, careful
ykman piv access change-pin    # default PIN is 123456
ykman piv access change-puk    # default PUK is 12345678</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>Generate a new key pair <strong>inside</strong> the Yubikey:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ykman piv keys generate 9a pubkey.pem</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>Extract only the public key to your local <code>~/.ssh</code> directory:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-keygen -D /usr/lib/opensc-pkcs11.so -e &gt; ~/.ssh/id_yubikey.pub</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 contents of that <code>~/.ssh/id_yubikey.pub</code> file is what you&rsquo;ll add to the <code>~/.ssh/authorized_keys</code> file on the remote server you want to connect to via SSH. To open an SSH connection to it, plug the Yubikey into the USB port and run:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh -I /usr/lib/opensc-pkcs11.so your-user@server</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 will ask for your PIN and authorize the connection. If you don&rsquo;t want to keep typing that <code>-I</code> every time, just edit <code>~/.ssh/config</code> with this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># ~/.ssh/config
Host server
  PKCS11Provider /usr/lib/opensc-pkcs11.so
  User your-user</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><strong>HOWEVER</strong></p>
<p>This procedure uses the key generator inside the Yubikey, which only supports <strong>RSA (1024–2048)</strong> or <strong>ECC (P-256, P-384)</strong>. If you want to generate an ED25519 key, the flow is different, we have to run it like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-keygen -t ed25519-sk -a 100 -C &#34;you@example.com&#34; -f ~/.ssh/id_ed25519_sk</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>id_ed25519_sk</code> file that it generates on your local machine is <strong>not the private key</strong>, it&rsquo;s just a &ldquo;stub&rdquo; that points to your Yubikey. So the key stays stored in the hardware and never passes through your machine.</p>
<p>To add your public key to the remote server, just manually append it to the <code>~/.ssh/authorized_keys</code> file as we did before, or run this command to log in with your password and it will add the public key for you:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub your-user@server</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>To use your key to log in now, just do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh -i ~/.ssh/id_ed25519_sk your-user@server</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 only works because the Yubikey 5 has FIDO2 support. The old ones don&rsquo;t, which is why you can&rsquo;t do this with them.</p>
<p>Oh, and on your home server or any other server, <strong>AFTER</strong> adding your key to <code>authorized_keys</code>, I recommend <strong>DISABLING</strong> the password login option (so that no bot keeps trying to brute-force its way in). On the server, edit:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># /etc/ssh/sshd_config
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM no
...
PubkeyAuthentication yes</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>In other words, turn off all login options and leave only public-key. Now just restart the service:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl restart sshd</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>
<h3>SSH-Agent<span class="hx:absolute hx:-mt-20" id="ssh-agent"></span>
    <a href="#ssh-agent" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>To be clear: the most secure way to manage private keys is via an external security hardware device, in this case the Yubikey 5.</p>
<p>But it&rsquo;s not mandatory. If you have a dedicated PC for work, with your Arch Linux or Omarchy installed, and you don&rsquo;t keep installing shady software — like pirated games — nor keep browsing sketchy sites and clicking on random links (malware risk), keeping your keys locally on your machine isn&rsquo;t entirely bad. I&rsquo;ve been using it this way for years and have never had problems — because I&rsquo;m paranoid about the security of my machine.</p>
<p>Remember, one of the most common types of malware are the ones that upload your files, or encrypt your files and then demand a cryptocurrency ransom to return them. The good old <strong>RANSOMWARE</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ransomware-message.jpg" alt="Ransomware"  loading="lazy" /></p>
<p>This exists not only on Windows but on Mac and Linux as well. And web browsers are the main vector, especially phishing emails with malicious links that you click on, or links that come through Discord, Telegram, or WhatsApp channels.</p>
<p><strong>NEVER CLICK ON LINKS</strong>. Copy the link, paste it into a terminal, and see what kind of link it is. If it&rsquo;s not an obviously official domain like &ldquo;whatspp.com&rdquo; or &ldquo;google.com&rdquo;, don&rsquo;t go forward.</p>
<p>With all that said. Let&rsquo;s say you created your keys locally and registered them in your server&rsquo;s <code>authorized_keys</code> and in your GitHub account. Now, every time you run <code>ssh</code> or <code>git push</code>, it will ask you to type your <strong>passphrase</strong> (which, if you&rsquo;re not silly, you made <strong>VERY LONG</strong>).</p>
<p>This is extremely inconvenient to keep typing all the time. That&rsquo;s why SSH has a feature to help: caching your authentication for a fixed period using ssh-agent.</p>
<p>Start by creating this user service for systemd:</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-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># ~/.config/systemd/user/ssh-agent.service</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">Unit</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">Description</span><span class="p">=</span><span class="nx">SSH</span> <span class="nx">Agent</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">Service</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">Type</span><span class="p">=</span><span class="nx">simple</span>
</span></span><span class="line"><span class="cl"><span class="nx">Environment</span><span class="p">=</span><span class="nx">SSH_AUTH_SOCK</span><span class="p">=</span><span class="err">%</span><span class="nx">t</span><span class="err">/</span><span class="nx">ssh-agent</span><span class="p">.</span><span class="nx">socket</span>
</span></span><span class="line"><span class="cl"><span class="nx">ExecStart</span><span class="p">=</span><span class="err">/</span><span class="nx">usr</span><span class="err">/</span><span class="nx">bin</span><span class="err">/</span><span class="nx">ssh-agent</span> <span class="nx">-D</span> <span class="nx">-a</span> <span class="err">$</span><span class="nx">SSH_AUTH_SOCK</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">Install</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">WantedBy</span><span class="p">=</span><span class="nx">default</span><span class="p">.</span><span class="nx">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>Enable it with your user privileges (no sudo):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>systemctl --user enable --now ssh-agent.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>Expose it in your <code>.bashrc</code> or <code>.zshrc</code>:</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"># ~/.zshenv (or ~/.profile)</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">SSH_AUTH_SOCK</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">/ssh-agent.socket&#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>Now, every time you log into your system and start working, add your work key:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-add -t 30m ~/.ssh/id_ed25519</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 will ask for your passphrase once, but for <strong>30 minutes</strong> (which is what the <code>-t 30m</code> option does), you won&rsquo;t need to re-type the passphrase and can comfortably keep accessing <code>ssh</code> or doing <code>git push</code>.</p>
<p>If you want to remove the cache from the agent before the timeout, you can do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-add -d ~/.ssh/id_ed25519</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 your work routine involves using ssh, instead of manually running <code>ssh-add</code> you can add the following configuration to your <code>~/.ssh/config</code>:</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"># ~/.ssh/config</span>
</span></span><span class="line"><span class="cl">Host *
</span></span><span class="line"><span class="cl">  AddKeysToAgent yes
</span></span><span class="line"><span class="cl">  IdentitiesOnly yes
</span></span><span class="line"><span class="cl">  IdentityFile ~/.ssh/id_ed25519</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 every time you open the terminal, it will ask you to type your passphrase. And that will last while your session is logged in.</p>
<p>Finally, the filename in the examples is always <code>id_ed25519</code>, but that has no relevance whatsoever — you can rename it however you want and even create multiple keys to use for separate services if you want to be extra secure. Just generate keys like <code>heroku_id_ed25519</code> or <code>google_cloud_id_ed25519</code> and so on. Create as many keys as you want or think necessary. This applies both to local keys and to keys on the Yubikey 5.</p>
<p>I think these are the aspects I consider most important to know about SSH keys. Did I forget anything else important? Feel free to share more tips in the comments below.</p>
]]></content:encoded><category>omarchy</category><category>security</category><category>linux</category></item><item><title>Omarchy 2.0 - TUIs (Terminal User Interface Apps)</title><link>https://akitaonrails.github.io/en/2025/09/09/omarchy-2-0-tuis/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/09/09/omarchy-2-0-tuis/</guid><pubDate>Tue, 09 Sep 2025 01:00:00 -0300</pubDate><description>&lt;p&gt;One of the most frustrating things for a Linux user is that there are very few well-made graphical apps. Look at Gimp or LibreOffice. They&amp;rsquo;re ugly, outdated, very similar to the first time I used them over 20 years ago.&lt;/p&gt;
&lt;p&gt;The more &amp;ldquo;modern&amp;rdquo; apps are basically canned web front-ends wrapped inside Electron. The problem? They&amp;rsquo;re &lt;strong&gt;HEAVY&lt;/strong&gt; and chew through RAM like there&amp;rsquo;s no tomorrow. Start opening Discord, Spotify, and a handful of these and see if your old 8GB notebook can keep up.&lt;/p&gt;</description><content:encoded><![CDATA[<p>One of the most frustrating things for a Linux user is that there are very few well-made graphical apps. Look at Gimp or LibreOffice. They&rsquo;re ugly, outdated, very similar to the first time I used them over 20 years ago.</p>
<p>The more &ldquo;modern&rdquo; apps are basically canned web front-ends wrapped inside Electron. The problem? They&rsquo;re <strong>HEAVY</strong> and chew through RAM like there&rsquo;s no tomorrow. Start opening Discord, Spotify, and a handful of these and see if your old 8GB notebook can keep up.</p>
<p>But, especially if you&rsquo;re a programmer, you should pay more attention to TUIs. At first, some will turn their nose up: <em>&ldquo;terminal apps are ugly, black and white, boxy, blegh&rdquo;</em></p>
<p>Twenty years ago, they&rsquo;d be right. But it&rsquo;s 2025 and today&rsquo;s terminals support 256 colors (more than enough), various resolutions and, most importantly, fonts, in particular <a href="https://www.nerdfonts.com/"target="_blank" rel="noopener">Nerd Fonts</a>, fonts that have icons inside. That&rsquo;s how a tool like <a href="https://github.com/eza-community/eza"target="_blank" rel="noopener">eza</a> works, which I use every day aliased to <code>ls</code> and looks 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>And there&rsquo;s more: there are modern libraries, for several languages, like <a href="https://github.com/charmbracelet/bubbletea"target="_blank" rel="noopener"><strong>BubbleTea</strong></a>, a framework for building TUIs. Check their GitHub but they have the tools to make interfaces like this:</p>
<p><img src="https://camo.githubusercontent.com/e2b13cfb4ea64e98028ab21e0dca97a6af46d8eeb78ef092c5cd4669ea7b7bef/68747470733a2f2f73747566662e636861726d2e73682f627562626c657465612f627562626c657465612d6578616d706c652e676966" alt="BubbleTea"  loading="lazy" /></p>
<p>And if you just want a simple widget to complement a Bash script you&rsquo;re writing, they have <a href="https://github.com/charmbracelet/gum"target="_blank" rel="noopener"><strong>Gum</strong></a>, which lets you do interfaces like this:</p>
<p><img src="https://camo.githubusercontent.com/cea695e2df946b14838196a5cfb7d6608f03387ba42c7a7fb5afa6432371635c/68747470733a2f2f7668732e636861726d2e73682f7668732d31715935375272516c584375796473456744703638472e676966" alt="Gum"  loading="lazy" /></p>
<p>And to use it just install the binary and put lines like this in your 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-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Gum examples</span>
</span></span><span class="line"><span class="cl">gum choose <span class="s2">&#34;fix&#34;</span> <span class="s2">&#34;feat&#34;</span> <span class="s2">&#34;docs&#34;</span> <span class="s2">&#34;style&#34;</span> <span class="s2">&#34;refactor&#34;</span> <span class="s2">&#34;test&#34;</span> <span class="s2">&#34;chore&#34;</span> <span class="s2">&#34;revert&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gum input --placeholder <span class="s2">&#34;scope&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gum confirm <span class="s2">&#34;Commit changes?&#34;</span> <span class="o">&amp;&amp;</span> git commit -m <span class="s2">&#34;</span><span class="nv">$SUMMARY</span><span class="s2">&#34;</span> -m <span class="s2">&#34;</span><span class="nv">$DESCRIPTION</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>And if you use <a href="https://akitaonrails.com/en/tags/omarchy/"target="_blank" rel="noopener"><strong>Omarchy</strong></a> they ship an extra feature for TUIs. Just hit &ldquo;SUPER+ALT+SPACE&rdquo; to bring up the main menu, then pick &ldquo;INSTALL&rdquo; and &ldquo;TUI&rdquo;. Or simply open the terminal and run:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$OMARCHY_PATH/bin/omarchy-tui-install</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-09-08_23-22-50.png" alt="omarchy-tui-install"  loading="lazy" /></p>
<p>It&rsquo;ll ask for a name for the App and the command line to run. You can also pick icons at <a href="https://dashboardicons.com/"target="_blank" rel="noopener">Dashboard Icons</a>. Once registered that way, when you call the Rofi launcher with &ldquo;SUPER+SPACE&rdquo; and type &ldquo;lazy&rdquo;, LazyDocker shows up as if it were a regular app:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/hdmi-a-3.png" alt="Rofi LazyDocker"  loading="lazy" /></p>
<p>And since Hyprland has no title bar or anything, TUI apps or web apps (there&rsquo;s <code>omarchy-webapp-install</code> too) open as if they were &ldquo;native&rdquo; apps:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-09_00-49-28.png" alt="tiles windows"  loading="lazy" /></p>
<p>Read the <a href="https://learn.omacom.io/2/the-omarchy-manual/59/tuis"target="_blank" rel="noopener">TUIs</a> and <a href="https://learn.omacom.io/2/the-omarchy-manual/63/web-apps"target="_blank" rel="noopener">Web-Apps</a> sections in the Omarchy manual.</p>
<p>Obviously, this is only for those who prefer the feeling of running &ldquo;Apps&rdquo;, but it&rsquo;s the same thing as opening a Terminal and simply typing <code>lazydocker</code> on the command line - which is my preference. What these Omarchy scripts do is create a <code>.desktop</code> file at <code>~/.local/share/applications</code>. The <code>LazyDocker.desktop</code> example generates this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ cat ~/.local/share/applications/LazyDocker.desktop
[Desktop Entry]
Version=1.0
Name=LazyDocker
Comment=LazyDocker
Exec=alacritty --class TUI.tile -e lazydocker
Terminal=false
Type=Application
Icon=/home/akitaonrails/.local/share/applications/icons/LazyDocker.png
StartupNotify=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>If you don&rsquo;t want this launcher anymore, just delete the file or call the <code>$OMARCHY_PATH/bin/omarchy-tui-remove</code> script.</p>
<p>This feature is particularly interesting in Hyprland because, unlike GNOME or KDE, the window has no top title bar, so it doesn&rsquo;t give away that you&rsquo;re just opening a terminal or web browser. That&rsquo;s how it ships pre-configured with things like ChatGPT or Grok looking like &ldquo;native&rdquo; apps, when they&rsquo;re really just Chromium windows opening each one&rsquo;s URLs directly:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-09_00-54-52.png" alt="WebApps Omarchy"  loading="lazy" /></p>
<h2>Some TUIs I like<span class="hx:absolute hx:-mt-20" id="some-tuis-i-like"></span>
    <a href="#some-tuis-i-like" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>In my opinion, since there are no pretty GUI apps for Linux, it&rsquo;s really better to use the respective web apps like Canva or ChatGPT that way, but more interesting is having actual TUIs. As I said, these days it&rsquo;s not hard to make pretty TUIs. For a programmer, the advantage is that you don&rsquo;t have to deal with front-end frameworks first, and second, you don&rsquo;t have to deploy anywhere on the internet.</p>
<p>TUIs are lightweight, use few machine resources, they&rsquo;re easy for any programmer to build, and you can have a home server or a server in some cloud, SSH in, and access the TUI, without having to deal with X11 or RDP or anything heavier just to get a GUI.</p>
<p>Some TUIs are obvious: in the <a href="https://akitaonrails.com/en/2025/09/07/omarchy-2-0-lazyvim-lazyextras/"target="_blank" rel="noopener">previous post</a> I already talked about <a href="https://www.lazyvim.org/"target="_blank" rel="noopener"><strong>LazyVim</strong></a> and how it&rsquo;s a text editor as competent and complete as VS Code, using only a fraction of the machine&rsquo;s resources. Everyone should know tools like HTOP or BTOP. Plus <a href="https://github.com/jesseduffield/lazygit"target="_blank" rel="noopener">LazyGit</a> or <a href="https://github.com/jesseduffield/lazydocker"target="_blank" rel="noopener">LazyDocker</a>. All of these already come in Omarchy.</p>
<p>But there&rsquo;s much more, for example, <a href="https://github.com/danvergara/dblab"target="_blank" rel="noopener"><strong>dblab</strong></a> which is a TUI database client for MySQL or Postgres:</p>
<p><img src="https://github.com/danvergara/dblab/raw/main/screenshots/dblab-cover.png" alt="dblab"  loading="lazy" /></p>
<p>Another similar one is <a href="https://github.com/jorgerojas26/lazysql"target="_blank" rel="noopener">LazySQL</a>, written in Go:</p>
<p><img src="https://github.com/jorgerojas26/lazysql/raw/main/images/lazysql.png" alt="LazySQL"  loading="lazy" /></p>
<p>Or if you prefer tools written in Rust, there&rsquo;s <a href="https://github.com/achristmascarl/rainfrog"target="_blank" rel="noopener"><strong>rainfrog</strong></a>:</p>
<p><img src="https://github.com/achristmascarl/rainfrog/raw/main/vhs/demo.gif" alt="rainfrog"  loading="lazy" /></p>
<p>For those who can&rsquo;t use CURL on the command line and prefer Postman to call and explore APIs, there&rsquo;s the TUI <a href="https://github.com/darrenburns/posting"target="_blank" rel="noopener"><strong>Posting</strong></a> that does the same thing:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/422760155-78359ab0-5e0c-4c0b-a60b-dce06b11bbf5.png" alt="Posting"  loading="lazy" /></p>
<p>For those of us who run lots of Docker containers, I already mentioned LazyDocker, but there&rsquo;s also <a href="https://github.com/mrjackwills/oxker"target="_blank" rel="noopener"><strong>oxker</strong></a>:</p>
<p><img src="https://github.com/mrjackwills/oxker/raw/main/.github/demo_01.webp" alt="oxker"  loading="lazy" /></p>
<p>If you use Discord, there&rsquo;s <a href="https://github.com/ayn2op/discordo"target="_blank" rel="noopener"><strong>discordo</strong></a>:</p>
<p><img src="https://github.com/ayn2op/discordo/raw/main/.github/preview.png" alt="Discordo"  loading="lazy" /></p>
<p>Anyone who does a lot of Linux ricing has had to burn ISOs to USB sticks dozens of times; for that there&rsquo;s <code>dd</code> on the command line, but there&rsquo;s a cooler TUI called <a href="https://github.com/ifd3f/caligula"target="_blank" rel="noopener"><strong>Caligula</strong></a>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2025/09/09/omarchy-2-0-tuis/caligula-burning.png" alt="Caligula burning a disk image in the terminal: a “Burning… 251.7 MB” bar at the top, a write-speed graph oscillating up to 32 MB/s over ~12 seconds, and a Stats panel showing input my_cool.img.lz4, output /dev/sdb, average write of 20.2 MB/s, and an ETA of 24 seconds."  loading="lazy" /></p>
<p>If your SSD is filling up and you have no idea where the hell all that space is going, there are several tools to analyze and show you. One of them is <a href="https://github.com/dundee/gdu"target="_blank" rel="noopener"><strong>gdu</strong></a>:</p>
<p><img src="https://camo.githubusercontent.com/d8fa7d2f7bdd10dce45a81c2accf26d597b300b82e01b97a1288ff2f1fe06c57/68747470733a2f2f61736369696e656d612e6f72672f612f3338323733382e737667" alt="gdu"  loading="lazy" /></p>
<p>And the last one I thought was cool out of nostalgia is <a href="https://github.com/andmarti1424/sc-im"target="_blank" rel="noopener"><strong>sc-im</strong></a>; think Excel as a TUI or - for those of us who came from the 80s - think Lotus 1-2-3 but with Vim keybindings:</p>
<p><img src="https://github.com/andmarti1424/sc-im/raw/main/screenshots/scim-plot-graph.gif?raw=true" alt="sc-im"  loading="lazy" /></p>
<p>Why open Google Spreadsheets just to make a Todo list if you can do it in the Terminal?? TUIs are exceptional!</p>
<p>If you want to discover more TUIs check out <a href="https://github.com/rothgar/awesome-tuis/?tab=readme-ov-file"target="_blank" rel="noopener"><strong>awesome-tuis</strong></a> on GitHub:</p>
<p><a href="https://github.com/rothgar/awesome-tuis/?tab=readme-ov-file"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2025/09/09/omarchy-2-0-tuis/awesome-tuis-banner.png" alt="awesome tuis"  loading="lazy" /></a>
Again: zero need for heavy web front-ends for simple things. Nothing beats the simplicity of a command line. And with libraries like BubbleTea or Gum, it&rsquo;s now very easy for anyone to make a pretty app. And with Hyprland and Omarchy, everything stays integrated side-by-side with any other GUI app.</p>
<p>Getting a bit philosophical, the history of GUIs starts with <em>skeuomorphic</em> graphical interfaces that try to imitate objects from our daily life, particularly looking pseudo-3D, like Windows 95 was. The 2000s arrived and they started migrating to <em>flat</em> or <em>minimalist</em> interfaces, completely removing the skeuomorphism, making everything flat. That came mainly from Windows 8&rsquo;s Metro. And I think that&rsquo;s converging toward a more TUI-like style, coming full circle, going back to how it was on 70s terminals. Personally, I find that fascinating.</p>
]]></content:encoded><category>omarchy</category><category>developer-tools</category><category>linux</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>Configuring My Synology NAS with NFS on Linux</title><link>https://akitaonrails.github.io/en/2025/04/17/configuring-my-synology-nas-with-nfs-on-linux/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/17/configuring-my-synology-nas-with-nfs-on-linux/</guid><pubDate>Thu, 17 Apr 2025 20:40:00 -0300</pubDate><description>&lt;p&gt;3 posts on the same day, but I took the day off to fix my Linux problems, so here is one more note for my future self.&lt;/p&gt;
&lt;p&gt;Everyone who follows me, particularly on &lt;a href="https://www.instagram.com/akitaonrails/"target="_blank" rel="noopener"&gt;Instagram&lt;/a&gt;, knows about my NAS saga. In particular my new Synology DS1821+ with more than &lt;strong&gt;100TB&lt;/strong&gt; (4x 12TB HDs and 4x 20TB HDs, and I already have 4 more 20TB HDs to upgrade in the future when I need to). I use it for a lot of stuff, particularly my &lt;a href="https://www.akitaonrails.com/2024/04/03/meu-netflix-pessoal-com-docker-compose"target="_blank" rel="noopener"&gt;Personal Netflix&lt;/a&gt; that I explained in that other post on how I set it up with Docker.&lt;/p&gt;</description><content:encoded><![CDATA[<p>3 posts on the same day, but I took the day off to fix my Linux problems, so here is one more note for my future self.</p>
<p>Everyone who follows me, particularly on <a href="https://www.instagram.com/akitaonrails/"target="_blank" rel="noopener">Instagram</a>, knows about my NAS saga. In particular my new Synology DS1821+ with more than <strong>100TB</strong> (4x 12TB HDs and 4x 20TB HDs, and I already have 4 more 20TB HDs to upgrade in the future when I need to). I use it for a lot of stuff, particularly my <a href="https://www.akitaonrails.com/2024/04/03/meu-netflix-pessoal-com-docker-compose"target="_blank" rel="noopener">Personal Netflix</a> that I explained in that other post on how I set it up with Docker.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4egervwxarfvls2uw3kg5k13i3hd" alt="Synology DSM"  loading="lazy" /></p>
<p>In the highlights and posts about it on Insta I already discussed details of this NAS, but to summarize:</p>
<ul>
<li>I prefer Synology because deep down it is a Linux box with the BTRFS filesystem (which I like), which is almost as flexible but easier to use than ZFS. And yes, I know unraid, TrueNAS and others exist. But I think Synology DSM is very competent (there is an open source version if you want), and it does everything I need.</li>
<li>I prefer Plex over Emby or Jellyfin. I have tested them, but Plex&rsquo;s support for TV and mobile apps is much better in my opinion, the player is more stable, and I also think its organization features are more robust.</li>
<li>Why not just use Cloud? First because it is slow. My fiber is only 1Gbps and upload is half of that. Second because my internal network is ultra fast (10Gbps) and much better. Third because nothing in the &ldquo;Cloud&rdquo; has any guarantee, they can block you, they can get hacked, they can delete things by accident. Fourth, everything that is mine, I like it being <strong>really</strong> mine, within my reach, under my total control, with nobody to mess with my property. It costs more, it is a choice. I literally have more and better movies and series than Netflix with content you will not easily find in any streaming service anymore, for example.</li>
</ul>
<p>In fact, I trust &ldquo;cloud&rdquo; so little that I use it but I keep a copy of everything on my local NAS using the Cloud Sync app from the Synology store:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/deednjd3ii774b5b7jdxu1azox3x" alt="Cloud Sync"  loading="lazy" /></p>
<p><strong>&ldquo;Yeah, but what if your NAS dies in some unrecoverable way and you lose EVERYTHING?!&rdquo;</strong></p>
<p>No backup is complete unless it follows the <strong>3-2-1 Mantra</strong>, remember??</p>
<ul>
<li><strong>3 copies, 2 media types, 1 off-site</strong></li>
</ul>
<p>I have some things recorded on Millennial Discs (mdiscs, which are like Blu-Rays but made of inorganic material that never degrades). And I make copies of the most important things from my NAS to Amazon Glacier using the &ldquo;Glacier Backup&rdquo; app that is also in the Synology store.</p>
<p>Glacier is like S3 but much cheaper because it does not have easy access or fast transfer. It is built for backups, where you write once and almost never access again. <strong>ENCRYPTED</strong>, of course.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/y83uw9rtu8p5hgsq0ugyanb5cibj" alt="Glacier Backup"  loading="lazy" /></p>
<p>Notice how little I trust cloud, like dropbox, that I make a copy on my NAS and send it to another cloud. There are things with multiple copies indeed. I do not trust anyone&rsquo;s service, some human will always commit a catastrophic mistake one day, it is just a matter of time.</p>
<p>Oh yes, and I do download a lot of things from torrent and I consider myself a Power User and I know what is malware/virus and what is not, and I know how they work. But I am also human, so all my Windows machines have at least Windows Defender active, Malwarebytes which I also like, and even so I still run yet another antivirus on the NAS as well, which is also in the Synology store. I do not trust anything without redundancy of everything, under my total control.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/bekpbla77t4o4uxcysqks18racwc" alt="antivirus"  loading="lazy" /></p>
<p>More importantly, and the reason I choose BTRFS both on the NAS and on my PC: <strong>snapshots</strong>. It takes snapshots of everything periodically. So, if at some point I run a botched <code>rm -Rf</code>, I lose nothing, I just go back to the previous snapshot and everything will be there, intact. Snapshots are smart, they take almost no space, because in BTRFS the same binary content of identical files points to the same physical location. It does not work like <code>rsync</code> that actually duplicates everything and doubles the space used.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/zhbq7dv8ai8jupqwj3zqvmetq5yk" alt="Synology Snapshots"  loading="lazy" /></p>
<p>On your Linux PC/notebook, with BTRFS, install the Timeshift program to do the same thing. The best part: if one day you update your system and it does not boot or something breaks after an update, it is easy: just reboot and choose the previous snapshot in GRUB and that is it, it will go back exactly as it was before the update. On Arch/Manjaro it is smart and already takes snapshots automatically every time before <code>pacman</code> runs:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/n20yo00ztofrjsu6p6678wmfphdd" alt="Timeshift"  loading="lazy" /></p>
<p>Anyway, another use is for personal code projects on my Linux. On the mini-PC I use on the TV as a media center, I run Windows 11. Because I use it connected to another eGPU with an RTX 4060 and I use Steam for games. Yes, many Steam games run on Linux, there is Bazzite, etc. But there are things that only run on Windows and I preferred convenience. I only use it for games and media, so screw it, it works plug and play.</p>
<p>My other home server mini-PC is where my Docker stuff for media center, torrent, plex server and everything else runs. It is a headless Ubuntu LTS. Plus my handhelds like the Rog Ally X that runs Windows. All of them have access to my Synology NAS, where I expose shared folders with SMB and that is it. Plug and play.</p>
<p>I used to do the same on my Manjaro Linux: mount the folders with SMB. But this has a problem if I want to code something and want to use Git: in SMB there are no chmod attributes (like the +x flag for executable scripts) and every time I do git push, it messes up all the permissions of everything. It is a huge pain. Anyone who has used Git on Windows knows this.</p>
<p>I cannot keep scripts executable in this shared directory. So it makes my life hard on Linux. So I decided to fix this once and for all and do the right thing: stop using SMB and start using NFS, which is the native Linux way to access shared folders on the network from another Linux server (if the server were Windows, then it would have to be SMB, but nobody in their right mind sets up a NAS with Windows Server).</p>
<p>In SMB, the service ignores filesystem ownership and permissions. I create users and assign them to the service, not to the files. Just log in with that user and it will have access to the files. So there is no way to do <code>chown</code> and change ownership, nor <code>chmod</code> to change permissions individually on the files, it is all normalized.</p>
<p>In NFS, the service exposes exactly the ownership and permissions on the filesystem directly, without an ACL middleman to check permissions on the side. So I have the same level of access to those attributes that I have on the local files of my Linux, which is what I want.</p>
<p>But Synology does an annoying thing for some reason. On any Linux, the first user and first group created are usually UID: 1000 and GID: 1000. Try it, go to your terminal and run <code>id [your user]</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ id akitaonrails
uid=1000(akitaonrails) gid=1000(akitaonrails) groups=1000(akitaonrails),998(wheel),996(audio),994(input),...</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>So it does not matter whether you do <code>chown akitaonrails:akitaonrails foo.txt</code> or <code>chown 1000:1000 foo.txt</code>. In practice, the identifier number is what matters, not the name.</p>
<p>With NFS, if on the Synology side there is a user with the same UID and GID number, I automatically have permission. But when you create users on Synology, for whatever reason, it starts creating them with IDs above <strong>1020 instead of 1000</strong>. So I have an &ldquo;akitaonrails&rdquo; user on Synology with UID 1026 instead of 1000.</p>
<p>Even being able to mount the shared folder with NFS, if I try to use Git, for example, it detects that my local UID is 1000 and the files have ownership of UID 1026:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git st fatal: detected dubious ownership in repository at &#39;/mnt/gigachad/Projects/dotfiles&#39; To add an exception for this directory, call: 

	git config --global --add safe.directory /mnt/gigachad/Projects/dotfiles</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 cannot keep using mixed UIDs, it becomes a mess as bad as SMB. There are mapping tricks and other hacks, but there are only two &ldquo;cleaner&rdquo; solutions:</p>
<ul>
<li>change my Linux UID/GID to also be 1026</li>
<li>create a new user on Synology, change its ID to 1000 and chown all files to this new user.</li>
</ul>
<p>I chose the second option. The first thing is to enable SSH access on the NAS (the ideal is to leave such things off, for security, but I am on a private local network so the risk is minimal):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jpanifwk7guaxaekpoph0hty61ve" alt="SSH Synology"  loading="lazy" /></p>
<p>At a minimum I always change the default SSH port, out of habit, but it does not really matter. You need to know how to use SSH, I will not teach it here, there is ChatGPT for that.</p>
<p>Now, create a new standard user:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/l6surkfy60whkaqri8m2rzqrq7d2" alt="Novo Usuário"  loading="lazy" /></p>
<p>Let&rsquo;s also make sure the NFS service is on, since I am going to start using it (by default it comes off, with only SMB on because the majority of clients are Windows PCs):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/tpyix3dfkc0h2i4x4iaad99djffj" alt="Enable NFS"  loading="lazy" /></p>
<p>Now SSH into the NAS and edit the UID and GID, first by editing <code>/etc/passwd</code> and finding a line like:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>akitaonrails-nfs:x:10xx:10xx:...</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 changing it to:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>akitaonrails-nfs:x:1000:1000:...</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 editing <code>/etc/group</code> and the same thing, changing the newly created <code>akitaonrails-nfs</code> group to <code>1000</code> as well. Done, now I have a user with UID 1000 and GID 1000 created. The name is not the same as on my PC but that does not matter, only the numbers.</p>
<p>In the Synology DSM GUI, do not forget to add permissions for each shared folder, for NFS:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/x5q0fmb5u6qxd7ahe7f7nquoy73k" alt="NFS permissions"  loading="lazy" /></p>
<p>Still over SSH, you have to change the ownership and fix the permissions of <strong>ALL FILES</strong> and in my case, with terabytes, it took QUITE a while, just leave it running there:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo chown -R 1000:1000 /volume1/GIGACHAD
sudo chmod -R 0755 /volume1/GIGACHAD</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 disconnect from SSH and leave it running there (since it is going to take a long time), just run it detached from the SSH session with <code>nohup</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>nohup chown -R 1000:1000 /volume1/GIGACHAD &gt; /tmp/chown.log 2&gt;&amp;1 &amp;</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>One thing to be careful about is not doing something like <code>chmod -R -x</code> on everything. First: directories need to have the execute bit and if you remove it, you will no longer be able to navigate directories, so you have to separate fixing directories from files. Second: it is good to apply this fix on directories to make sure no files were left with the executable bit (which can create security problems, imagine random files inside Node modules directories). Third: directories like &ldquo;#snapshot&rdquo; are for filesystem control and are read-only, trying to mess with them will only eat A LOT of time, so it is better to filter them out and not touch them.</p>
<p>Or make a script with this and run it with nohup so you can leave SSH while waiting for it to run (in my case, with terabytes, it takes hours):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>nohup /tmp/fix-terachad-perms.sh &gt; /dev/null 2&gt;&amp;1 &amp;</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>There are still some hidden little problems. Still on the Synology SSH you have to change the <code>/etc/exports</code> that NFS uses because it still maps by default to user UID 1026 and 100, you have to change both to 1000:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># old
sudo cat /etc/exports
Password:

/volume1/GIGACHAD       192.168.0.0/24(rw,async,no_wdelay,no_root_squash,insecure_locks,sec=sys,anonuid=1025,anongid=100)
/volume1/TERACHAD       192.168.0.0/24(rw,async,no_wdelay,crossmnt,no_root_squash,insecure_locks,sec=sys,anonuid=1025,anongid=100</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 problem with messing with system files like this is that you do not know if touching the NFS permissions GUI, or if updates come, will revert it to how it was before. If anyone knows of a more permanent solution, let me know.</p>
<p>And done, with all this, just unconfigure the CIFS/SMB configuration I had and replace it with NFS. I prefer to use <a href="https://wiki.archlinux.org/title/Autofs"target="_blank" rel="noopener">AutoFS</a> to do this. I had an <code>auto.cifs</code> before, which I can now delete. And create a new <code>/etc/autofs/auto.nfs</code> like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gigachad  -fstype=nfs4,rw,nosuid,noatime 192.168.0.xx:/volume1/GIGACHAD
terachad  -fstype=nfs4,rw,nosuid,noatime 192.168.0.xx:/volume1/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>And replace the <code>auto.cifs</code> line in <code>/etc/autofs/auto.master</code> with this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/mnt  /etc/autofs/auto.nfs --timeout=60 --ghost --browse</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 restart the service:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl daemon-reexec
sudo systemctl restart autofs</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 done, everything mounted with NFS and now I have full support for native Linux ownership and permissions, without the overhead and incompatibilities of crappy SMB. It was a bit scary doing chown and chmod en masse, but it is a NAS with BTRFS. It has RAID to handle data corruption, it has snapshots if I need to revert something. That is the advantage of having safety systems: you can do things knowing that if something goes wrong, there is a way to revert. That peace of mind is priceless.</p>
]]></content:encoded><category>homelab</category><category>linux</category><category>networking</category></item><item><title>NVIDIA and Wayland - Problems for PCI Passthrough in VMs</title><link>https://akitaonrails.github.io/en/2025/04/17/nvidia-and-wayland-problems-for-pci-passthrough-in-vms/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/17/nvidia-and-wayland-problems-for-pci-passthrough-in-vms/</guid><pubDate>Thu, 17 Apr 2025 19:35:00 -0300</pubDate><description>&lt;p&gt;Another article that&amp;rsquo;s mostly so I don&amp;rsquo;t forget and serves as a note to myself.&lt;/p&gt;
&lt;p&gt;In February 2023 I made a long video about how I configured Windows to run emulated in a QEMU/Libvirt VM, managing to pass my second GPU, an RTX 4090, into the VM, making it possible to run heavy games even while emulating Windows. If you haven&amp;rsquo;t watched it, it&amp;rsquo;s at &lt;a href="https://www.akitaonrails.com/2023/02/01/akitando-137-games-em-maquina-virtual-com-gpu-passthrough-entendendo-qemu-kvm-libvirt"target="_blank" rel="noopener"&gt;this link&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In short, I have a beefy machine, a Ryzen 9 7950X3D with 96GB of RAM. This CPU has an iGPU, a weak integrated AMD GPU. Weak but more than enough to run GNOME on two monitors, with HDR support and at 4K without any stutter. For day-to-day use, which is mostly opening a browser, it&amp;rsquo;s more than enough.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Another article that&rsquo;s mostly so I don&rsquo;t forget and serves as a note to myself.</p>
<p>In February 2023 I made a long video about how I configured Windows to run emulated in a QEMU/Libvirt VM, managing to pass my second GPU, an RTX 4090, into the VM, making it possible to run heavy games even while emulating Windows. If you haven&rsquo;t watched it, it&rsquo;s at <a href="https://www.akitaonrails.com/2023/02/01/akitando-137-games-em-maquina-virtual-com-gpu-passthrough-entendendo-qemu-kvm-libvirt"target="_blank" rel="noopener">this link</a>.</p>
<p>In short, I have a beefy machine, a Ryzen 9 7950X3D with 96GB of RAM. This CPU has an iGPU, a weak integrated AMD GPU. Weak but more than enough to run GNOME on two monitors, with HDR support and at 4K without any stutter. For day-to-day use, which is mostly opening a browser, it&rsquo;s more than enough.</p>
<p>This GPU loads on Linux with the driver that ships in the kernel, <code>amdgpu</code>, and is mounted as <code>/dev/dri/card1</code>. Then I added a 2nd GPU, the beefy RTX 4090 with 24GB of GDDR6X VRAM. Not enough to run the heaviest LLMs, but it can run enough. I bought it primarily for games, but I end up using it for AI and 3D rendering as well.</p>
<p>Now the concept is simple: at boot, Linux needs to ignore this card and not load the NVIDIA drivers. If it does that, I can&rsquo;t later pass it into the Windows VM. I used to edit my channel&rsquo;s videos in DaVinci Resolve, which used plugins that only work on Windows; that was one of the reasons at the time. Resolve itself works on Linux too, but many plugins don&rsquo;t. I also preferred using Photoshop, which is also Windows-only.</p>
<p>For this we configure Linux to load a fake driver, called <code>vfio-pci</code>, in place of the NVIDIA driver (or the open source alternative, Nouveau). In the other article I explain in more detail, but it&rsquo;s a huge pain to configure this by hand. I don&rsquo;t know if it still works, but at the time I also used a script that automates this, <a href="https://github.com/uwzis/GPU-Passthrough-Manager"target="_blank" rel="noopener">GPU-Passthrough-Manager</a>. It configures everything to prevent the NVIDIA drivers from loading and switches to vfio-pci correctly. I think it also works on Optimus laptops, when you have a weaker iGPU (to save battery) and a separate discrete GPU, but I haven&rsquo;t tested that in years so don&rsquo;t ask me, ask ChatGPT.</p>
<p>Since 2023 this has worked great. I have a script called <code>nvidia-locker</code> that&rsquo;s in the other article. If I want to run LLMs or Blender on my native Linux, I run that script and it removes vfio-pci and puts the NVIDIA driver back in real time, without rebooting. Then when I&rsquo;m done and need to boot into Windows, I run the script again and it unloads the driver in real time too, allowing it to be passed via PCI Passthrough to the QEMU VM. All neat and tidy.</p>
<p>That is, until a few months ago, when Arch and Manjaro updated to swap out the old legacy <strong>Xorg for Wayland</strong>. These days Wayland is much more stable, its portals work properly and I can use webcam, microphone, tablet and everything else without any problem. Everything works and, theoretically, Wayland is supposed to be better. The new GNOME 48 also turned out really good, with HDR support and all. Anyway, everything&rsquo;s pretty slick, I recommend it.</p>
<p>Except for the GNOME/Wayland combo with an NVIDIA GPU specifically in this PCI Passthrough to VM scenario. In short: boot still works normally, vfio-pci loads and hides the NVIDIA GPU. My <code>nvidia-locker</code> script also manages to hot-reload the NVIDIA driver. The problem is when I try to unload the driver to open the VM: GNOME won&rsquo;t let me.</p>
<p>Wayland is super aggressive and as soon as it detects that a new GPU has appeared, it loads the <code>gnome-shell</code> process onto that GPU. And since there&rsquo;s a process attached, there&rsquo;s no way to remove the driver hot anymore. Only by rebooting, and that&rsquo;s a pain.</p>
<p>There&rsquo;s no easy solution. I tried dozens of different things like permissions, UDEV rules, SystemD services, kernel options, but nothing prevents the damn <code>gnome-shell</code> or <code>gjs</code> (Flatpak portal) processes from latching onto the GPU.</p>
<p>There&rsquo;s only one way, kind of a hack:</p>
<ul>
<li>first, configure GDM (which loads GNOME and is the parent process) to not auto-restart automatically if it dies</li>
<li>second, switch to another TTY (Ctrl+Alt+F3, for example), kill GDM from there and only then unload the NVIDIA driver.</li>
<li>third, when the driver is swapped for vfio-pci, now the GPU is off and you can restart GDM and log back in. Just a pain that the open graphical apps will die, but it&rsquo;s faster than a full PC reboot.</li>
</ul>
<h2>1 - disable GDM auto-restart<span class="hx:absolute hx:-mt-20" id="1---disable-gdm-auto-restart"></span>
    <a href="#1---disable-gdm-auto-restart" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Disable GDM auto-restart
sudo mkdir -p /etc/systemd/system/gdm.service.d
sudo tee /etc/systemd/system/gdm.service.d/10-no-restart.conf &lt;&lt;EOF
[Service]
Restart=no
EOF

# Update systemd
sudo systemctl daemon-reload</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 config prevents GDM from auto-restarting. Now I can kill it. Without doing this, if I kill GDM, it reopens too fast and there&rsquo;s no time to remove the NVIDIA driver before that.</p>
<h2>2 - Trying to keep Wayland from grabbing the GPU<span class="hx:absolute hx:-mt-20" id="2---trying-to-keep-wayland-from-grabbing-the-gpu"></span>
    <a href="#2---trying-to-keep-wayland-from-grabbing-the-gpu" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The GPU-Passthrough-Manager script theoretically already does this, but to be safe it&rsquo;s good to have the following:</p>
<p>First, in <code>/etc/modprobe.d/blacklist-nvidia.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>blacklist nvidia
blacklist nvidia_drm
blacklist nvidia_uvm
blacklist nvidia_peer_mem
blacklist nvidia_modeset
blacklist nvidia-nvlink
blacklist xhci_pci
blacklist ucsi_ccg
options nvidia NVreg_EnableGpuFirmware=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>Second, in <code>/etc/modprobe.d/nvidia.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>options nvidia_drm modeset=0
options nvidia NVreg_InitializeSystemMemoryAllocations=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>The modeset option is to try to keep nvidia_drm from latching onto the GPU, but this didn&rsquo;t work either. It&rsquo;s optional, I don&rsquo;t know if there&rsquo;s a problem leaving it or removing it back to modeset=1. I think this matters more if you use the Nvidia GPU as the primary GPU where your graphical interface actually runs, especially to support Wayland correctly.</p>
<p>The other attempt is with UDEV rules in <code>/etc/udev/rules.d/80-nvidia-composite.rules</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Unified NVIDIA/AMD GPU management
ACTION==&#34;add&#34;, SUBSYSTEM==&#34;drm&#34;, KERNEL==&#34;card*&#34;, ATTRS{vendor}==&#34;0x10de&#34;, \
    RUN&#43;=&#34;/bin/sh -c &#39;chmod 000 /dev/dri/%k; echo GNOME_MUTTER_IGNORE_DEVICE=1 &gt;&gt; /etc/environment&#39;&#34;

ACTION==&#34;add&#34;, SUBSYSTEM==&#34;drm&#34;, KERNEL==&#34;card*&#34;, ATTRS{vendor}==&#34;0x1002&#34;, \
    RUN&#43;=&#34;/bin/sh -c &#39;chmod 660 /dev/dri/%k; chown root:video /dev/dri/%k&#39;&#34;

# New critical addition:
ACTION==&#34;add&#34;, SUBSYSTEM==&#34;pci&#34;, ATTR{vendor}==&#34;0x10de&#34;, ATTR{class}==&#34;0x030000&#34;, ATTR{enable}=&#34;0&#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>The idea is to try to change the chmod attributes to 000, but that didn&rsquo;t work either.</p>
<p>The last attempt was trying a Systemd service in <code>/etc/systemd/system/nvidia-lock.service</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Unit]
Description=Hardened NVIDIA Lock
Before=display-manager.service
Conflicts=multi-user.target

[Service]
Type=oneshot
# Merge of both prepare scripts
ExecStart=/bin/sh -c &#39;echo 0 &gt; /sys/class/vtconsole/vtcon0/bind&#39;
ExecStart=/bin/sh -c &#39;echo 0 &gt; /sys/class/vtconsole/vtcon1/bind&#39;
ExecStart=/bin/sh -c &#39;chmod 000 /dev/dri/card0&#39;
ExecStart=/bin/sh -c &#39;for card in /dev/dri/card*; do [ &#34;$(cat /sys/class/drm/${card##*/}/device/vendor)&#34; == &#34;0x10de&#34; ] &amp;&amp; chmod 000 &#34;$card&#34;; done&#39;

[Install]
WantedBy=graphical.target</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>Same idea of trying to block Wayland via tighter permissions, but it didn&rsquo;t work either. I think there&rsquo;s no harm in leaving this running but you can skip to the next section.</p>
<h2>3. What worked<span class="hx:absolute hx:-mt-20" id="3-what-worked"></span>
    <a href="#3-what-worked" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>After much testing, I now have two scripts (just to keep it less spaghetti). Here&rsquo;s the <code>nvidia-unlock</code> script that unloads the NVIDIA driver and has to be run from a different TTY than the one running GDM:</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">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="c1"># /usr/local/bin/nvidia-unlock - Improved version with session handling</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Store current TTY</span>
</span></span><span class="line"><span class="cl"><span class="nv">CURRENT_TTY</span><span class="o">=</span><span class="k">$(</span>tty <span class="p">|</span> sed <span class="s1">&#39;s/\/dev\/tty//&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">IS_GDM_RUNNING</span><span class="o">=</span><span class="k">$(</span>systemctl is-active gdm<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 1. Stop GDM gracefully</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$IS_GDM_RUNNING</span><span class="s2">&#34;</span> <span class="o">=</span> <span class="s2">&#34;active&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Stopping GDM...&#34;</span>
</span></span><span class="line"><span class="cl">  sudo systemctl stop gdm
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 2. Kill all graphical processes</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Terminating graphical sessions...&#34;</span>
</span></span><span class="line"><span class="cl">sudo pkill -9 -f <span class="s2">&#34;gnome-shell|gjs|Xwayland&#34;</span>
</span></span><span class="line"><span class="cl">sleep <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 3. Nuclear GPU unbind</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Removing PCI devices...&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;1&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/devices/0000:01:00.0/remove &gt;/dev/null
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;1&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/devices/0000:01:00.1/remove &gt;/dev/null
</span></span><span class="line"><span class="cl">sleep <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 4. Purge drivers</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Unloading NVIDIA modules...&#34;</span>
</span></span><span class="line"><span class="cl">sudo rmmod nvidia_drm nvidia_modeset nvidia_uvm nvidia 2&gt;/dev/null
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 5. Rescan PCI</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Rescanning PCI bus...&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;1&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/rescan &gt;/dev/null
</span></span><span class="line"><span class="cl">sleep <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 6. Restore GDM if it was running</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$IS_GDM_RUNNING</span><span class="s2">&#34;</span> <span class="o">=</span> <span class="s2">&#34;active&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Restarting GDM...&#34;</span>
</span></span><span class="line"><span class="cl">  sudo systemctl start gdm
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Return to original TTY</span>
</span></span><span class="line"><span class="cl">  sleep <span class="m">3</span> <span class="c1"># Wait for GDM to initialize</span>
</span></span><span class="line"><span class="cl">  sudo chvt <span class="nv">$CURRENT_TTY</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 7. Final verification</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> ! lsmod <span class="p">|</span> grep -q nvidia<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;✅ NVIDIA GPU successfully unlocked&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;❌ Failed to fully unlock GPU&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</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>This script kills everything under GDM, GNOME Shell, the open graphical programs, everything, so don&rsquo;t leave your Libreoffice open without saving if you run this, it will kill it all. That&rsquo;s why you have to do Ctrl+Alt+F3 and run it from a separate terminal.</p>
<p>And now, here&rsquo;s my new <code>nvidia-locker</code>:</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></span><span class="line"><span class="cl"><span class="c1">#!/bin/bash</span>
</span></span><span class="line"><span class="cl"><span class="c1"># nvidia-locker - Final working version with proper TTY handling</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get the directory where this script lives</span>
</span></span><span class="line"><span class="cl"><span class="nv">SCRIPT_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASH_SOURCE</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Configuration</span>
</span></span><span class="line"><span class="cl"><span class="nv">NVIDIA_GPU</span><span class="o">=</span><span class="s2">&#34;0000:01:00.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">NVIDIA_AUDIO</span><span class="o">=</span><span class="s2">&#34;0000:01:00.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">PCI_PATH</span><span class="o">=</span><span class="s2">&#34;/sys/bus/pci/devices&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">DRM_PATH</span><span class="o">=</span><span class="s2">&#34;/dev/dri&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">UNLOCK_SCRIPT</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">SCRIPT_DIR</span><span class="si">}</span><span class="s2">/nvidia-unlock&#34;</span> <span class="c1"># Look in same directory</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#  Core Functions </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">get_driver<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">local</span> <span class="nv">device</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$device</span><span class="s2">/driver&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    basename <span class="k">$(</span>readlink <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$device</span><span class="s2">/driver&#34;</span><span class="k">)</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;none&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;none&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">is_gui_session<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Returns true if running in a graphical session</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span> -n <span class="s2">&#34;</span><span class="nv">$DISPLAY</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">||</span> <span class="o">[</span> -n <span class="s2">&#34;</span><span class="nv">$WAYLAND_DISPLAY</span><span class="s2">&#34;</span> <span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">print_status<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> -e <span class="s2">&#34;\nCurrent GPU Status:&#34;</span>
</span></span><span class="line"><span class="cl">  lspci -nnk -d 10de:2684
</span></span><span class="line"><span class="cl">  lspci -nnk -d 10de:22ba
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">load_vfio<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;🔒 Binding to vfio-pci...&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;vfio-pci&#34;</span> <span class="p">|</span> sudo tee <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$NVIDIA_GPU</span><span class="s2">/driver_override&#34;</span> &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;vfio-pci&#34;</span> <span class="p">|</span> sudo tee <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$NVIDIA_AUDIO</span><span class="s2">/driver_override&#34;</span> &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_GPU</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/vfio-pci/bind &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_AUDIO</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/vfio-pci/bind &gt;/dev/null
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">load_nvidia_headless<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;🔓 Loading NVIDIA in headless mode...&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Clear any existing overrides</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;&#34;</span> <span class="p">|</span> sudo tee <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$NVIDIA_GPU</span><span class="s2">/driver_override&#34;</span> &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;&#34;</span> <span class="p">|</span> sudo tee <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$NVIDIA_AUDIO</span><span class="s2">/driver_override&#34;</span> &gt;/dev/null
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Load modules with headless options</span>
</span></span><span class="line"><span class="cl">  sudo modprobe nvidia <span class="nv">NVreg_InitializeSystemMemoryAllocations</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="cl">  sudo modprobe nvidia_modeset
</span></span><span class="line"><span class="cl">  sudo modprobe nvidia_uvm
</span></span><span class="line"><span class="cl">  sudo modprobe nvidia_drm <span class="nv">modeset</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Bind devices</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_GPU</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/nvidia/bind &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_AUDIO</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/nvidia/bind &gt;/dev/null
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Lock DRM nodes</span>
</span></span><span class="line"><span class="cl">  sudo chmod <span class="m">000</span> <span class="s2">&#34;</span><span class="nv">$DRM_PATH</span><span class="s2">/card0&#34;</span> 2&gt;/dev/null <span class="c1"># NVIDIA</span>
</span></span><span class="line"><span class="cl">  sudo chmod <span class="m">660</span> <span class="s2">&#34;</span><span class="nv">$DRM_PATH</span><span class="s2">/card1&#34;</span> 2&gt;/dev/null <span class="c1"># AMD</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#  Command Handlers </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">vm_mode<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">current_driver</span><span class="o">=</span><span class="k">$(</span>get_driver <span class="s2">&#34;</span><span class="nv">$NVIDIA_GPU</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$current_driver</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;nvidia&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> is_gui_session<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;⚠️  Please switch to TTY (Ctrl+Alt+F2) and run:&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;   sudo systemctl stop gdm &amp;&amp; sudo nvidia-unlock&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;⚠️  Already in TTY - running nvidia-unlock...&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="o">[</span> -x <span class="s2">&#34;</span><span class="nv">$UNLOCK_SCRIPT</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        sudo <span class="s2">&#34;</span><span class="nv">$UNLOCK_SCRIPT</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;❌ Error: nvidia-unlock not found at </span><span class="si">${</span><span class="nv">UNLOCK_SCRIPT</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;ℹ️  Make sure both scripts are in the same directory&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">      <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  load_vfio
</span></span><span class="line"><span class="cl">  print_status
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">host_mode<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">current_driver</span><span class="o">=</span><span class="k">$(</span>get_driver <span class="s2">&#34;</span><span class="nv">$NVIDIA_GPU</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$current_driver</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;vfio-pci&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;🔄 Releasing vfio-pci binding...&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_GPU</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/vfio-pci/unbind &gt;/dev/null
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_AUDIO</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/vfio-pci/unbind &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  load_nvidia_headless
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="o">&amp;&amp;</span> nvidia-smi
</span></span><span class="line"><span class="cl">  print_status
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#  Main </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span> in
</span></span><span class="line"><span class="cl">on<span class="o">)</span> vm_mode <span class="p">;;</span>
</span></span><span class="line"><span class="cl">off<span class="o">)</span> host_mode <span class="p">;;</span>
</span></span><span class="line"><span class="cl">status<span class="o">)</span> print_status <span class="p">;;</span>
</span></span><span class="line"><span class="cl">*<span class="o">)</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Usage: </span><span class="nv">$0</span><span class="s2"> [on|off|status]&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;  on     - Prepare GPU for VM (may require nvidia-unlock)&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;  off    - Enable NVIDIA for host (headless mode)&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;  status - Show current GPU status&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">  <span class="p">;;</span>
</span></span><span class="line"><span class="cl"><span class="k">esac</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>To run it it&rsquo;s the same thing: <code>nvidia-locker off</code> will lift the vfio-pci block and load the Nvidia drivers when I want to run an AI LLM natively on my Linux or render something in Blender with Cycles.</p>
<p>After I&rsquo;m done, and I want to run Windows in the VM. Ctrl-Alt-F3, fresh login on the plain text terminal, and <code>nvidia-locker on</code>. This will kill GDM, unload the NVIDIA drivers, reload the vfio-pci block and restart GDM so I can log back into graphical mode. And from there I can launch via the <code>virsh start</code> command or using the Virtual Machine Manager GUI if I want to use Display Spice, for example.</p>
<p>And that&rsquo;s it, the article is more so I don&rsquo;t forget what I did, to use as a reference in the future.</p>
]]></content:encoded><category>linux</category><category>hardware</category></item><item><title>BIOS Configuration of My PC - X670E Aorus Xtreme</title><link>https://akitaonrails.github.io/en/2025/04/17/bios-config-my-pc-x670e-aorus-xtreme/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/17/bios-config-my-pc-x670e-aorus-xtreme/</guid><pubDate>Thu, 17 Apr 2025 18:50:00 -0300</pubDate><description>&lt;p&gt;This is a note to myself, so I don&amp;rsquo;t forget, and maybe it helps someone else.&lt;/p&gt;
&lt;p&gt;I know every motherboard ships pre-configured with safe and conservative &amp;ldquo;defaults&amp;rdquo;, the most stable rather than the most performant. So if you don&amp;rsquo;t go into the BIOS to tweak things, you&amp;rsquo;re leaving performance on the table.&lt;/p&gt;
&lt;p&gt;I have an AMD Ryzen 9 7950X3D CPU. The processor is a package that works with two &amp;ldquo;dies&amp;rdquo;, I&amp;rsquo;m not sure if &amp;ldquo;chip&amp;rdquo; is a way to visualize it. Each one has 8 cores, totaling 16 cores, and each core has 2 threads, so 32 threads possible in parallel.&lt;/p&gt;</description><content:encoded><![CDATA[<p>This is a note to myself, so I don&rsquo;t forget, and maybe it helps someone else.</p>
<p>I know every motherboard ships pre-configured with safe and conservative &ldquo;defaults&rdquo;, the most stable rather than the most performant. So if you don&rsquo;t go into the BIOS to tweak things, you&rsquo;re leaving performance on the table.</p>
<p>I have an AMD Ryzen 9 7950X3D CPU. The processor is a package that works with two &ldquo;dies&rdquo;, I&rsquo;m not sure if &ldquo;chip&rdquo; is a way to visualize it. Each one has 8 cores, totaling 16 cores, and each core has 2 threads, so 32 threads possible in parallel.</p>
<p>But there&rsquo;s a difference: each die has different L3 caches. One of the dies has only 32MB and the other has 96MB, so heavier programs benefit from running on the cores of the die with more L3, and you can configure that.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/gcbhrzj8wuc38z3lonisck0ooeck" alt="lstopo"  loading="lazy" /></p>
<p>Besides that, it has a base clock of about 3GHz with the ability to turbo boost up to 5.7GHz. They can&rsquo;t all run at that speed or it heats up too fast and may &ldquo;thermal throttle&rdquo;, and then it drops everything below 3GHz. That&rsquo;s why, especially in laptops that don&rsquo;t have adequate thermal capacity, the best option is not overclocking but &ldquo;undervolting&rdquo; (cutting power so it runs less fast). Because it&rsquo;s better for everyone to be a bit less fast than for everyone to be super fast and immediately overheat and slow everyone down. It&rsquo;s complex.</p>
<p>To complicate things further, I have 96GB of DDR5 6000 RAM. Capable of a theoretical maximum of 6GHz, but that&rsquo;s theoretical because everything can desync and cause instabilities up to a kernel panic crash. That&rsquo;s why the motherboard default is the base speed of about 4GHz and the CPU also maxing out at around 4GHz.</p>
<p>But you can push that up a bit without making it unstable. In short, here&rsquo;s what I did:</p>
<h2>PBO - Precision Boost Overdrive<span class="hx:absolute hx:-mt-20" id="pbo---precision-boost-overdrive"></span>
    <a href="#pbo---precision-boost-overdrive" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Ryzen CPUs have PBO to turbo boost the core clocks. It usually ships either disabled or in a conservative configuration, so your 5.7GHz CPU may be running at only 4GHz or less and never delivering its maximum.</p>
<p>To adjust this, if you want to get advanced, you need to tweak parameters like PPT (Package Power Tracking) to 200W or more. TDC (Thermal Design Current) to 160A or more. EDC (Electrical Design Current) to 200A or more. And you can set up a curve optimizer, per core, to negative (-10 or -20) to reduce voltage, which can allow higher boost clocks.</p>
<p>But this is very advanced, and you need to know a lot more about electrical stuff before
trying it. Better not risk it and not mess with this. On the screen below, don&rsquo;t leave it on Advanced, leave it on Auto:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/vxeurat4imakwzdl6artkzuqymmq" alt="PBO Advanced"  loading="lazy" /></p>
<p>Instead, it&rsquo;s better to mess with PBO Enhancement Presets, which is this screen below:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a1vgigv3ka64xs6nclq9z2hh21w7" alt="PBO Presets"  loading="lazy" /></p>
<p><strong>70/80/90</strong> refers to the maximum temperature we allow the cores to reach. This will depend on whether you have a good cooling solution. Whether it&rsquo;s an AIO with liquid cooling or a <a href="https://noctua.at/en/nh-d15-g2"target="_blank" rel="noopener"><strong>Noctua NH-D15 G2</strong></a>, which is what I use. Noctua fans are extremely silent, I almost never hear them, and they have less maintenance and lower failure potential than liquid cooling. I prefer fewer moving parts to be safe.</p>
<p>[<img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4d10atf2k9rb5pntb9eec5uzqk7u" alt="Noctua NH-D15 G2"  loading="lazy" /></p>
<p>Now those <strong>Level 1 to 5</strong> are the PPT/TDC/EDC tunings I mentioned, but already pre-defined and tested by the manufacturer. 1 is less aggressive, the most stable, while 5 is the most aggressive and can be unstable. The right approach is to start at <strong>90 level 5</strong> and decrease until you find the most stable for your system. This will vary; for instance, the more RAM you have, the less aggressive it seems you can be, especially the faster that RAM is.</p>
<p>In my case, right at the boot of my Manjaro Linux, it gave a kernel panic, which looks something like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/f25kqct9urmereleunhurknw40d4" alt="kernel panic"  loading="lazy" /></p>
<p>So I went back into the BIOS and tested different levels and, for my configuration, I could only get up to <strong>90 Level 2</strong>. This has been stable and so far I haven&rsquo;t had any random crashes.</p>
<p>Beyond that, recently (early 2025) motherboard manufacturers released firmware updates that opened access to a feature called <strong>X3D Turbo Mode</strong>. If you see this, don&rsquo;t think it&rsquo;s free extra performance. In reality, unless you use the machine more for games, you&rsquo;ll actually feel it got slower.</p>
<p>As I explained before, Ryzen 9 has 1 CCD (core complex), which is the die with 96MB of L3 cache (V-Cache). Turbo mode tries to force more programs onto the CCD that does <strong>NOT</strong> have V-Cache (the 32MB one), which can therefore reach a higher clock boost but has more latency. This is good for games, which usually don&rsquo;t benefit from having more cache but rather from more clock.</p>
<p>But at the same time, programs that need V-Cache will end up landing more often on the Core with <strong>LESS</strong> L3 cache and will then perform worse than before. Examples of that are 3D modeling programs like Blender, exactly what I want to use more now. So test it, but in principle you can leave it off.</p>
<p>This option in my firmware is called &ldquo;X3D Turbo Mode&rdquo; but elsewhere it may be &ldquo;3D V-Cache Performance Optimizer&rdquo;, same thing.</p>
<h2>XMP/EXPO RAM<span class="hx:absolute hx:-mt-20" id="xmpexpo-ram"></span>
    <a href="#xmpexpo-ram" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Now, DDR5 RAM boots at the base clock of about 4.8GHz. But depending on the RAM model, it can go much higher, in my case up to 6GHz. All modern memory also has several settings like frequency, latency, voltage, and more. Again, it&rsquo;s not worth trying to set these by hand. Better to use predefined profiles. Intel calls this support <strong>XMP</strong> and AMD calls it <strong>EXPO</strong>.</p>
<p>I forgot to take a photo of my BIOS, but this slightly older photo from Google Images is the same thing. XMP/EXPO ships disabled (conservative) but you just pick the profile that already exists:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ypwo0rsbr1bmu6w6ujm7b1qyjlof" alt="XMP/EXPO Profile"  loading="lazy" /></p>
<p>Note that just below there are things like <strong>Low Latency Support</strong> and <strong>High Bandwidth Support</strong>. Theoretically to reduce latency and increase transfer bandwidth. All of which sounds great, but in my case it doesn&rsquo;t work. If I try to enable both, my system doesn&rsquo;t boot. I was forced to clear the CMOS to reset the BIOS without that.</p>
<p>The problem: possibly too much memory (96GB). Maybe if you have less, around 32GB, this works, but it seems the more you have, the more unstable it gets if you try to go too fast.</p>
<p>Another problem, on DDR5, the more memory, the slower the Cold Boot, the initial boot when the PC was off. The BIOS has to do a check on the memory to ensure those parameters, response time and so on. And this happens on <strong>EVERY</strong> boot. It can take a few minutes and some people might even think the machine is frozen, but it&rsquo;s just that check.</p>
<p>In the BIOS there&rsquo;s an option called <strong>Memory Context Restore</strong> where it uses the parameters it recorded the last time it did that check. But I don&rsquo;t recommend it; for me it doesn&rsquo;t even boot anymore after enabling it. Theoretically it would cut down the check time and reuse old data, but that data may not match (the values can fluctuate) and that makes the whole system unstable. Better to wait an extra minute on boot than to live with an unstable system.</p>
<p>In advanced memory settings there&rsquo;s one more thing we should change: <strong>DF C-States</strong>, which needs to be <strong>DISABLED</strong>. In theory this feature is to reduce the latency between CPU and memory. But on systems with a lot of RAM (96GB or more like mine) this can also cause instability for not much noticeable gain. I prefer staying stable in this case.</p>
<p>Another setting to change is <strong>Power Supply Idle Control</strong>, which has to be set to &ldquo;Typical Current Idle&rdquo;, and so far (a firmware fix update may come), it&rsquo;s mandatory for Ryzen processors (even AM5). This prevents the &ldquo;Deep Power State&rdquo; sleep state, which can cause random kernel panics.</p>
<p>Same thing for <strong>Global C-State Control</strong>, which also needs to be disabled for the same reason: avoid &ldquo;deep sleep states&rdquo; (C6/C10) when the CPU sits in &ldquo;idle&rdquo; (doing nothing) for too long. These features exist to save energy, but it seems AMD has bugs when the CPU is forced into that state and when it wakes up, it comes back unstable or crashing. So it&rsquo;ll consume more energy when doing nothing (around less than 20W, rather than near zero), but at least it won&rsquo;t be unstable. If you want to save energy, the better option is to shut down and actually cut the power when not using it.</p>
<h2>BIOS Summary (for me)<span class="hx:absolute hx:-mt-20" id="bios-summary-for-me"></span>
    <a href="#bios-summary-for-me" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>PBO Enhancement: <strong>90 Level 2</strong> (clock boost slightly more aggressive than the default)</li>
<li>Power Supply Idle Control: <strong>Typical Current Idle</strong> (prevents the idle crash bug)</li>
<li>Global C-State Control: <strong>Disabled</strong> (avoids deep sleep, which causes instability)</li>
<li>DF C-States: <strong>Disabled</strong> (also avoids sleep, improving Infinity Fabric latency)</li>
<li>XMP/EXPO: <strong>Profile XMP1</strong> if you have it (raises the memory clock)</li>
</ul>
<p>There are 2 important things, especially if you&rsquo;re using discrete GPUs:</p>
<ul>
<li>Above 4G Decoding and Re-Size Bar Support. In my case both are on by default; if yours are off, turn them on.</li>
</ul>
<p>On Linux, you can check if it&rsquo;s enabled. Run the following:</p>
<p><code>sudo lspci -vv | grep -A 15 VGA</code></p>
<p>You should see &ldquo;Resizable BAR&rdquo; like this:</p>
<p><code>Capabilities: [XX] PCI Express, ... Resizable BAR: Supported (Enabled)</code></p>
<p>If it doesn&rsquo;t show up, go to the BIOS and look for it to enable. Without this you&rsquo;re not using the maximum of your video card. I think on iGPUs (which are GPUs already built into the CPU) this option doesn&rsquo;t exist.</p>
<h2>Linux-Zen Kernel<span class="hx:absolute hx:-mt-20" id="linux-zen-kernel"></span>
    <a href="#linux-zen-kernel" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;ve always used the most stable LTS default kernel, currently Linux66, if I&rsquo;m not mistaken. But I remembered I can use the Zen kernel, which is the same default kernel Arch uses with performance tunings to improve latency, especially in multi-thread. It switches to the CFS scheduler with improvements. Theoretically, it improves overall system responsiveness. I think it&rsquo;s worth using. On Arch/Manjaro it&rsquo;s like this:</p>
<p><code>yay -S linux-zen-versioned-bin linux-zen-versioned-headers-bin</code></p>
<p>This will uninstall the old kernel and, if you&rsquo;re using NVIDIA GPUs, it&rsquo;ll also uninstall the driver package. So you also need to install the following package:</p>
<p><code>yay -S nvidia-dkms</code></p>
<p>And then, with everything installed, run the following command:</p>
<p><code>sudo dkms autoinstall</code></p>
<p>This will reinstall the nvidia driver binaries for the new Zen kernel. In Manjaro&rsquo;s case, you don&rsquo;t need to mess with GRUB because in my case it&rsquo;s set to <code>GRUB_DEFAULT=saved</code> in <code>/etc/default/grub</code>. That means when it reboots, it&rsquo;ll record which kernel I last chose, and on the next reboot, if I don&rsquo;t choose anything, it&rsquo;ll load again the last kernel I picked. I prefer it this way over hard-coding which kernel is the default.</p>
<p>Once that&rsquo;s done, on the next boot just check:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ uname -r
6.14.2-zen1-1-zen</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>Another thing is to check if <strong>CPPC</strong> is active. That&rsquo;s <strong>Collaborative Processor Performance Control</strong>, a feature that exposes to the OS kernel a ranking of cores (which has the highest boost, which has more cache, etc). Remember the X3D Turbo Mode I mentioned earlier? Same concept: to help the OS pick which core to send each program to so it runs better.</p>
<p>Not every motherboard firmware exposes this as an option; in the case of my Gigabyte X670E, it doesn&rsquo;t exist. That probably means it&rsquo;s already on by default. To check if it&rsquo;s on, just go to the Linux terminal and run:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>cat /sys/devices/system/cpu/cpu*/cpufreq/amd_pstate_highest_perf</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 it returns any result, it means it&rsquo;s on and the firmware is informing the kernel about the characteristics of each core, so the scheduler has more information to manage where to send each thread.</p>
<p>Another way to check:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ls /sys/devices/system/cpu/cpu0/cpufreq/</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 needs to return a list containing names like these:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>amd_pstate_highest_perf
amd_pstate_nominal_perf
amd_pstate_preferred_core</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>Which, again, is a sign that Linux is recognizing CPPC properly.</p>
<p>Manjaro itself, besides the stable kernels, has the &ldquo;linux-rt&rdquo; versions which are for real-time systems. Technically, this is meant not just for lower latency, but for stable and more predictable latencies. <strong>DO NOT USE THIS KIND OF KERNEL</strong>. This is for factory systems or things like that which need processing determinism and have no tolerance for random latency. It doesn&rsquo;t mean it&rsquo;ll run more responsively; it might even run slower, but the important thing is that it&rsquo;s a predictable slow.</p>
<p>Others might mention the <strong>Linux Liquorix</strong> kernel, which many compare to Zen. But while Zen is based on the same kernel Arch uses, Liquorix&rsquo;s is based on the Debian/Ubuntu version. So it&rsquo;s not recommended on Arch/Manjaro. On top of that it&rsquo;s more experimental and theoretically less stable too. Use it if you&rsquo;re on Ubuntu or derivatives. On Arch, better stick with Zen.</p>
<h2>check_ryzen_perf.sh<span class="hx:absolute hx:-mt-20" id="check_ryzen_perfsh"></span>
    <a href="#check_ryzen_perfsh" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>If you have a Ryzen system, copy the following script and run it on your machine:</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">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🔍 Verifying Ryzen 7950X3D Tuning on Linux&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ CPU Driver: &#34;</span>
</span></span><span class="line"><span class="cl">grep . /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ EPP Mode: &#34;</span>
</span></span><span class="line"><span class="cl">cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ CPPC Detected: &#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -f /sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_highest_perf <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Yes&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;No&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ Preferred Core Ranking: &#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -f /sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_prefcore_ranking <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Yes&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;No&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ Running Kernel: &#34;</span>
</span></span><span class="line"><span class="cl">uname -r
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -e <span class="s2">&#34;\n✅ All key Linux-side Ryzen tuning options appear active.\n&#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>On my machine it now produces this result:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>🔍 Verifying Ryzen 7950X3D Tuning on Linux
✅ CPU Driver: amd-pstate-epp
✅ EPP Mode: performance
✅ CPPC Detected: Yes
✅ Preferred Core Ranking: Yes
✅ Running Kernel: 6.14.2-zen1-1-zen

✅ All key Linux-side Ryzen tuning options appear active.</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>Saying that, theoretically, all the performance bits seem to be active and working. If one of them isn&rsquo;t, ask ChatGPT what to do for your setup.</p>
<h2>Checking the BIOS Version<span class="hx:absolute hx:-mt-20" id="checking-the-bios-version"></span>
    <a href="#checking-the-bios-version" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Many motherboard manufacturers offer software, for Windows, to automatically download and update the firmware. But in Gigabyte&rsquo;s case, there&rsquo;s no Linux support. To check which version is currently installed, without having to enter the BIOS, on Linux there&rsquo;s this command:</p>
<p><code>sudo dmidecode -s bios-version</code></p>
<p>In my case, it now returns F33 (March 2025 version). Now, to compare whether that&rsquo;s the newest version, I just have to manually go to Gigabyte&rsquo;s crappy site, which in my case is <a href="https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support"target="_blank" rel="noopener">this link</a>. For your specific model, and manufacturer of course, this link will vary.</p>
<p><a href="https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-1x/support#support-dl"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/72l1if05q22u1ogowfzws9rtz95j" alt="x670e support page"  loading="lazy" /></a></p>
<p>To make it worse, it seems the content is loaded asynchronously by javascript, so if you try to use <code>wget</code> or <code>curl</code> from the command line, the content won&rsquo;t have loaded yet.</p>
<p>I decided to write a dumb script to solve that. First, in case you don&rsquo;t use <code>mise</code> like I do (see my Github project <a href="https://github.com/akitaonrails/omakub-mj"target="_blank" rel="noopener">Omakub-MJ</a> for a setup just like mine), install Node manually like this:</p>
<p><code>sudo pacman -S nodejs npm</code></p>
<p>Create some directory for the project and from there do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>npm init -y
npm install -g playwright
npx playwright install</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>Create a file <code>check_bios_playwright.js</code> with this content:</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-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">chromium</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;playwright&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">browser</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">chromium</span><span class="p">.</span><span class="nx">launch</span><span class="p">({</span> <span class="nx">headless</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newPage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Load support page
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s1">&#39;https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">waitUntil</span><span class="o">:</span> <span class="s1">&#39;domcontentloaded&#39;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Click the BIOS tab manually (it triggers JS to fetch content)
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="s1">&#39;id=bios-count&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Wait for BIOS version element to load
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForSelector</span><span class="p">(</span><span class="s1">&#39;.div-table-body-BIOS .download-version&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">timeout</span><span class="o">:</span> <span class="mi">60000</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Grab the first BIOS version listed
</span></span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">biosVersion</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">$eval</span><span class="p">(</span><span class="s1">&#39;.div-table-body-BIOS .download-version&#39;</span><span class="p">,</span> <span class="nx">el</span> <span class="p">=&gt;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">innerText</span><span class="p">.</span><span class="nx">trim</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;🌐 Latest BIOS Version on Gigabyte site:&#39;</span><span class="p">,</span> <span class="nx">biosVersion</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><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>Now create a bash script <code>check_bios_update.sh</code> with this content:</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">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get directory of this script (absolute path)</span>
</span></span><span class="line"><span class="cl"><span class="nv">SCRIPT_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASH_SOURCE</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get current BIOS version</span>
</span></span><span class="line"><span class="cl"><span class="nv">CURRENT</span><span class="o">=</span><span class="k">$(</span>sudo dmidecode -s bios-version <span class="p">|</span> tr -d <span class="s1">&#39;[:space:]&#39;</span> <span class="p">|</span> tr <span class="s1">&#39;[:lower:]&#39;</span> <span class="s1">&#39;[:upper:]&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run the Playwright JS script in the same directory</span>
</span></span><span class="line"><span class="cl"><span class="nv">LATEST</span><span class="o">=</span><span class="k">$(</span>node <span class="s2">&#34;</span><span class="nv">$SCRIPT_DIR</span><span class="s2">/check_bios_playwright.js&#34;</span> <span class="p">|</span> grep -o <span class="s1">&#39;F[0-9]\+&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Output</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🖥  Current BIOS: </span><span class="nv">$CURRENT</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🌐 Latest BIOS:  </span><span class="nv">$LATEST</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$CURRENT</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;✅ You are up to date.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;⬆  BIOS update available!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</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>Do a <code>chmod +x</code> to make it executable and, if all goes well, you&rsquo;ll get a response like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[sudo] password for akitaonrails:
🖥  Current BIOS: F33
🌐 Latest BIOS:  F33
✅ You are up to date.</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 a cannon shot to kill a fly, but this will use headless Chromium to pull the page, load the javascript, and then use CSS selectors to find the BIOS version, compare it to the one on your machine, and see if it&rsquo;s up to date.</p>
<p>If it isn&rsquo;t, there&rsquo;s no way around it: you have to manually go to the site, download the ZIP file, extract it onto a pen drive, and boot into the BIOS to update manually. But at least I can quickly check from time to time with one command line now.</p>
<h2>Automatic BIOS Version Check<span class="hx:absolute hx:-mt-20" id="automatic-bios-version-check"></span>
    <a href="#automatic-bios-version-check" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I decided I don&rsquo;t want to remember to run that script manually, so the best thing is to create a user systemd service and check automatically every week. If there&rsquo;s a new version, create a notification to GNOME so I can see it visually and automatically open the download page for me.</p>
<p>First, I need to give my user access to the dmidecode command to check my current BIOS version without having to type a password with <code>sudo</code>. To do that, let&rsquo;s create a new sudoers rule like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo visudo -f /etc/sudoers.d/bios-check</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;ll open an empty file where I fill in like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo /usr/sbin/dmidecode -s bios-version</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 don&rsquo;t forget to set the correct permissions on this file:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo chmod 0440 /etc/sudoers.d/bios-check</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 that it won&rsquo;t ask for a password to run that command anymore, since we&rsquo;re going to put it in a background service running automatically. Obviously it&rsquo;s not safe to allow running <code>sudo</code> without a password, so we restrict it exclusively to allow it only on that specific binary and nothing else.</p>
<p>In the same directory as the other files with playwright installed and so on, I create a <code>bios-update-check.sh</code> with this content:</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">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Path to your Playwright BIOS scraper script</span>
</span></span><span class="line"><span class="cl"><span class="nv">JS_CHECKER</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/bios-check/check_bios_playwright.js&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">NODE_BIN</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>which node<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># URL of your motherboard&#39;s BIOS support page</span>
</span></span><span class="line"><span class="cl"><span class="nv">URL</span><span class="o">=</span><span class="s2">&#34;https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support#support-dl-bios&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get current BIOS version</span>
</span></span><span class="line"><span class="cl"><span class="nv">CURRENT</span><span class="o">=</span><span class="k">$(</span>sudo dmidecode -s bios-version <span class="p">|</span> tr -d <span class="s1">&#39;[:space:]&#39;</span> <span class="p">|</span> tr <span class="s1">&#39;[:lower:]&#39;</span> <span class="s1">&#39;[:upper:]&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get latest BIOS version from Gigabyte site</span>
</span></span><span class="line"><span class="cl"><span class="nv">LATEST</span><span class="o">=</span><span class="k">$(</span><span class="nv">$NODE_BIN</span> <span class="s2">&#34;</span><span class="nv">$JS_CHECKER</span><span class="s2">&#34;</span> <span class="p">|</span> grep -o <span class="s1">&#39;F[0-9]\+&#39;</span> <span class="p">|</span> head -1<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> -z <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  notify-send <span class="s2">&#34;BIOS Check&#34;</span> <span class="s2">&#34;⚠️ Failed to fetch latest BIOS version from Gigabyte&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$CURRENT</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  notify-send -u normal <span class="s2">&#34;🔔 BIOS Update Available!&#34;</span> <span class="s2">&#34;Current: </span><span class="nv">$CURRENT</span><span class="s2"> → Latest: </span><span class="nv">$LATEST</span><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Opening the download page...&#34;</span> --app-name<span class="o">=</span><span class="s2">&#34;BIOS Checker&#34;</span>
</span></span><span class="line"><span class="cl">  xdg-open <span class="s2">&#34;</span><span class="nv">$URL</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</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>It&rsquo;s the same as the previous script but now using <code>notify-send</code> to send messages to Gnome (in KDE it&rsquo;s different) and using <code>xdg-open</code> to open the URL in the system&rsquo;s default browser. Configure differently for your system. Don&rsquo;t forget to make it executable and test:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>chmod &#43;x ~/.local/bin/bios-update-check.sh</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 to create the systemd service for my user (different from creating one for the system that runs with more permissions. I only need this running at my user level), so I create the file <code>~/.config/systemd/user/bios-check.service</code> with the following content:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Unit]
Description=Weekly BIOS version checker

[Service]
Type=oneshot
ExecStart=/home/akitaonrails/.local/bin/bios-check/bios-update-check.sh</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 the timer service to run every week, called <code>~/.config/systemd/user/bios-check.timer</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Unit]
Description=Run BIOS version check weekly

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=default.target</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 enable the timer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>systemctl --user daemon-reexec
systemctl --user daemon-reload
systemctl --user enable --now bios-check.timer</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>By Systemd convention, a timer [blabla].timer will execute the service with the same name [blabla].service. That&rsquo;s why you need both. If you enable the service directly, it&rsquo;ll run only once and exit. To know if it&rsquo;s enabled correctly do the following:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>✦ ❯ systemctl --user list-timers

NEXT                          LEFT LAST PASSED UNIT             ACTIVATES
Mon 2025-04-21 00:00:00 -03 2 days -         - bios-check.timer bios-check.service

1 timers listed.
Pass --all to see loaded but inactive timers, too.</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 how you can automate some things on your system and get more out of your Gnome.</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>I only started this journey this time because it occurred to me that it had been months since I&rsquo;d last updated the motherboard firmware, and then I took the chance to tune some things I hadn&rsquo;t done before. Things like PBO were on automatic (so I was leaving performance behind; I remember that last time it had been unstable but I didn&rsquo;t dig in to find out why), things like XMP/EXPO were already on. But now I think I really enabled everything I could.</p>
<p>It had also been years since I tried out the Linux-Zen kernel, so I took the chance to test it now. And as I said, so far it&rsquo;s stable. Since I&rsquo;ve started learning Blender and pulling more CPU and memory, I figured it was worth spending time trying to extract the most from my machine. 3D modeling is totally CPU/RAM, more than GPU.</p>
<p>Oh yes, speaking of Blender, one last thing. Remember my Ryzen has 1 CCD, which is that 1 die with 96MB of L3 cache. The best thing to do is create affinity for Blender with that die and prevent it from landing on the core with only 32MB of L3 cache.</p>
<p>On Linux, to do that, here&rsquo;s the command line:</p>
<p><code>taskset -c 0-15 blender-4.3 &amp;</code></p>
<p>The taskset command will hint to the scheduler that blender should stay on threads 0 to 15 (where the V-Cache is) and not 16 to 31 (which only have 32MB). That way, in theory, I&rsquo;m giving Blender the best chances to run at the best possible performance on this machine. Less important programs like the browser, the terminal, if they land on the 32MB core, no problem.</p>
<p>I do the same thing in libvirt/qemu when I run emulated Windows: I configure libvirt to bring up the VM tied to the 96MB CCD and leave the other core for the Linux host. Speaking of BIOS, by the way, to run VMs you have to enable SVM and IOMMU, which usually ship disabled because virtual machine support steals a bit of the CPU&rsquo;s performance. Maximum performance is with them <strong>disabled</strong>. If you&rsquo;re not going to use VMs, leave them off.</p>
<p>And if anyone noticed that I&rsquo;m explicitly running Blender 4.3 instead of the newer Blender 4.4, it&rsquo;s because some addons still don&rsquo;t work on the newer version, so it&rsquo;s better to stay one version back to keep more compatibility with addons that don&rsquo;t update fast.</p>
]]></content:encoded><category>hardware</category><category>performance</category><category>linux</category></item><item><title>Running Arch Linux over Windows 10!</title><link>https://akitaonrails.github.io/en/2018/04/29/running-arch-linux-over-windows-10/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2018/04/29/running-arch-linux-over-windows-10/</guid><pubDate>Sun, 29 Apr 2018 22:06:00 -0300</pubDate><description>&lt;p&gt;Ok, anyone that has been reading my blog for the past year or two knows how I &lt;a href="http://www.akitaonrails.com/archlinux"target="_blank" rel="noopener"&gt;enjoy Arch Linux&lt;/a&gt; (in particular Manjaro Gnome).&lt;/p&gt;
&lt;p&gt;I am also still very much intrigued by the idea of Windows Subsystem for Linux (or WSL) which properly debuted in the &lt;a href="http://www.akitaonrails.com/en/2016/07/26/o-ano-do-linux-no-desktop-esta-usavel"target="_blank" rel="noopener"&gt;Windows 10 Anniversary Edition&lt;/a&gt; back in mid-2016.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s been almost 2 years and we&amp;rsquo;re expecting some significant updates to WSL compatibility and performance for the next &lt;a href="https://winaero.com/blog/command-line-wsl-improvements-windows-10-version-1803/"target="_blank" rel="noopener"&gt;Spring Creators Update&lt;/a&gt; (or version 1803) scheduled to arrive on April 30, 2018. This brings it closer to making WSL actually usable for professional programmers. Just to give you an idea, right now you will have orders of magnitude better performance if you run your favorite Linux distro inside Virtualbox or VMWare Workstation.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Ok, anyone that has been reading my blog for the past year or two knows how I <a href="http://www.akitaonrails.com/archlinux"target="_blank" rel="noopener">enjoy Arch Linux</a> (in particular Manjaro Gnome).</p>
<p>I am also still very much intrigued by the idea of Windows Subsystem for Linux (or WSL) which properly debuted in the <a href="http://www.akitaonrails.com/en/2016/07/26/o-ano-do-linux-no-desktop-esta-usavel"target="_blank" rel="noopener">Windows 10 Anniversary Edition</a> back in mid-2016.</p>
<p>It&rsquo;s been almost 2 years and we&rsquo;re expecting some significant updates to WSL compatibility and performance for the next <a href="https://winaero.com/blog/command-line-wsl-improvements-windows-10-version-1803/"target="_blank" rel="noopener">Spring Creators Update</a> (or version 1803) scheduled to arrive on April 30, 2018. This brings it closer to making WSL actually usable for professional programmers. Just to give you an idea, right now you will have orders of magnitude better performance if you run your favorite Linux distro inside Virtualbox or VMWare Workstation.</p>
<p>Moreover, out of the box the &ldquo;Bash for Windows&rdquo; proper only officially supports Ubuntu, Fedora, and OpenSuse I guess. Most people will just install Ubuntu. And it works ok. For toy and cool demonstration purposes, it works. But it really frustrates me that I can do so much but I can&rsquo;t actually use it as my daily driver. It&rsquo;s like being able to carry the fully built prototype of the next iPhone but no 4G support yet. So it&rsquo;s just a toy.</p>
<p>Bear with me here, if you just want to see how the performance compares to my VMWare setup, go straight to the end of the article.</p>
<p>Anyway, in the case of distros. Serious Linux users would prefer better alternatives. Enter this GitHub repository:</p>
<p><a href="https://github.com/yuk7/ArchWSL"target="_blank" rel="noopener"><img src="http://web.archive.org/web/20221226012212/https://raw.githubusercontent.com/wiki/yuk7/WSL-DistroLauncher/img/Arch_Alpine_Ubuntu.png" alt="screenfetch of arch"  loading="lazy" /></a></p>
<p>You just download <a href="https://github.com/yuk7/ArchWSL/releases/latest"target="_blank" rel="noopener">this zip file</a>, unzip it and run the included <code>Arch.exe</code>. And that&rsquo;s about it!</p>
<p>Now, you can go ahead and open the Windows version of &ldquo;Bash&rdquo; and we can bootstrap the rest of the installation setup from there.</p>
<p>We must get Pacman and some AUR manager. To start Pacman, we must do this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>pacman-key --init
pacman-key --populate
pacman -Syu</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>Hell yeah, Pacman running natively on Windows!! Who would&rsquo;ve known??</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/674/big_IMG_20180429_174518.jpg" alt="pacman on windows"  loading="lazy" /></p>
<p>This will bring everything up to date. Next up, we must create a non-root user:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>useradd -m your_user
passwd your_user
EDITOR=nano visudo</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>Look for the line that says <code>root   ALL=(ALL) ALL</code> and add your user right on the next line like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>your_user   ALL=(ALL) 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>Finally, we should login as this user:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>su your_user</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>Next up, let&rsquo;s install Yaourt so we can use it to install Pacaur (this is a personal preference, you can just use Yaourt. I think Yaourt prompts too much, that&rsquo;s why I prefer Pacaur).</p>
<p>Start by editing your <code>/etc/pacman.conf</code> file with your favorite editor like vim or nano.</p>
<p>Just add the following section to the very bottom of the file:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[archlinuxfr]
SigLevel = Never
Server = http://repo.archlinux.fr/$arch</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>Before we can proceed, we have one small problem. WSL still has no support for SYSV IPC. This is required for <a href="https://unix.stackexchange.com/questions/9714/what-is-the-need-for-fakeroot-command-in-linux"target="_blank" rel="noopener">fakeroot</a>, which is required by <code>makepkg</code> (which cannot run as root).</p>
<p>To overcome this problem, this is what we&rsquo;ll have to do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>wget https://github.com/yuk7/arch-prebuilt/releases/download/17121600/fakeroot-tcp-1.22-1-x86_64.pkg.tar.xz
sudo pacman -U fakeroot-tcp-1.22-1-x86_64.pkg.tar.xz</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 for now! Until WSL adds proper support for SYSV IPC, we can use this. Yaourt itself will probably prompt you to install <code>fakeroot-tcp</code> on top of it, just let it.</p>
<p>As I said, Yaourt is quite verbose in its many prompts for confirmations. Rule of thumb, whenever it prompts if you want to edit anything, just say &ldquo;N&rdquo;(o). Whenever it prompts you if you want to build or install something, just say &ldquo;Y&rdquo;(es). If you got annoyed with me, do this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yaourt -S pacaur</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>From now on, I will assume you have Pacaur.</p>
<h3>Graphical User Interface Support<span class="hx:absolute hx:-mt-20" id="graphical-user-interface-support"></span>
    <a href="#graphical-user-interface-support" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Now, let&rsquo;s install some of the basics for a development environment:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -S base base-devel gvim git</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 will prompt you sometimes &ldquo;default: all&rdquo;, just install everything. Hard disk space should not be a concern in 2018.</p>
<p>I prefer to have a full-on GNOME 3 environment, so I just do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -S gnome</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>Again, let it install everything it wants. Everybody has a different taste when it comes to GUIs, some prefer KDE5, others prefer XFCE4. Some even go as far as using the tile-based GUI <a href="https://i3wm.org/"target="_blank" rel="noopener">i3</a>.</p>
<p>That requires some serious adapting. It&rsquo;s for people that like to do stuff like using a tenkeyless keyboard with nothing written in the keycaps and using an alien layout like  DVORAK or <a href="https://colemak.com/"target="_blank" rel="noopener">Colemak</a> (yikes!). Taste is taste. And I dare you to become a touch typist using <a href="https://www.maltron.com/the-maltron-letter-layout-advantage.html"target="_blank" rel="noopener">Maltron</a>, I double dare ya! :-D</p>
<p>But, I digress. Now, you can add this to your <code>/etc/.bashrc</code> file or the global <code>/etc/environment</code> file:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>echo &#34;export LIBGL_ALWAYS_INDIRECT=1&#34; &gt;&gt; /etc/.bashrc
echo &#34;export DISPLAY=:0&#34; &gt;&gt; /etc/.bashrc</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>Export those to your current shell, or just close and reopen Bash.</p>
<p>We can have a full GUI environment by installing a local X Server. You have 2 options, Xming and VcXSrv. I am having trouble resizing windows in Xming, so I am recommending <a href="https://sourceforge.net/projects/vcxsrv/"target="_blank" rel="noopener">VcXSrv</a> instead. Just download and install it as you would with any Windows application and start it up. It will add a Desktop shortcut named &ldquo;XLaunch&rdquo;, just fire it up.</p>
<p>And finally, we can install a <strong>PROPER</strong> usable terminal instead of that poor contraption derived from old cmd.exe that WSL provides you out of the box.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>pacaur -S tilix-bin
tilix</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;ve been a Mac user, you probably know iTerm2, which is possibly the best Terminal emulator I&rsquo;ve ever used. There is nothing else that comes close. Nothing, except possibly <a href="https://gnunn1.github.io/tilix-web/"target="_blank" rel="noopener">Tilix</a> (previously known as Terminix).</p>
<p>Just for having &ldquo;Ctrl-Alt-D&rdquo; to split the terminal horizontally down and &ldquo;Ctrl-Alt-R&rdquo; to split it vertically to the right, and using &ldquo;Alt-1&rdquo;, &ldquo;Alt-2&rdquo;, and so on to switch between the terminals, it&rsquo;s great for me. It also comes pre-installed with very nice themes such as the now classic Solarized Dark or my current favorite Monokai Dark.</p>
<p>Don&rsquo;t curse me, I know. Some will prefer the default terminal app and just add TMUX. Again, taste (it&rsquo;s more useful if I were working on a remote server).</p>
<p>Add the Hack monotype font to make everything more pleasing to the eye:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -S ttf-hack</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>
<h3>Programming Languages<span class="hx:absolute hx:-mt-20" id="programming-languages"></span>
    <a href="#programming-languages" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Let&rsquo;s see if the most important languages out there install correctly. As I always say, look no further than ASDF for that task.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.4.3
echo -e &#39;\n. $HOME/.asdf/asdf.sh&#39; &gt;&gt; ~/.bashrc
echo -e &#39;\n. $HOME/.asdf/completions/asdf.bash&#39; &gt;&gt; ~/.bashrc</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>Restart your terminal and continue:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>asdf plugin-add nodejs
addf plugin-add ruby</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>For Node.js you must add the proper GPG keys and only then you can install:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>bash ~/.asdf/plugins/nodejs/bin/import-release-team-keyring
asdf install nodejs 10.0.0
asdf global nodejs 10.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>Ruby benefits from jemalloc to allocate less system memory. I have no idea if it impacts WSL but let&rsquo;s do it anyway:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -S jemalloc
RUBY_EXTRA_CONFIGURE_OPTIONS=&#34;--with-jemalloc&#34; asdf install ruby 2.5.1
asdf global ruby 2.5.1
gem install bundler</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://akitaonrails.s3.amazonaws.com/assets/image_asset/image/675/big_IMG_20180429_193432.jpg" alt="ruby compiles"  loading="lazy" /></p>
<p>So far so good, if you need to install an old Ruby (&lt; 2.4) for legacy projects, you must do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>pacaur -S openssl-1.0
PKG_CONFIG_PATH=/usr/lib/openssl-1.0/pkgconfig RUBY_EXTRA_CONFIGURE_OPTIONS=&#34;--with-openssl-dir=/usr/lib/openssl-1.0&#34; asdf install ruby 2.3.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>Unfortunately it needs a version of OpenSSL that you should not be using due to security problems. We all moved on to 1.1. But Ruby 2.3.x reached its EOL (end of line). So it&rsquo;s at your own risk to keep using it.</p>
<p>Well, so far so good. Can we install Go as well? Let&rsquo;s see:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>asdf plugin-add golang
asdf install golang 1.9.5
asdf global goland 1.9.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>We can keep going, but I think you got the idea. It just works!</p>
<h3>Background Jobs<span class="hx:absolute hx:-mt-20" id="background-jobs"></span>
    <a href="#background-jobs" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Again, another current limitation of WSL (that we expect to see fixed on the Spring Creators Update) is the ability to start and keep running background jobs (daemons).</p>
<p>Just doing <code>sudo systemctl enable postgresql</code> and restarting won&rsquo;t automatically start Postgresql in the background as it should. This Arch.exe thing we&rsquo;re using won&rsquo;t even have systemd.</p>
<p>Fortunately, servers such as Postgresql or Redis do work normally, they can be started as user-level processes, and they do properly bind to their respective TCP ports. But, you will have to manually start them in every session. Close your Bash terminal and they are all killed off.</p>
<p>For example, Postgresql:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[your_user]# sudo pacman -S postgresql
[your_user]# sudo mkdir -p /run/postgresql
[your_user]# sudo chown -R postgres:postgres /run/postgresql

[your_user]# sudo -u postgres -i
[postgresql]# initdb --locale $LANG -E UTF8 -D &#39;/var/lib/postgres/data&#39;

[postgresql]# pg_ctl -D /var/lib/postgres/data start
waiting for server to start....2018-04-29 23:30:35.483 UTC [1064] LOG:  listening on IPv4 address &#34;127.0.0.1&#34;, port 5432
2018-04-29 23:30:35.505 UTC [1064] LOG:  listening on Unix socket &#34;/run/postgresql/.s.PGSQL.5432&#34;
2018-04-29 23:30:35.583 UTC [1065] LOG:  database system was shut down at 2018-04-29 23:23:48 UTC
2018-04-29 23:30:35.660 UTC [1064] LOG:  database system is ready to accept connections

[postgresql]# createuser --interactive
Enter name of role to add: your_user
Shall the new role be a superuser? (y/n) y

[postgresql]# exit
[your_user]#</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 what I did here? There is a small problem in the install under WSL where the <code>/run/postgresql</code> is not created with the correct permissions. Fixing that we can start the server. We can exit out of the <code>su</code> session and the postgresql server will keep running. But when we close the first Bash window/session, it will die.</p>
<p>And another very annoying thing? The <code>mkdir</code> and <code>chown</code> fixes above? I have to do it every single time I close the Bash for Windows app and start it again!! Super duper annoying if you ask me!</p>
<p>But, we can live with it for now. Hopefully, this will be fixed tomorrow (April 30) or in an upcoming upgrade.</p>
<p>In a Rails development environment, we can take advantage of <a href="https://github.com/theforeman/foreman"target="_blank" rel="noopener">Foreman</a> to help us out. Just edit some <code>Procfile.dev</code> file:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>web: bundle exec puma -C config/puma.rb -p 3000
db: su postgres -c &#39;pg_ctl start -D /var/lib/postgres/data&#39;
redis: redis-server /usr/local/etc/redis.conf
mailcatcher: mailcatcher -f</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 we can do: <code>foreman start -f Procfile.dev</code> and all services will start properly at once.</p>
<h3>Real World Performance<span class="hx:absolute hx:-mt-20" id="real-world-performance"></span>
    <a href="#real-world-performance" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>My daily driver is Manjaro GNOME under VMWare Workstation Pro on my Surface Book 2. I give it 6 threads out of the 8 and a hefty 12GB of RAM out of the total 16GB (damn Microsoft, we need more RAM nowadays that the cool kids deploy stupid ass Apps with heavy JS/CSS coating that eats up all available RAM).</p>
<p>In the project I am currently working on, my full RSpec suite is a bit on the slow side. For this comparison, I am skipping all my feature specs as I am in no mood to tweak chrome-driver to work in WSL (probably runs, but let&rsquo;s have it another day).</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 12 minutes 8 seconds (files took 14.58 seconds to load)
1493 examples, 0 failures, 35 pending

531,34s user 127,56s system 88% cpu 12:24,99 total</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>In this comparable WSL environment I have just installed, I am running the exact same RSpec suite, and this is what I get:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 13 minutes 43 seconds (files took 3 minutes 16.8 seconds to load)
1474 examples, 0 failures, 35 pending


455.80s user 399.14s system 83% cpu 17:04.96 total</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>For some reason, my WSL environment is skipping a few examples (seems more like an RSpec counter bug of some sort though), but I think this shouldn&rsquo;t pose too much of a difference in the grand scheme of things here. WSL is losing even if the counter is correct and it&rsquo;s running fewer examples.</p>
<p>When we start to really exercise the WSL a whole lot of issues start to show up. For example, PostgreSQL goes crazy and keeps stdout&rsquo;ing &ldquo;could not flush dirty data: Function not implemented&rdquo;, which is due to <a href="https://github.com/Microsoft/WSL/issues/645"target="_blank" rel="noopener">this documented issue</a> from the WSL! So what ends up happening is that sometimes the RSpec runner just pauses while PG tries to figure out what to do.</p>
<p>You can clearly see that under VMWare files took less than 15 seconds to load. On WSL they took over 3 minutes just on file I/O! It&rsquo;s orders of magnitude slower. This is the bad part. When it&rsquo;s just CPU vs CPU, they are pretty comparable actually.</p>
<p>On paper, the total time is actually quite close. And CPU-wise I think WSL has an edge, which is why it&rsquo;s able to recover in the totals even though the I/O is clearly sluggish.</p>
<p>In practice what happens in a normal programming situation is that you fire up Vim, or any other text editor of your choice. Any programmer has to keep opening files and going back and forth between a lot of files. Now, I/O being super slow, every file you try to open pauses your action for a split second. And this adds up.</p>
<p>Within VMWare, Vim is super responsive, whenever I need to open a file, it&rsquo;s almost instantaneous.</p>
<p>The benchmark may be hard to account for this situation. But in a normal scenario, you will end up annoyed. Moreover, running a GUI over X (VcXsrv in my case) slows things down a bit as well. From within VMWare, the normal Xorg can take advantage of being GPU accelerated. So the entire rasterization pipeline is faster.</p>
<p>You &ldquo;can&rdquo; use WSL as your daily driver. But if I can have a smoother experience from within VMWare, that&rsquo;s where I will be for now.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I will actually hold my conclusions until tomorrow (April 30) or later this week, because if the Spring Creators Update actually fixes some performance issues, then I can see myself using this instead of my VMWare Workstation install of Manjaro GNOME.</p>
<p>It may not come in this big update, but I have word that they are working on the outstanding I/O performance issues. So it&rsquo;s right around the corner.</p>
<p>Everything I need just works. Tilix, Vim, Git, Zsh, Ruby, Node.js. Everything installs as expected from Pacman and AUR. Pure Arch Linux glory! So sad the bad performance spoils it.</p>
<p>It&rsquo;s close, it&rsquo;s very close! For the first time in almost 15 years, I can almost see myself returning to 100% Windows (with no VMWare support) and still be able to be fully productive with real, professional, open source tools that &ldquo;Just Works&rdquo;.</p>
<p>Keep an eye on this blog post for updates this week!</p>
]]></content:encoded><category>linux</category><category>microsoft</category><category>developer-tools</category></item><item><title>Windows Subsystem for Linux is good, but not enough yet</title><link>https://akitaonrails.github.io/en/2017/09/20/windows-subsystem-for-linux-is-good-but-not-enough-yet/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2017/09/20/windows-subsystem-for-linux-is-good-but-not-enough-yet/</guid><pubDate>Wed, 20 Sep 2017 14:43:00 -0300</pubDate><description>&lt;p&gt;I wrote a premature post in July of 2016 titled &lt;a href="http://www.akitaonrails.com/en/2016/07/26/o-ano-do-linux-no-desktop-esta-usavel"target="_blank" rel="noopener"&gt;&amp;ldquo;The Year of Linux on the Desktop - It&amp;rsquo;s Usable!&amp;rdquo;&lt;/a&gt; and, indeed, it was (barely) usable.&lt;/p&gt;
&lt;p&gt;The real &amp;ldquo;Year of Linux on the Desktop&amp;rdquo; is still not here. It&amp;rsquo;s getting closer as I will explain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; this won&amp;rsquo;t be &amp;lsquo;yet-another-WSL-install-blog-post&amp;rsquo;, there are already several out there, for example &lt;a href="https://medium.com/xtrememl/why-how-to-use-windows-10-wsl-built-in-linux-for-machine-learning-6a225f4bbd3a"target="_blank" rel="noopener"&gt;this one&lt;/a&gt; or &lt;a href="https://www.neowin.net/news/bash-plus-windows-10-equals-linux-gui-apps-on-the-windows-desktop"target="_blank" rel="noopener"&gt;this other one&lt;/a&gt;. And you can always &lt;a href="http://lmgtfy.com/?q=windows&amp;#43;10&amp;#43;creator&amp;#43;update&amp;#43;install&amp;#43;WSL"target="_blank" rel="noopener"&gt;Google it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Back in early 2016 Microsoft announced and launched a very alpha version of their current Windows Subsystem for Linux (WSL). It booted and ran some software, but it was awfully buggy. Fair enough as it was still an Insider preview only (by the way, avoid Insider Preview builds, they BSOD all the time as it&amp;rsquo;s in active development).&lt;/p&gt;</description><content:encoded><![CDATA[<p>I wrote a premature post in July of 2016 titled <a href="http://www.akitaonrails.com/en/2016/07/26/o-ano-do-linux-no-desktop-esta-usavel"target="_blank" rel="noopener">&ldquo;The Year of Linux on the Desktop - It&rsquo;s Usable!&rdquo;</a> and, indeed, it was (barely) usable.</p>
<p>The real &ldquo;Year of Linux on the Desktop&rdquo; is still not here. It&rsquo;s getting closer as I will explain.</p>
<p><strong>TL;DR</strong> this won&rsquo;t be &lsquo;yet-another-WSL-install-blog-post&rsquo;, there are already several out there, for example <a href="https://medium.com/xtrememl/why-how-to-use-windows-10-wsl-built-in-linux-for-machine-learning-6a225f4bbd3a"target="_blank" rel="noopener">this one</a> or <a href="https://www.neowin.net/news/bash-plus-windows-10-equals-linux-gui-apps-on-the-windows-desktop"target="_blank" rel="noopener">this other one</a>. And you can always <a href="http://lmgtfy.com/?q=windows&#43;10&#43;creator&#43;update&#43;install&#43;WSL"target="_blank" rel="noopener">Google it</a>.</p>
<p>Back in early 2016 Microsoft announced and launched a very alpha version of their current Windows Subsystem for Linux (WSL). It booted and ran some software, but it was awfully buggy. Fair enough as it was still an Insider preview only (by the way, avoid Insider Preview builds, they BSOD all the time as it&rsquo;s in active development).</p>
<p>A year has passed, and we got to the Windows 10 Anniversary Update and, finally, the much-anticipated <strong>Creators Update</strong>. This is very polished in comparison.</p>
<p>If you&rsquo;re unaware of what the WSL is, I highly recommend that you read the <a href="https://blogs.msdn.microsoft.com/wsl/"target="_blank" rel="noopener">articles at Microsoft&rsquo;s Developer Blog</a> and Jessie Frazelle&rsquo;s awesome <a href="https://blog.jessfraz.com/post/windows-for-linux-nerds/"target="_blank" rel="noopener">Windows for Linux Nerds blog post</a>. In a nutshell, WSL wraps the Windows kernel system calls and maps them as Linux Kernel system calls, fooling ELF64 unmodified Linux compatible binaries to run as if they were on a real Linux-based system. The kicker is that there is no Linux kernel running! It also wraps Windows services such as filesystem, networking, into appropriate devices that Linux can access.</p>
<p>So, it&rsquo;s not any kind of virtualization. There is no Hyper-V involved. It&rsquo;s enabling Windows to boot ELF64 binaries up directly, without recompilation, without modification.</p>
<p>Unlike in OS X where we need to tweak source code and recompile to run on Darwin (the Mach/BSD-based operating system layer), we can download Ubuntu binaries (or any other distro for that matter, there are Fedora and Suse coming soon to the Windows App Store) and run them <strong>directly</strong>.</p>
<p>Even more interesting, you can run system level daemons (yes, Postgresql, Memcached, Redis, etc all load and run in the background) and you can even run X (GUI) programs!</p>
<p>Don&rsquo;t get me wrong, I commend everybody that is working hard at Microsoft to make this happen. It is a technical tour de force in itself.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/657/2017-09-14.png" alt="Windows and X side-by-side"  loading="lazy" /></p>
<p>Take a good look at the screenshot above. This is a sight to behold: native GNOME-VIM running natively side-by-side with Microsoft Edge loading a web site running out of a Rails app running within WSL, accessing PostgreSQL.</p>
<p>But, is it good for real people?</p>
<h3>How good is WSL?<span class="hx:absolute hx:-mt-20" id="how-good-is-wsl"></span>
    <a href="#how-good-is-wsl" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>TL;DR</strong> it&rsquo;s not nearly as smooth as running a pure Linux-distro, and the integration is not nearly as good as OS X in terms of usability as Windows was not redesigned in any meaningful way to be a UNIX system like OS X.</p>
<p>In an ideal world, Windows should replace its entire NT underpinnings and either build their own BSD-inspired infrastructure (like Darwin) or create their own Linux distro using off-the-shelf Linux kernel as the main driver (probably can&rsquo;t because of GPL).</p>
<p>But this is unrealistic. Apple could do it in a time when the Mac installed base was tiny, so alienating some people wouldn&rsquo;t hurt so much. Windows is just too big to fail. It is and will remain a maintenance nightmare of epic proportions.</p>
<p>That being said, WSL in itself is a miracle, so I can&rsquo;t complain. Microsoft had a really harsh decade in the turn of the century. Windows 10 is a return to form, and 2 big iterations later, it&rsquo;s finally becoming reasonable again. Maybe we&rsquo;re a couple of upgrades away from a really usable WSL. Remains to be seen.</p>
<p>Apple had its miracle in the turn of the century, and OS X and iOS are terrific OSes. Best of the breed. And by being able to reboot the entire ecosystem from scratch is not something anyone can do.</p>
<p>Linux-distros had their own ups and downs. No ecosystem can evolve healthily and in a cohesive way being that fragmented. But each major distro was able to brute force their own ecosystems. Canonical has prevailed where many failed but they are still not a definitive winner, and it feels like they&rsquo;re <a href="https://arstechnica.com/information-technology/2017/04/ubuntu-unity-is-dead-desktop-will-switch-back-to-gnome-next-year/"target="_blank" rel="noopener">losing their steam</a>. RedHat enjoyed the craze of the dot-com era and was able to fly high in the Enterprise market. There is no &ldquo;best&rdquo; distro. It&rsquo;s again, a matter of taste and ego, most of the time for most of the people.</p>
<p>And the OSS community still wastes an insurmountable amount of time in crazy disputes over stuff like the <a href="https://www.reddit.com/r/linuxmasterrace/comments/616wxo/what_is_with_all_the_controversy_with_systemd/"target="_blank" rel="noopener">Systemd controversy</a> or the time it is taking to get over X and finally get into Wayland. OSS has pros and cons, just like everything else.</p>
<p>But I digress, going back to WSL. Yes, as promised almost anything you can do in a native Ubuntu 16.04 can be done in this new environment. Even the ridiculous <code>cmd.exe</code> was heavily upgraded to the 21st century. Still nowhere near a default Terminal on Linux or OS X, but getting close. Actually, I&rsquo;d recommend installing <a href="https://github.com/goreliu/wsl-terminal"target="_blank" rel="noopener">WSL Terminal</a> as a replacement, and despite the terrible &ldquo;Bash on Ubuntu on Windows&rdquo; moniker, I&rsquo;d recommend installing ZSH instead and launching it over Bash for extra usability features.</p>
<p>You can Google around for endless tutorials on how to customize your environment, Vim, tweak PostgreSQL and so forth. So again I am not repeating those here. A caveat is that I have to <code>sudo /etc/init.d/PostgreSQL start</code> to actually run daemons. They won&rsquo;t autoload on a restart, keep that in mind.</p>
<p>And now you can install a Windows X Server such as <a href="https://sourceforge.net/projects/xming/"target="_blank" rel="noopener">xming</a> and <code>export DISPLAY=:0</code> and you now <code>sudo apt install gvim</code> and load the graphical version of Gvim, for instance. Or a full GNOME or XFCE desktop if that&rsquo;s your thing. You can run X based applications side-by-side to Windows apps, and that&rsquo;s super neat. I like to install <a href="https://github.com/skwp/dotfiles"target="_blank" rel="noopener">YADR</a> which is one of those super customized dotfiles collection and it works just fine.</p>
<p>I am now using <a href="https://github.com/asdf-vm/asdf"target="_blank" rel="noopener">ASDF</a> to install and manage multiple versions of Ruby, Node.js, Elixir, etc. And it also just works.</p>
<p>I was able to move my SSH keys from my previous <a href="http://www.akitaonrails.com/en/2017/01/10/arch-linux-a-melhor-distro-de-todas"target="_blank" rel="noopener">Arch Linux</a> install. And then I can clone from my Git repositories. Now, here comes the <strong>big caveat</strong>. I cloned and configured a Rails project. This is the fastest I can get running an entire RSpec suite with PhantomJS acceptance tests:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 5 minutes 35 seconds (files took 48.53 seconds to load)
888 examples, 0 failures</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 is running on my <a href="http://www.akitaonrails.com/en/2017/01/31/do-macbook-pro-para-o-dell-xps-arch-linux-para-usuarios-criativos"target="_blank" rel="noopener">Dell XPS 9550</a> with Core i7 Skylake (4 cores, 8 threads), 16 GB of RAM and NVME2 SSD. I asked a co-worker to run it on his very old Mac Mini 2012 with Core i5 (probably Haswell), 10 GB of RAM, normal SSD. And this is his test run:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 3 minutes 37.6 seconds (files took 9.73 seconds to load)
888 examples, 0 failures</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>Yep, this is shocking. The filesystem emulation is <a href="https://meta.discourse.org/t/installation-notes-for-discourse-on-bash-for-windows/48141/3?u=sam"target="_blank" rel="noopener">notoriously slow</a> under WSL (at least 4 times slower as you can see in the numbers above).</p>
<p>This other test ran in a slightly newer hardware (still underpowered compared to my Dell XPS) running on Linux Mint:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 3 minutes 2.4 seconds (files took 22.69 seconds to load)
888 examples, 1 failure</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 ran the same suite a couple of times to make sure, but it can&rsquo;t go below the 5 minute mark and loading files is always around 1 minute while in other OSes it&rsquo;s 2 to 4 times faster.</p>
<p>If your development workflow is I/O bound, you will suffer. Webpack compilation, which is slow, gets even slower. Npm/Yarn installs take forever. Installing stuff from source is super slow.</p>
<p>It&rsquo;s not unusable, but this slowness will get old fast.</p>
<h3>What about Virtualization?<span class="hx:absolute hx:-mt-20" id="what-about-virtualization"></span>
    <a href="#what-about-virtualization" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>After I saw the dismal filesystem performance shown above, I wondered <em>&ldquo;and what about running everything under good old Virtualbox?&rdquo;</em></p>
<p>In theory, WSL &ldquo;should&rdquo; be a thinner layer than Virtualbox, but the worst offender in terms of overhead seems to be the filesystem abstraction layer, and not necessarily access to CPU or RAM. My instinct would first suggest that a Virtual Drive, which is a single file mounted as a virtual hard drive, would perform much better.</p>
<p>So here I go, installing Virtualbox and the same set of tools (asdf, etc).</p>
<p>Mounting a shared folder directly to the one in WSL doesn&rsquo;t work properly, seems like the mounting protocol doesn&rsquo;t understand dot files, so they don&rsquo;t show up. One has to tar the folder and untar within the machine to preserve hidden files and permissions.</p>
<p>This is running the same Rspec suite over Virtualbox with 4 Cores (out of the 8 my machine has) and 4GB of RAM:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 2 minutes 3.4 seconds (files took 6.33 seconds to load)
888 examples, 2 failures</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>So, we&rsquo;re down from 5:35 min to 2:03 min and file loading from 49 seconds down to 6 seconds! So around 2.5 times faster execution and a whopping 8 times increase in file loading! This is ridiculous and it makes way more sense. My 2016 machine, within Virtualbox, still outperforms 2011/2012 bare hardware. And WSL is in the bottom of the list.</p>
<p>Forget synthetic benchmarks, this is a real-world case mixing CPU and I/O bound executions. Just for the sake of comparison, I tried to see if I could squeeze a bit more performance if I try to run over VMWare Workstation. This is the result:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 2 minutes 3.3 seconds (files took 6.21 seconds to load)</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>Almost identical. To me, it says that Virtualbox came a long way in being comparable to commercial, historically more robust solutions such as VMware or Parallels, so I will stick with Virtualbox for the time being.</p>
<p>I tried another test. This is <code>asdf</code> installing the brand new Ruby 2.4.2 from source. First over WSL (running with <code>time</code>):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>229.38s user 244.59s system 113% CPU 6:57.51 total</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 the same install from Virtualbox:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>295,39s user 22,43s system 135% CPU 3:53,87 total</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>Again, a mixed scenario of CPU and I/O bound executions. And I am at least twice as fast on Virtualbox.</p>
<p>If you do want to see more controlled scenarios running synthetic benchmark comparison, I recommend you read <a href="http://www.phoronix.com/scan.php?page=article&amp;item=intel-7900x-wsl&amp;num=1"target="_blank" rel="noopener">Phoronix&rsquo;s take on WSL vs Virtualbox</a></p>
<h3>The &ldquo;Year of Linux on the Desktop&rdquo; Conundrum<span class="hx:absolute hx:-mt-20" id="the-year-of-linux-on-the-desktop-conundrum"></span>
    <a href="#the-year-of-linux-on-the-desktop-conundrum" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The Lack of commercial software is one problem but one of the reasons is the lack of backward binary compatibility.</p>
<p>For all its problems, Microsoft remained committed to a very difficult concept: you can actually run unmodified binaries from Windows 95 in the current Windows 10. Most of the time they &ldquo;just work&rdquo;. In Linux distros, every binary is compiled against very specific library headers such as the kernel itself, Glibc and many others. Whenever one of those change, all binaries must be recompiled to run. Which is why you have the concept of &ldquo;distros&rdquo; where they recompile and test a whole set of software. Long-Term Support (LTS) is basically locking those dependencies versions for a couple of years to have software running without the need to recompile so much. But in bleeding-edge distros, you can&rsquo;t avoid having to recompile a lot of things many times over.</p>
<p>Windows carries the old dependency binaries around. OS X does the same to a degree. And OS X goes one step further by providing the concept of &ldquo;fat binaries&rdquo; (or &ldquo;Universal Binaries&rdquo;) where you have a package with multiple binaries that target different hardware architectures (32-bit vs 64 bit, Intel vs ARM vs PowerPC).</p>
<p>Mostly because of this support, commercial complex applications like Office or Adobe&rsquo;s Creative Suite can run on many different machines, across many different OS versions, over many years without upgrading. In Linux distros, it&rsquo;s a major hassle for binaries. Which is why having source code available is so important and it&rsquo;s a philosophical discussion over the whole &ldquo;Free Software&rdquo; concept, as envisioned by Richard Stallman.</p>
<p>This is an unsolvable dilemma. Linux distros don&rsquo;t have binary backward compatibility out of the box. Developers must rely on tools such as the <a href="https://lvc.github.io/abi-compliance-checker/"target="_blank" rel="noopener">ABI Compliance Checker</a>. Try to use <a href="http://www.airs.com/blog/archives/220"target="_blank" rel="noopener">symbol versioning</a> from glibc and so forth, but it&rsquo;s not realistic to expect volunteers collaborating their free time to open source to keep maintaining old versions of software for many years down the road. It is a fair compromise. Do you want the best, faster, most secure? Upgrade often, but expect old, unmaintained software to break.</p>
<p>So, without releasing the source code, you can&rsquo;t have good compatible binaries for every single major distribution, across many different versions going back many years.</p>
<h3>Which is right for you?<span class="hx:absolute hx:-mt-20" id="which-is-right-for-you"></span>
    <a href="#which-is-right-for-you" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>There is no point in trying to benchmark OSes. They are all generally good enough.</p>
<p>Macs are more expensive (sometimes by a whopping 1 grand or more). There is no denying that. The compromise is that Apple&rsquo;s hardware, OS X and the ecosystem are generally the better all around, and the &quot; fit and finish&quot; of the products are really usually better from a user experience perspective.</p>
<p>For the best mix of mainstream software and open source software, you won&rsquo;t find a better alternative. If you can afford a Mac, get a Mac.</p>
<p>Gamers have to stick with Windows. Again, there is no denying that. The best games are on Windows, period. The best combination of hardware supports Windows. No Linux tweaking will give you the raw juice you need to squeeze out 4K 60fps out of the newest AMD and NVIDIA cards through DirectX. And the Mac hardware is capped, and you can&rsquo;t add the newest GPUs. If you want to play the latest AAA games the way they were envisioned to perform, get custom hardware and Windows 10.</p>
<p>Web Developers are luckier. You can&rsquo;t go wrong, it&rsquo;s literally a matter of taste which OS you choose. You will be able to deliver the same results using the same tools. And unless you&rsquo;re targeting specific Microsoft infrastructure (SQL Server, Active Directory, etc), you can install any Linux distro and rejoice.</p>
<p>Desktop App Developers must target their audience. If you want to build Windows desktop apps, you have to be on Windows. You can use .NET/Xamarin or Java to build cross-platform compatible binaries, though. But you guys are out of luck because ideally, you have to have all 3 to ensure your software is performing as you want.</p>
<p>Mobile App Developers are also out of luck. You can do Android on Windows or Linux, but you have to have a Mac to develop for the Apple App Store. You can&rsquo;t escape that, and good luck waiting for Apple to release their tools for other platforms.</p>
<p>Creative Professionals can choose either Windows or OS X. That depends on the tooling. If you&rsquo;re an Adobe guy, either will do just fine, but you will probably want a custom workstation. Until Apple remodels the ridiculous Mac Pro, you won&rsquo;t find the best hardware for the buck at Apple. The current iMac 5K is good, but higher than that is not available. For example, if you need the power of 2 NVIDIA Titan or Quadro cards running in parallel, Apple can&rsquo;t help you there.</p>
<p>If you do require a Final Cut Pro, Logic Pro, Motion 5 workflow, the best option you will get is the iMac 5K.</p>
<p>I am not a full-time developer anymore. And I mix my time running a company, doing presentations, experimenting with tech and doing research. My ideal setup is actually a MacBook Pro. But I am forcing myself to live out of the Apple ecosystems, and it hurts. My web developer side can stay in Linux (Manjaro GNOME is my favorite nowadays) and be happy. But when my Creative side needs tooling, Gimp, Inkscape, Kdenlive, are just not good enough. I am too used to Photoshop, Illustrator, Final Cut/Premiere, Motion 5/After Effects. LibreOffice Impress is terrible to do complex presentations, and  those HTML/JS based slides software are good just for simple stuff. Try to do serious graphics and animation there and you will suffer in hell.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/a59U6kRJHLg"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>If you&rsquo;re a Creative, you <strong>need</strong> Color Calibrated screens (Adobe RGB space), you need the raw processing power to do a quick render and advanced post-processing. Adobe video editors will prefer Windows. Final Cut video editors are well served on Macs, even though the new MacBook Pro&rsquo;s cooling system is terrible:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/6TWbXV5xeYE"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>And if you&rsquo;re a full-time video editor, you can afford either high-end PC or Mac configurations, so there is not much dispute here.</p>
<p>If you&rsquo;re a video editor on a budget, you will <strong>not</strong> have a good 4K editing workflow on Linux, period. There is poor support to unlock the full potential of modern GPUs, no good Cuda or OpenCL support, and most video editing tools don&rsquo;t use them. Blender - which is primarily a 3D modeling and animation tool - is considered one of the best video editing tools available on Linux, which says a lot. The sad state of GPU support on Linux compromises Creative usage. The only other non-linear video editing tool that can achieve professional level workflows is <a href="https://www.linux.com/learn/pro-level-video-editing-lightworks-linux"target="_blank" rel="noopener">LightWorks</a> and it&rsquo;s not open source, requires the proprietary NVIDIA binaries, and <a href="https://www.lwks.com/index.php?option=com_shop&amp;view=shop&amp;Itemid=205"target="_blank" rel="noopener">will cost</a> you.</p>
<p>If you&rsquo;re going that way, it&rsquo;s probably better to stick with either Windows or OS X at this point.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I digressed quite a bit, but my conclusion is simple:</p>
<ul>
<li>WSL is not there yet. We have to wait another couple upgrades. When you hear that they improved the filesystem abstraction by a few orders of magnitude, then we can try again. And keep in mind that there are tons of <a href="https://github.com/Microsoft/BashOnWindows/issues"target="_blank" rel="noopener">open issues</a>, so your mileage will vary.</li>
<li>If you&rsquo;re a Web Developer, you&rsquo;re safe on any Linux distro.</li>
<li>If you&rsquo;re a mixed professional in need of both development toolkits and creative tools, use a Mac or Windows 10 with a fully virtualized Linux distro.</li>
<li>If you&rsquo;re a Gamer, Windows.</li>
<li>If you&rsquo;re a full-time creative professional, either Macs or PCs will do just fine.</li>
</ul>
<p>For me, I will stick with my Dell XPS running Windows 10 and Manjaro GNOME over Virtualbox. Virtualbox is fast enough that it won&rsquo;t hurt my workflow. And the creative tools can appreciate the access to my NVIDIA Cuda cores without any problem, so I can render with Blender and make Premiere render 4K video quickly enough.</p>
]]></content:encoded><category>linux</category><category>microsoft</category><category>performance</category></item><item><title>Enabling Optimus NVIDIA GPU on the Dell XPS 15 with Linux, even on Battery</title><link>https://akitaonrails.github.io/en/2017/03/14/enabling-optimus-nvidia-gpu-on-the-dell-xps-15-with-linux-even-on-battery/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2017/03/14/enabling-optimus-nvidia-gpu-on-the-dell-xps-15-with-linux-even-on-battery/</guid><pubDate>Tue, 14 Mar 2017 14:21:00 -0300</pubDate><description>&lt;p&gt;It&amp;rsquo;s been more than a month since my &lt;a href="http://www.akitaonrails.com/en/2017/01/31/do-macbook-pro-para-o-dell-xps-arch-linux-para-usuarios-criativos"target="_blank" rel="noopener"&gt;last post&lt;/a&gt; on tuning Manjaro for the Dell XPS 15.&lt;/p&gt;
&lt;p&gt;Manjaro released its newest release &lt;a href="https://manjaro.org/2017/03/07/manjaro-gnome-17-0-released/"target="_blank" rel="noopener"&gt;version 17&lt;/a&gt; and the kernel released 4.10. The upgrade from Manjaro 16 and kernel 4.9 went smoothly.&lt;/p&gt;
&lt;p&gt;These are the currently installed, kernel specific packages:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;$ pacman -Ss 410 | grep installed
core/linux410 4.10.1-1 [installed]
core/linux410-headers 4.10.1-1 [installed]
extra/linux410-acpi_call 1.1.0-0.7 (linux410-extramodules) [installed]
extra/linux410-bbswitch 0.8-0.7 (linux410-extramodules) [installed]
extra/linux410-ndiswrapper 1.61-0.7 (linux410-extramodules) [installed]
extra/linux410-nvidia 1:375.39-0.7 (linux410-extramodules) [installed]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;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"&gt;
&lt;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"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And to make sure everything is ok, I removed the old 4.9 related packages:&lt;/p&gt;</description><content:encoded><![CDATA[<p>It&rsquo;s been more than a month since my <a href="http://www.akitaonrails.com/en/2017/01/31/do-macbook-pro-para-o-dell-xps-arch-linux-para-usuarios-criativos"target="_blank" rel="noopener">last post</a> on tuning Manjaro for the Dell XPS 15.</p>
<p>Manjaro released its newest release <a href="https://manjaro.org/2017/03/07/manjaro-gnome-17-0-released/"target="_blank" rel="noopener">version 17</a> and the kernel released 4.10. The upgrade from Manjaro 16 and kernel 4.9 went smoothly.</p>
<p>These are the currently installed, kernel specific packages:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ pacman -Ss 410 | grep installed
core/linux410 4.10.1-1 [installed]
core/linux410-headers 4.10.1-1 [installed]
extra/linux410-acpi_call 1.1.0-0.7 (linux410-extramodules) [installed]
extra/linux410-bbswitch 0.8-0.7 (linux410-extramodules) [installed]
extra/linux410-ndiswrapper 1.61-0.7 (linux410-extramodules) [installed]
extra/linux410-nvidia 1:375.39-0.7 (linux410-extramodules) [installed]</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 to make sure everything is ok, I removed the old 4.9 related packages:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -R linux49 linux49-headers linux49-acpi_call linux49-bbswitch linux49-ndiswrapper linux49-nvidia</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 upgraded the system BIOS to the <a href="http://dell.to/2mWmWDg"target="_blank" rel="noopener">latest 1.2.19</a> (although many said to stay at 1.2.18 for now, but I didn&rsquo;t downgrade). The BIOS upgrade is quite easy as you just need to have a FAT formatted USB drive and copy the &ldquo;XPS_9550_1.2.19.exe&rdquo; file. On boot, you can press F12 and choose the option to upgrade directly from there.</p>
<p>One thing that stopped working was the function keys to control screen brightness. I wasn&rsquo;t able to tweak it back but I can still control the brightness manually from the Terminal using commands like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>xbacklight -inc 20 # to increment
xbacklight -dec 20 # to decrement</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, the most annoying part: the NVIDIA Optimus card.</p>
<p>Suspending the OS works flawlessly most of the time. I can just close the lid, open it the next day and the battery stays reasonably at the same level. Kudos to the kernel team for supporting it.</p>
<p>But the power management system turns off the NVIDIA GPU and I can&rsquo;t re-enable it after the machine wakes up, even if I reconnect to a power source. Whenever I try to run something through <code>optirun</code> (which forces the rendering through the NVIDIA GPU instead of the primary integrated Intel GPU) it errors out with this message:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Could not enable discrete graphics card</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 the only way to have it running was to connect the power cord and reboot the machine. Then I could use the NVIDIA GPU normally. Rebooting all the time is not slow (thanks to the fast SSD) but it&rsquo;s still annoying to have to reopen every single application every time.</p>
<p>Finally, after a lot of research, I found out how to be able to have the NVIDIA GPU enabled even on battery and after suspend. First, you need to know the PCI ID for the card:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ lspci | grep &#34;NVIDIA&#34; | cut -b -8
01:00.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>Then you need to edit <code>/etc/default/tlp</code> and add that PCI ID to be blacklisted from power management:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Exclude PCI(e) device addresses the following list from Runtime PM
# (separate with spaces). Use lspci to get the addresses (1st column).
#RUNTIME_PM_BLACKLIST=&#34;bb:dd.f 11:22.3 44:55.6&#34;
RUNTIME_PM_BLACKLIST=&#34;01:00.0&#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>Reboot, and this is it! Now I can run applications through the NVIDIA card even without being connected to the power cord.</p>
<p>It seems that <a href="https://github.com/linrunner/TLP/issues/244"target="_blank" rel="noopener">there is a conflict</a> between TLP and Bumblebee. The solution was listed in <a href="https://www.reddit.com/r/archlinux/comments/5m78zz/bumblebee_nvidia_error_on_optimus_laptop/"target="_blank" rel="noopener">this 2 months old reddit thread</a> and <a href="https://forum.manjaro.org/t/bumblebee-could-not-enable-discrete-graphics-card/16728/12"target="_blank" rel="noopener">this 2 weeks old Manjaro forum thread</a> if you&rsquo;re interested in the discussion around it.</p>
<p>The most difficult part on using NVIDIA on Linux is understanding all the many terminologies around it. I&rsquo;m not even sure that I got it all already.</p>
<p>This is what I figured out so far:</p>
<ul>
<li><a href="https://wiki.archlinux.org/index.php/NVIDIA_Optimus"target="_blank" rel="noopener">Optimus</a> is the hybrid graphics card technology, which enables a low power Intel GPU as the primary card that you can bridge to the secondary, power demanding, NVIDIA GPU, just when you really need it.</li>
<li><code>optirun</code> is the command you use to make this bridge.</li>
<li>&ldquo;NVIDIA&rdquo; is what we call the official proprietary binaries. On Arch it&rsquo;s available on package &ldquo;linux410-nvidia&rdquo;.</li>
<li>&ldquo;Nouveau&rdquo; is the open source driver, it uses Primus to make the bridge instead of <code>optirun</code>. I understand that you should avoid this driver for now if you need full performance and full compliance from the GPU.</li>
<li><a href="https://wiki.archlinux.org/index.php/Bumblebee#Power_management"target="_blank" rel="noopener">&ldquo;Bumblebee&rdquo;</a> is a daemon used to enable and disable the NVIDIA GPU. You don&rsquo;t want it enabled all the time, especially when running on battery, to avoid draining it too fast.</li>
<li><a href="https://github.com/Bumblebee-Project/bbswitch"target="_blank" rel="noopener">&ldquo;bbswitch&rdquo;</a> is the kernel module that does the low level ACPI calls to control the power state of the NVIDIA GPU card.</li>
<li><a href="https://github.com/linrunner/TLP"target="_blank" rel="noopener">&ldquo;TLP&rdquo;</a> is the general Linux power management system, which controls every aspect of the machine&rsquo;s hardware, including the PCI devices (one of which is the NVIDIA card).</li>
</ul>
<p>The way I understand it, you don&rsquo;t want TLP to kick in and shut off the card, because if it does, then Bumblebee can&rsquo;t enable it back on when needed (through bbswitch). So you have to blacklist its PCI device on TLP and let Bumblebee do its job.</p>
<p>If everything is working fine, then the NVIDIA GPU is turned off by default. You can check that it is off through bbswitch:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ cat /proc/acpi/bbswitch
0000:01:00.0 OFF</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, let&rsquo;s say you want to force something to use the card, so you do it like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ optirun -vv glxgears
[ 1817.200384] [DEBUG]Reading file: /etc/bumblebee/bumblebee.conf
[ 1817.200519] [INFO]Configured driver: nvidia
[ 1817.200579] [DEBUG]optirun version 3.2.1-2017-02-27-Format:%h$ starting...
[ 1817.200584] [DEBUG]Active configuration:
[ 1817.200588] [DEBUG] bumblebeed config file: /etc/bumblebee/bumblebee.conf
[ 1817.200592] [DEBUG] X display: :8
[ 1817.200595] [DEBUG] LD_LIBRARY_PATH: /usr/lib/nvidia:/usr/lib32/nvidia
[ 1817.200599] [DEBUG] Socket path: /var/run/bumblebee.socket
[ 1817.200603] [DEBUG] Accel/display bridge: auto
[ 1817.200607] [DEBUG] VGL Compression: proxy
[ 1817.200611] [DEBUG] VGLrun extra options: 
[ 1817.200615] [DEBUG] Primus LD Path: /usr/lib/primus:/usr/lib32/primus
[ 1817.200645] [DEBUG]Using auto-detected bridge virtualgl
[ 1818.163747] [INFO]Response: Yes. X is active.

[ 1818.163757] [INFO]Running application using virtualgl.
[ 1818.163843] [DEBUG]Process vglrun started, PID 9770.
10419 frames in 5.0 seconds = 2083.766 FPS
10671 frames in 5.0 seconds = 2134.041 FPS</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 run <code>glxgears</code> (a simple app to test the card) through the Optimus bridge (in verbose mode, which is why you have all this extra information). And if <code>glxgears</code> was able to use the NVIDIA GPU it should report FPS (frames per second) higher than 1,000.</p>
<p>And you can check with <code>bbswitch</code> like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ cat /proc/acpi/bbswitch
0000:01:00.0 ON</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>When you <code>Ctrl-C</code> out of <code>glxgears</code> it should report as <code>OFF</code> again.</p>
<p>Just to make sure, it&rsquo;s important to guarantee that <code>/etc/bumblebee/bumblebee.conf</code> is customized like this (only important keys are shown below):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[bumblebeed]
Driver=nvidia
...
[optirun]
Bridge=auto
...
[driver-nvidia]
KernelDriver=nvidia
PMMethod=bbswitch
...</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>So far, the only small issues I still have are these:</p>
<ul>
<li>Function keys don&rsquo;t change screen brightness</li>
<li>Bose bluetooth headset connects flawlessly but won&rsquo;t become primary sound output without manually changing to it under the Sound settings (but hardware function keys for volume and media control all work without problems).</li>
<li>I had to install Manjaro using the old BIOS boot and MBR partition scheme instead of GPT over UEFI. Not sure how to move to GPT/UEFI now (using a LUKS encrypted partition scheme)</li>
</ul>
<p>But after fixing the NVIDIA GPU after a suspend or power disconnect, the other issues are just very minor annoyances.</p>
<p>So far, I am very happy to be using Manjaro over the Dell XPS. I am using a dual monitor setup, and everything is working quite smoothly. If you want to try it out, I recommend you stick to the 9560 (mid 2016 version) Sandy Bridge version, do not go to the new Kaby Lake versions yet as you will find buggy BIOS firmware and many aspects of the hardware won&rsquo;t be properly supported or documented yet.</p>
<p>And if you&rsquo;re new to Arch, I highly recommend you start with Manjaro GNOME. It&rsquo;s by far the best and most usable Linux desktop I&rsquo;ve tried.</p>
]]></content:encoded><category>linux</category><category>hardware</category></item></channel></rss>