<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Manuel Herrmann</title>
    <link>https://blog.0x17.de/</link>
    <description>Recent content on Manuel Herrmann</description>
    <image>
      <title>Manuel Herrmann</title>
      <url>https://blog.0x17.de/images/mh.jpg</url>
      <link>https://blog.0x17.de/images/mh.jpg</link>
    </image>
    <generator>Hugo -- 0.159.0</generator>
    <language>en</language>
    <lastBuildDate>Sat, 21 Mar 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://blog.0x17.de/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>One Git Repo to Rule Them All: Managing 12 Machines with NixOS</title>
      <link>https://blog.0x17.de/post/managing-12-machines-with-nixos/</link>
      <pubDate>Sat, 21 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.0x17.de/post/managing-12-machines-with-nixos/</guid>
      <description>How I went from manually babysitting Linux boxes to declaratively managing servers, desktops, a gaming rig, and a VR machine from a single git repo - and why I&amp;#39;ll never go back.</description>
      <content:encoded><![CDATA[<h2 id="the-pain-before-the-solution">The Pain Before the Solution</h2>
<p>I started with Gentoo. If you know, you know: USE flags, <code>emerge --sync</code>, watching a browser compile for six hours, and the quiet satisfaction of a system shaped entirely to your will. It was maximum configuration at maximum cost - every package exactly how you wanted it, every kernel option hand-chosen, every dependency chain personally audited. It was also completely unreproducible. The system on disk was the result of hundreds of commands typed across months. If the disk died, so did the configuration. You could document it, but documentation and reality drift apart fast.</p>
<p><img alt="nixos journey" loading="lazy" src="/post/managing-12-machines-with-nixos/nixos-journey.png"></p>
<p>After a few years of that, I migrated my servers to Debian. Less customization, fewer surprises, faster iteration. Debian is rock solid. But the gap between &ldquo;I&rsquo;ve configured this&rdquo; and &ldquo;this is reproducible&rdquo; never closed. The configuration lived partly in files, partly in database state, partly in my memory. Upgrading a server meant hoping nothing drift-collided with the new packages. I learned to fear dist-upgrades.</p>
<p>Multi-machine management made this worse. When I had three servers to keep consistent, I reached for Ansible. Later tried Salt. Both are genuinely useful tools - they brought the configuration into version control, made state more explicit, handled idempotent runs reasonably well. But they&rsquo;re scaffolding over a fundamentally mutable OS. Ansible describes <em>actions</em> to take, not the <em>state</em> to achieve. The playbook says &ldquo;ensure this package is installed&rdquo; and &ldquo;write this file,&rdquo; but it says nothing about what happens to the thousand other files the system has accumulated over the years. You run the playbook, the system mostly matches what you expect, and you&rsquo;re left with a lingering uncertainty: what else is in there?</p>
<p>Building a WireGuard mesh with Salt illustrates the problem. You write YAML templates, manage state files, think carefully about idempotency edge cases, and you still have hosts where salt-minion mysteriously fell out of sync six months ago and nobody noticed. The tool is fighting the problem. You&rsquo;re layering abstraction on top of a mutable foundation and hoping the accretion doesn&rsquo;t bite you.</p>
<p>NixOS is different in kind, not just in degree. The whole system is declared as a pure function over text files. Run the same config on two machines and you get the same system. Roll back to yesterday&rsquo;s config and you get yesterday&rsquo;s system. The &ldquo;what else is in there&rdquo; question dissolves because the system is a direct consequence of what&rsquo;s in the repo.</p>
<p>That framing clicked for me when I started this config, now covering a dozen machines across servers, desktops, a gaming rig, and a VR setup. This isn&rsquo;t a single desktop experiment. It&rsquo;s a homelab, and the whole thing lives in one git repository.</p>
<hr>
<h2 id="what-nixos-actually-is">What NixOS Actually Is</h2>
<p>The central insight: your entire NixOS system is a pure function. Given the same inputs - configuration files, package hashes, module options - you get the same, bit-for-bit reproducible system. Not &ldquo;mostly the same.&rdquo; The same.</p>
<p>Three consequences fall out of this:</p>
<p><strong>Reproducibility.</strong> If a coworker asks for the same toolchain you use, you share a Nix expression instead of a wiki page. It works the first time.</p>
<p><strong>Rollback.</strong> Every <code>nixos-rebuild switch</code> creates a new system generation. If the new kernel panics at boot, you select the previous generation from the bootloader menu. The old generation is still there on disk, unchanged. You lose nothing.</p>
<p><strong>Fearless experimentation.</strong> Want to try a different init setup, a new compositor, a service you&rsquo;ve never configured before? Add it, switch, try it. If it&rsquo;s wrong, roll back. The cost of experimentation drops to nearly zero.</p>
<p>It&rsquo;s worth being clear about what NixOS is <em>not</em>. It&rsquo;s not just a package manager (though it has one). It&rsquo;s not Docker - there are no containers or images involved in basic NixOS usage, and your system packages live in the Nix store, not a layered filesystem. It&rsquo;s not Ansible - there are no playbooks, no target hosts, no SSH connections during a normal rebuild. The system is <em>built</em> from a description; it doesn&rsquo;t <em>describe operations</em> to run against a running system.</p>
<p>The honest note on the learning curve: Nix the language is genuinely strange. It&rsquo;s lazy and functional, variables are declarations rather than assignments, and error messages can be opaque. The first two weeks feel like reading documentation written for people who already understand it. That phase ends, and what&rsquo;s on the other side is worth it. But it is a real ramp.</p>
<hr>
<h2 id="the-flake-one-entry-point-for-everything">The Flake: One Entry Point for Everything</h2>
<p>A flake is Nix&rsquo;s mechanism for pinned, reproducible builds. A <code>flake.nix</code> declares what your project depends on (inputs) and what it produces (outputs). The <code>flake.lock</code> file pins every input to an exact commit hash and content hash, giving you a complete bill of materials for your infrastructure.</p>
<p>The inputs section of this config shows the pinning strategy clearly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>inputs <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  nixpkgs<span style="color:#f92672">.</span>url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;github:nixos/nixpkgs?ref=nixos-unstable&#34;</span>;
</span></span><span style="display:flex;"><span>  home-manager <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;github:nix-community/home-manager&#34;</span>;
</span></span><span style="display:flex;"><span>    inputs<span style="color:#f92672">.</span>nixpkgs<span style="color:#f92672">.</span>follows <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;nixpkgs&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  sops <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;github:Mic92/sops-nix&#34;</span>;
</span></span><span style="display:flex;"><span>    inputs<span style="color:#f92672">.</span>nixpkgs<span style="color:#f92672">.</span>follows <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;nixpkgs&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  disko <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;github:nix-community/disko&#34;</span>;
</span></span><span style="display:flex;"><span>    inputs<span style="color:#f92672">.</span>nixpkgs<span style="color:#f92672">.</span>follows <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;nixpkgs&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  hyprland <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;github:hyprwm/Hyprland/v0.54.2&#34;</span>;
</span></span><span style="display:flex;"><span>    inputs<span style="color:#f92672">.</span>nixpkgs<span style="color:#f92672">.</span>follows <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;nixpkgs&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  hy3 <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;github:outfoxxed/hy3?ref=hl0.54.2&#34;</span>;
</span></span><span style="display:flex;"><span>    inputs<span style="color:#f92672">.</span>hyprland<span style="color:#f92672">.</span>follows <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;hyprland&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  lanzaboote <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;github:nix-community/lanzaboote&#34;</span>;
</span></span><span style="display:flex;"><span>    inputs<span style="color:#f92672">.</span>nixpkgs<span style="color:#f92672">.</span>follows <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;nixpkgs&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>Notice two things: <code>inputs.nixpkgs.follows = &quot;nixpkgs&quot;</code> propagates throughout, ensuring all these tools share one nixpkgs version rather than each pulling their own. And Hyprland is pinned to <code>v0.54.2</code> with hy3 at the matching <code>hl0.54.2</code> ref - intentional, because the Hyprland plugin API changes between releases.</p>
<p>For that last point, pinning isn&rsquo;t enough if you need the pinned version available system-wide. The overlay pattern solves it. An overlay in Nix is a function that takes the final and previous package sets and returns overrides. The hyprland overlay replaces the nixpkgs hyprland package with the pinned one:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>hyprlandOverlay <span style="color:#f92672">=</span> final: prev:
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">let</span>
</span></span><span style="display:flex;"><span>    hyprlandPkgs <span style="color:#f92672">=</span> inputs<span style="color:#f92672">.</span>hyprland<span style="color:#f92672">.</span>packages<span style="color:#f92672">.</span><span style="color:#e6db74">${</span>system<span style="color:#e6db74">}</span>;
</span></span><span style="display:flex;"><span>    hy3Pkgs <span style="color:#f92672">=</span> inputs<span style="color:#f92672">.</span>hy3<span style="color:#f92672">.</span>packages<span style="color:#f92672">.</span><span style="color:#e6db74">${</span>system<span style="color:#e6db74">}</span>;
</span></span><span style="display:flex;"><span>    hyprlandPkg <span style="color:#f92672">=</span> hyprlandPkgs<span style="color:#f92672">.</span>hyprland or hyprlandPkgs<span style="color:#f92672">.</span>default;
</span></span><span style="display:flex;"><span>    hy3Pkg <span style="color:#f92672">=</span> hy3Pkgs<span style="color:#f92672">.</span>hy3 or hy3Pkgs<span style="color:#f92672">.</span>default;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">in</span>
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    hyprland <span style="color:#f92672">=</span> hyprlandPkg;
</span></span><span style="display:flex;"><span>    hyprland-unwrapped <span style="color:#f92672">=</span> hyprlandPkgs<span style="color:#f92672">.</span>hyprland-unwrapped or hyprlandPkg;
</span></span><span style="display:flex;"><span>    hyprlandPackages <span style="color:#f92672">=</span> hyprlandPkgs;
</span></span><span style="display:flex;"><span>    hyprlandPlugins <span style="color:#f92672">=</span> (prev<span style="color:#f92672">.</span>hyprlandPlugins or { }) <span style="color:#f92672">//</span> { hy3 <span style="color:#f92672">=</span> hy3Pkg; };
</span></span><span style="display:flex;"><span>    hy3 <span style="color:#f92672">=</span> hy3Pkg;
</span></span><span style="display:flex;"><span>  };
</span></span></code></pre></div><p>This overlay gets applied in <code>nixpkgs.overlays</code>, so every module in the entire config that references <code>pkgs.hyprland</code> gets the pinned version. No special imports, no passing versions around - the whole system agrees on one build.</p>
<p>The most satisfying part of the flake setup is how new hosts appear automatically. There&rsquo;s no host registration, no list to update, no imports to add:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>hostDirs <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>attrNames (lib<span style="color:#f92672">.</span>filterAttrs (_: v: v <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;directory&#34;</span>) (builtins<span style="color:#f92672">.</span>readDir <span style="color:#e6db74">./hosts</span>));
</span></span><span style="display:flex;"><span>mkHosts <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>genAttrs hostDirs;
</span></span></code></pre></div><p><code>builtins.readDir ./hosts</code> returns an attrset mapping names to types (<code>&quot;directory&quot;</code>, <code>&quot;regular&quot;</code>, etc.). Filter to directories, take the names, and generate a NixOS configuration for each one. Every directory inside <code>hosts/</code> automatically becomes a machine. Add a folder, add a <code>default.nix</code> inside it, run <code>nixos-rebuild</code> or deploy - that&rsquo;s it.</p>
<hr>
<h2 id="roles-one-module-system-for-12-different-machines">Roles: One Module System for 12 Different Machines</h2>
<p>Twelve machines sound like twelve configs to maintain. They&rsquo;re not. Machines are variations on a theme, and the theme is expressed as roles.</p>
<p>The role system starts with a typed option in <code>modules/default.nix</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>options<span style="color:#f92672">.</span>ox<span style="color:#f92672">.</span>roles <span style="color:#f92672">=</span> mkOption {
</span></span><span style="display:flex;"><span>  default <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;desktop&#34;</span> ];
</span></span><span style="display:flex;"><span>  type <span style="color:#f92672">=</span> <span style="color:#66d9ef">with</span> lib<span style="color:#f92672">.</span>types; listOf (enum [ <span style="color:#e6db74">&#34;desktop&#34;</span> <span style="color:#e6db74">&#34;laptop&#34;</span> <span style="color:#e6db74">&#34;server&#34;</span> <span style="color:#e6db74">&#34;games&#34;</span> <span style="color:#e6db74">&#34;vr&#34;</span> <span style="color:#e6db74">&#34;nas&#34;</span> ]);
</span></span><span style="display:flex;"><span>  description <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    The machine roles.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  &#39;&#39;</span>;
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>options<span style="color:#f92672">.</span>ox<span style="color:#f92672">.</span>role <span style="color:#f92672">=</span>
</span></span><span style="display:flex;"><span>  lib<span style="color:#f92672">.</span>genAttrs [ <span style="color:#e6db74">&#34;desktop&#34;</span> <span style="color:#e6db74">&#34;laptop&#34;</span> <span style="color:#e6db74">&#34;server&#34;</span> <span style="color:#e6db74">&#34;games&#34;</span> <span style="color:#e6db74">&#34;vr&#34;</span> <span style="color:#e6db74">&#34;nas&#34;</span> ] (name:
</span></span><span style="display:flex;"><span>    mkOption {
</span></span><span style="display:flex;"><span>      type <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>types<span style="color:#f92672">.</span>bool;
</span></span><span style="display:flex;"><span>      readOnly <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>      default <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>elem name config<span style="color:#f92672">.</span>ox<span style="color:#f92672">.</span>roles;
</span></span><span style="display:flex;"><span>      description <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Whether this machine has the </span><span style="color:#e6db74">${</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74"> role.&#34;</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ) <span style="color:#f92672">//</span> {
</span></span><span style="display:flex;"><span>    enduser <span style="color:#f92672">=</span> mkOption {
</span></span><span style="display:flex;"><span>      type <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>types<span style="color:#f92672">.</span>bool;
</span></span><span style="display:flex;"><span>      readOnly <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>      default <span style="color:#f92672">=</span> config<span style="color:#f92672">.</span>ox<span style="color:#f92672">.</span>role<span style="color:#f92672">.</span>desktop <span style="color:#f92672">||</span> config<span style="color:#f92672">.</span>ox<span style="color:#f92672">.</span>role<span style="color:#f92672">.</span>laptop;
</span></span><span style="display:flex;"><span>      description <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Whether this machine has a desktop or laptop role.&#34;</span>;
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>  };
</span></span></code></pre></div><p><code>ox.roles</code> is a list you set per-host: <code>[&quot;server&quot;]</code>, <code>[&quot;desktop&quot; &quot;games&quot; &quot;vr&quot;]</code>, whatever the machine is. <code>ox.role</code> derives boolean attributes from that list automatically - <code>role.server</code>, <code>role.desktop</code>, and so on - plus the computed <code>role.enduser</code> which is true for any machine a human sits in front of.</p>
<p>Those booleans then drive defaults for every feature in the system:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>ox <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  auth<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  boot<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  samba<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault role<span style="color:#f92672">.</span>enduser;
</span></span><span style="display:flex;"><span>  bluetooth<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault role<span style="color:#f92672">.</span>laptop;
</span></span><span style="display:flex;"><span>  printing<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault role<span style="color:#f92672">.</span>enduser;
</span></span><span style="display:flex;"><span>  gui<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault role<span style="color:#f92672">.</span>enduser;
</span></span><span style="display:flex;"><span>  k8s<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault (role<span style="color:#f92672">.</span>desktop <span style="color:#f92672">||</span> role<span style="color:#f92672">.</span>laptop <span style="color:#f92672">||</span> role<span style="color:#f92672">.</span>server);
</span></span><span style="display:flex;"><span>  k8s<span style="color:#f92672">.</span>server<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault role<span style="color:#f92672">.</span>server;
</span></span><span style="display:flex;"><span>  ffmpeg<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault role<span style="color:#f92672">.</span>enduser;
</span></span><span style="display:flex;"><span>  hyprland<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault role<span style="color:#f92672">.</span>enduser;
</span></span><span style="display:flex;"><span>  packages<span style="color:#f92672">.</span>nixEnable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault role<span style="color:#f92672">.</span>enduser;
</span></span><span style="display:flex;"><span>  ssh<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkDefault <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>When a machine is a server, <code>gui.enable</code>, <code>hyprland.enable</code>, <code>bluetooth.enable</code>, <code>printing.enable</code> all default to false. When it&rsquo;s a laptop, bluetooth turns on. When it&rsquo;s an enduser machine, you get Hyprland, Samba mounts, printing, and the Nix development packages. All without writing those conditions in twelve separate host configs. <code>mkDefault</code> means any individual host can still override a specific option - the defaults are overridable, not forced.</p>
<p>Look at the full <code>brunnen</code> host config, a production server:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>ox <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  hostName <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;brunnen&#34;</span>;
</span></span><span style="display:flex;"><span>  boot <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    type <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;grub&#34;</span>;
</span></span><span style="display:flex;"><span>    efi<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  roles <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;server&#34;</span> ];
</span></span><span style="display:flex;"><span>  networking<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>That&rsquo;s it. Eight lines of meaningful configuration plus boilerplate imports. The role system handles the rest - no GUI, no Bluetooth, no printing, no Hyprland, no gaming tools. Clean.</p>
<p>Now look at <code>itw</code>, the gaming desktop:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>ox <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  hostName <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;itw&#34;</span>;
</span></span><span style="display:flex;"><span>  boot <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    efi<span style="color:#f92672">.</span>dir <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/boot/EFI&#34;</span>;
</span></span><span style="display:flex;"><span>    cryptPart <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;crypt&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  roles <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;desktop&#34;</span> <span style="color:#e6db74">&#34;games&#34;</span> <span style="color:#e6db74">&#34;vr&#34;</span> ];
</span></span><span style="display:flex;"><span>  monitor<span style="color:#f92672">.</span>with4k <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  monitor<span style="color:#f92672">.</span>with4kFix <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  video <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;nvidia&#34;</span>;
</span></span><span style="display:flex;"><span>  wayland<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>environment<span style="color:#f92672">.</span>etc<span style="color:#f92672">.</span>crypttab <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  text <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    games /dev/disk/by-uuid/0292b609-...  /etc/luks-keys/games.key luks
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  &#39;&#39;</span>;
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>fileSystems <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;/games&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    device <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/dev/mapper/games&#34;</span>;
</span></span><span style="display:flex;"><span>    fsType <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;ext4&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;/mnt/win&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    device <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/dev/disk/by-partuuid/3d69d556-...&#34;</span>;
</span></span><span style="display:flex;"><span>    fsType <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;ntfs&#34;</span>;
</span></span><span style="display:flex;"><span>    options <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;defaults&#34;</span> <span style="color:#e6db74">&#34;noauto&#34;</span> ];
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>This is the most complex host in the repo. NVIDIA GPU, 4K display with DPI fixes, encrypted <code>/games</code> partition with a keyfile, dual-boot Windows on an NTFS mount, VR role on top of gaming. And the entire description still fits on a screen. The role system handles enabling Steam, Lutris, Gamescope, and related tools via <code>role.games</code>; the VR tools via <code>role.vr</code>; the Hyprland Wayland compositor via <code>role.enduser</code>. Setting <code>video = &quot;nvidia&quot;</code> injects the correct Wayland environment variables automatically - more on that in a moment.</p>
<p>That&rsquo;s the full range of the fleet: from a minimal server to a maxed-out gaming and VR workstation, with nothing special needed in between.</p>
<hr>
<h2 id="home-manager-your-dotfiles-are-config-too">home-manager: Your Dotfiles Are Config Too</h2>
<p>NixOS manages the system - packages, services, kernel configuration, networking. home-manager extends the same declarative model into the user&rsquo;s home directory. It manages dotfiles, user services, user packages, and shell configuration, all in the same language and the same git repo.</p>
<p>The <code>modules/home.nix</code> bridges the NixOS config and home-manager with one critical pattern: <code>gconfig</code>. When home-manager modules are evaluated, they need access to the top-level NixOS configuration to ask role questions. <code>gconfig</code> is passed as an extra special argument:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>home-manager<span style="color:#f92672">.</span>extraSpecialArgs <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  gconfig <span style="color:#f92672">=</span> config;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">inherit</span> oxpkgs;
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>Now any home-manager module can ask <code>gconfig.ox.role.enduser</code> or <code>gconfig.ox.wayland.enable</code> and get the host-level answer. The <code>home.nix</code> uses this immediately:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>ox<span style="color:#f92672">.</span>home<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> mkDefault config<span style="color:#f92672">.</span>ox<span style="color:#f92672">.</span>role<span style="color:#f92672">.</span>enduser;
</span></span></code></pre></div><p>Servers don&rsquo;t get a user home-manager setup at all. Desktops and laptops do.</p>
<p>The Emacs configuration is the best example of what this buys you. In a single declaration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>home <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  packages <span style="color:#f92672">=</span> [ emacs emacsedit ];
</span></span><span style="display:flex;"><span>  file<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;.emacs&#34;</span><span style="color:#f92672">.</span>source <span style="color:#f92672">=</span> <span style="color:#e6db74">./dot_emacs</span>;
</span></span><span style="display:flex;"><span>  activation<span style="color:#f92672">.</span>emacs-config <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>hm<span style="color:#f92672">.</span>dag<span style="color:#f92672">.</span>entryAfter [ <span style="color:#e6db74">&#34;writeBoundary&#34;</span> ] <span style="color:#e6db74">&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    [ -d &#34;$HOME/git&#34; ] || run mkdir -p &#34;$HOME/git&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    [ -d &#34;$HOME/git/emacs-config&#34; ] || run </span><span style="color:#e6db74">${</span>pkgs<span style="color:#f92672">.</span>git<span style="color:#e6db74">}</span><span style="color:#e6db74">/bin/git clone \
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      https://github.com/0x17de/emacs-config &#34;$HOME/git/emacs-config&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    [ -f &#34;$HOME/git/emacs-config/custom.el&#34; ] || run touch \
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      &#34;$HOME/git/emacs-config/custom.el&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  &#39;&#39;</span>;
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>systemd<span style="color:#f92672">.</span>user<span style="color:#f92672">.</span>services<span style="color:#f92672">.</span>emacs-daemon <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  Unit <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    Description <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Emacs Daemon&#34;</span>;
</span></span><span style="display:flex;"><span>    After <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;graphical-session.target&#34;</span> ];
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  Service <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    Type <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;notify&#34;</span>;
</span></span><span style="display:flex;"><span>    ExecStart <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>pkgs<span style="color:#f92672">.</span>emacs-pgtk<span style="color:#e6db74">}</span><span style="color:#e6db74">/bin/emacs --fg-daemon&#34;</span>;
</span></span><span style="display:flex;"><span>    ExecStop <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>pkgs<span style="color:#f92672">.</span>emacs-pgtk<span style="color:#e6db74">}</span><span style="color:#e6db74">/bin/emacsclient --eval &#39;(kill-emacs)&#39;&#34;</span>;
</span></span><span style="display:flex;"><span>    Restart <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;on-failure&#34;</span>;
</span></span><span style="display:flex;"><span>    RestartSec <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;2s&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  Install <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    WantedBy <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;graphical-session.target&#34;</span> ];
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>Four things at once: installs <code>emacs-pgtk</code> and wrapper scripts (<code>emacs</code> and <code>emacsedit</code>), writes a <code>.emacs</code> bootstrap file, registers a <code>systemd</code> user service that starts the daemon after the graphical session is up, and clones the external Emacs configuration repository on first activation. Fresh machine, run the rebuild, open Emacs - everything is there.</p>
<p>Where to draw the line: Nix manages the install, the service lifecycle, and the initial bootstrap. The Emacs config itself (keybindings, packages, themes) lives in its own git repository and manages itself through <code>package.el</code> or <code>use-package</code>. Nix doesn&rsquo;t try to own what the application is good at managing.</p>
<hr>
<h2 id="the-desktop-stack">The Desktop Stack</h2>
<h3 id="hyprland-and-the-overlay-pattern">Hyprland and the Overlay Pattern</h3>
<p>Hyprland&rsquo;s Wayland compositor is pinned to v0.54.2 across the whole fleet. This is intentional - the plugin API changes with releases, and the hy3 tiling plugin must match. The overlay from Section 3 means every machine that runs Hyprland gets the same version without any per-host specification.</p>
<p>Monitor configuration is generated per-host from <code>ox.hyprland.monitors</code>. The <code>itw</code> host sets <code>monitor.with4k = true</code> and <code>monitor.with4kFix = true</code>, which triggers the correct scaling and DPI configuration in the Hyprland module. When <code>ox.video = &quot;nvidia&quot;</code>, the module automatically injects the Wayland-specific NVIDIA environment variables (<code>GBM_BACKEND</code>, <code>__GLX_VENDOR_LIBRARY_NAME</code>, <code>LIBVA_DRIVER_NAME</code>, and the rest). No hunting through Arch Wiki pages at 2am for the magic env var combination. Declare the GPU, get the right environment.</p>
<h3 id="rebuilding-the-running-system">Rebuilding the Running System</h3>
<p>The most-used command in the workflow is <code>nho</code>, defined in <code>modules/nushell/config/nix.nu</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>def nho --wrapped [...rest] {
</span></span><span style="display:flex;"><span>    cd ~/git/nix
</span></span><span style="display:flex;"><span>    nh os switch . ...$rest
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>nh</code> is a wrapper around <code>nixos-rebuild</code> that adds better output formatting, diff reporting, and some quality-of-life features. This alias changes into the repo directory and invokes <code>nh os switch</code> - rebuild from the current flake, apply immediately. The <code>--wrapped</code> flag in Nushell passes extra arguments through unchanged, so <code>nho --hostname other-machine</code> works for remote deploys.</p>
<p>Make a config change, type <code>nho</code>, the system rebuilds and switches atomically. If something is wrong, the previous generation is one bootloader selection away. That feedback loop changes how you interact with your configuration.</p>
<h3 id="nushell-as-primary-shell">Nushell as Primary Shell</h3>
<p>Nushell runs as the primary interactive shell on all enduser machines. Its typed pipeline model, structured data handling, and built-in table rendering make it particularly good for homelab work. The config includes a suite of plugins: <code>polars</code> for dataframe operations, <code>query</code> for SQL-like filtering, <code>net</code> for network diagnostics, <code>formats</code> for parsing structured file formats. These are declared in the nushell module and activated automatically.</p>
<h3 id="gaming-on-nixos">Gaming on NixOS</h3>
<p>NixOS has a reputation for being difficult for gaming. That reputation is stale.</p>
<p>Steam, Lutris, Heroic Games Launcher, ProtonUp-Qt, MangoHUD, and Gamescope are all declared in the <code>games</code> module, enabled automatically when <code>role.games</code> is true. Steam&rsquo;s FHS compatibility layer handles most Windows titles without any special configuration. Proton versions are managed via ProtonUp-Qt.</p>
<p>The occasional game that absolutely requires an FHS environment - a proprietary launcher that hardcodes library paths, for instance - can be wrapped in a custom <code>buildFHSEnv</code> expression. That&rsquo;s about ten lines of Nix and not a deep rabbit hole. <code>nix-alien</code> was tried and found unreliable; the <code>buildFHSEnv</code> approach is more transparent and predictable.</p>
<p>NVIDIA gaming is handled by the <code>video = &quot;nvidia&quot;</code> flag described above. On <code>itw</code>, Gamescope handles frame pacing and fullscreen correctly for both native Linux titles and Windows games via Proton.</p>
<p>The role system means gaming tools are only present on machines that declare <code>role.games</code>. Servers stay lean. Desktops used for work but not gaming skip the Steam runtime and all the associated tooling. Nothing to manage, nothing to drift.</p>
<hr>
<h2 id="secrets-without-headaches-sops--age">Secrets Without Headaches: SOPS + Age</h2>
<p>The natural objection to &ldquo;everything is in git&rdquo;: what about passwords?</p>
<p>The answer is <a href="https://github.com/mozilla/sops">SOPS</a> with age keys. Secrets are encrypted in the repository - committed plaintext is never stored - and decrypted at activation time by the <code>sops-nix</code> NixOS module. Each secret is mounted as a file with explicit ownership and permissions.</p>
<p>The PowerDNS module shows this concretely. The primary DNS server needs an API key, and that key must be accessible to the service at runtime but must never appear in plaintext in git or the Nix store:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>config <span style="color:#f92672">=</span> mkIf cfg<span style="color:#f92672">.</span>enable {
</span></span><span style="display:flex;"><span>  sops<span style="color:#f92672">.</span>templates<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;pdns-env&#34;</span> <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkIf (<span style="color:#f92672">!</span>cfg<span style="color:#f92672">.</span>secondary) {
</span></span><span style="display:flex;"><span>    content <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      PDNS_API_KEY=</span><span style="color:#e6db74">${</span>config<span style="color:#f92672">.</span>sops<span style="color:#f92672">.</span>placeholder<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;powerdns/api-key&#34;</span><span style="color:#e6db74">}</span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#39;&#39;</span>;
</span></span><span style="display:flex;"><span>    owner <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;pdns&#34;</span>;
</span></span><span style="display:flex;"><span>    group <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;pdns&#34;</span>;
</span></span><span style="display:flex;"><span>    mode  <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;0400&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  services<span style="color:#f92672">.</span>powerdns <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>    extraConfig <span style="color:#f92672">=</span> baseExtraConfig;
</span></span><span style="display:flex;"><span>    secretFile <span style="color:#f92672">=</span> lib<span style="color:#f92672">.</span>mkIf (<span style="color:#f92672">!</span>cfg<span style="color:#f92672">.</span>secondary) config<span style="color:#f92672">.</span>sops<span style="color:#f92672">.</span>templates<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;pdns-env&#34;</span><span style="color:#f92672">.</span>path;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p><code>config.sops.placeholder.&quot;powerdns/api-key&quot;</code> is a reference to an encrypted value in <code>secrets/</code>. During activation, <code>sops-nix</code> decrypts it and writes the rendered template to a path in <code>/run/secrets/</code>, owned by <code>pdns:pdns</code>, mode 0400. <code>services.powerdns.secretFile</code> tells the service to load environment variables from that path. The API key never appears in the Nix store. The secret file in <code>secrets/</code> is committed encrypted with age - anyone with the host&rsquo;s age key (generated at first boot, stored in <code>/etc/ssh/ssh_host_ed25519_key</code>) can decrypt it; everyone else sees ciphertext.</p>
<p>This pattern applies uniformly to database passwords, API keys, WireGuard private keys, and anything else that shouldn&rsquo;t be readable by arbitrary processes. The <code>secrets/</code> directory lives in the same repo as everything else. The repo truly is the single source of truth.</p>
<hr>
<h2 id="deploying-a-new-machine-in-one-command">Deploying a New Machine in One Command</h2>
<p>New machine provisioning follows a single script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>nix run github:nix-community/nixos-anywhere -- <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --flake .#<span style="color:#e6db74">&#34;</span>$NAME<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;</span>$REMOTE<span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --generate-hardware-config nixos-generate-config <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;./hosts/</span><span style="color:#e6db74">${</span>NAME<span style="color:#e6db74">}</span><span style="color:#e6db74">/hardware-configuration.nix&#34;</span>
</span></span></code></pre></div><p><code>nixos-anywhere</code> SSHs into a running NixOS installer (booted from a USB stick or network image), generates hardware configuration for the specific machine, partitions disks according to the host&rsquo;s <code>disko.nix</code>, and installs the full system from the flake. The only prerequisites are a host config in <code>hosts/$NAME/</code> and SSH access to the target.</p>
<p>Disk layout is declared per-host in <code>disko.nix</code>. LUKS encryption, partition sizes, filesystem types, subvolumes - all expressed as Nix. The <code>itw</code> host has an encrypted system partition, a separate encrypted <code>/games</code> partition with a keyfile, and an NTFS Windows partition, all described in code and applied automatically during provisioning.</p>
<p>After the initial bootstrap, all future updates are <code>nho</code> - atomic, with rollback available from the bootloader. The setup cost is one remote command; the ongoing cost is nearly zero.</p>
<p>The PowerDNS setup illustrates what this means at scale. Two servers - <code>schere</code> (primary) and <code>stein</code> (secondary) - run the DNS infrastructure. Both configs live in the same repo. Adding or changing a DNS-related NixOS option is one file edit, then two deploys: <code>nho --hostname schere</code> and <code>nho --hostname stein</code>. No config management layer to synchronize, no state to reconcile, no &ldquo;did I update both servers?&rdquo; uncertainty.</p>
<hr>
<h2 id="the-hard-parts">The Hard Parts</h2>
<p>In the interest of accuracy: NixOS has rough edges.</p>
<p><strong>The language.</strong> Nix is lazy and functional, with an evaluation model that can surprise you. Error messages have improved significantly in recent Nix versions, but a type mismatch deep in a module evaluation can still produce a backtrace that takes time to interpret. The learning curve is front-loaded; it gets easier, but the first weeks are real.</p>
<p><strong><code>nixos-unstable</code> breakage.</strong> This config tracks <code>nixos-unstable</code>, which means <code>nix flake update</code> occasionally introduces a broken package or module API change. The fix is usually in nixpkgs within a day or two, and rollback handles the immediate problem - but it does happen. The stable channels are calmer.</p>
<p><strong>Proprietary FHS binaries.</strong> Most software works fine. Occasionally a binary with hardcoded library paths needs a <code>buildFHSEnv</code> wrapper. It&rsquo;s ten lines of Nix and not scary once you&rsquo;ve done it once, but it&rsquo;s a known rough edge.</p>
<p><strong>First-time secure boot setup.</strong> Getting lanzaboote (Secure Boot), LUKS encryption, and disko all working correctly on first install has a real setup cost. The pieces fit together well once configured, but the first time through the documentation takes effort.</p>
<p><strong>Honest scope assessment.</strong> If you have one machine and enjoy imperative tinkering, the learning investment may not be worth the return. NixOS pays off at scale, for reproducibility across machines, or for users who want the peace of mind that comes from knowing exactly what&rsquo;s on each system. Single-machine casual users are not the target audience.</p>
<hr>
<h2 id="where-this-leads">Where This Leads</h2>
<p>A year ago, provisioning a new server meant: boot the installer, partition manually, install the base system, copy configs from another machine, run Ansible, debug what drifted, repeat. With this setup, it&rsquo;s <code>./install-remote.sh newhost root@192.168.1.x</code> and wait. The system that comes up is defined entirely by the config in git, with no variation possible.</p>
<p>That mental shift - from &ldquo;the system is the result of what I&rsquo;ve done to it&rdquo; to &ldquo;the system is a function of what I&rsquo;ve declared&rdquo; - is the core of what NixOS offers.</p>
<p>A few threads worth pulling on from here:</p>
<p><strong>disko and LUKS in depth.</strong> The declarative disk setup deserves its own writeup. Combining disko, LUKS, and lanzaboote (Secure Boot) on a single machine, from a declarative base, is a solved problem that&rsquo;s worth documenting step by step.</p>
<p><strong>Writing your own NixOS module.</strong> The <code>ox.*</code> namespace in this repo is a collection of custom modules. The pattern - options, defaults, conditional config - is learnable in an afternoon and immediately useful for your own abstraction layer.</p>
<p><strong>home-manager on non-NixOS.</strong> The home-manager configuration described here works on any Linux (or macOS) system, not just NixOS. If NixOS on bare metal is too much commitment, running home-manager standalone on Debian or Ubuntu gets you the dotfile management and reproducible user environment without changing the OS.</p>
<p>Where to start: <a href="https://nixos.org">nixos.org</a> → <a href="https://nix.dev">nix.dev</a> → find a real flake on GitHub and read it → start with one machine.</p>
<p>The whole system is a function. Write better inputs; get a better system. That&rsquo;s the deal.</p>
]]></content:encoded>
    </item>
    <item>
      <title>DocMan: A Minimalist Document Management Solution</title>
      <link>https://blog.0x17.de/post/document-management/</link>
      <pubDate>Thu, 08 May 2025 23:00:00 +0200</pubDate>
      <guid>https://blog.0x17.de/post/document-management/</guid>
      <description>&lt;p&gt;As a German living with the country&amp;rsquo;s infamous love for paperwork, I built a streamlined Qt-based tool called DocMan to manage my scanned documents efficiently. This post explains how it works and why I created it several years ago—and why it&amp;rsquo;s still my go-to solution today without requiring any maintenance.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;intro&#34; loading=&#34;lazy&#34; src=&#34;https://blog.0x17.de/post/document-management/intro.jpg&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;the-problem&#34;&gt;The Problem&lt;/h2&gt;
&lt;p&gt;My document workflow starts with my scanner, which connects to my network via Samba and deposits scanned PDFs into an INBOX folder. I needed a lightweight but effective way to:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>As a German living with the country&rsquo;s infamous love for paperwork, I built a streamlined Qt-based tool called DocMan to manage my scanned documents efficiently. This post explains how it works and why I created it several years ago—and why it&rsquo;s still my go-to solution today without requiring any maintenance.</p>
<p><img alt="intro" loading="lazy" src="/post/document-management/intro.jpg"></p>
<h2 id="the-problem">The Problem</h2>
<p>My document workflow starts with my scanner, which connects to my network via Samba and deposits scanned PDFs into an INBOX folder. I needed a lightweight but effective way to:</p>
<ol>
<li>Navigate through my organized document folders</li>
<li>View and examine PDF documents clearly</li>
<li>Name files consistently (I prefer the YYmmdd_description format)</li>
<li>Move files to their permanent location quickly</li>
</ol>
<p>After trying several alternatives like ecoDMS, Paperwork, and Paperless-ngx, I found they didn&rsquo;t quite match my specific workflow needs or added complexity I didn&rsquo;t want.</p>
<h2 id="docman-the-solution">DocMan: The Solution</h2>
<p><img alt="docman" loading="lazy" src="/post/document-management/docman.png"></p>
<p>DocMan is a focused Qt application that serves a single purpose extremely well - helping me move documents from my scanner&rsquo;s output folder to their proper home in my directory structure. The interface is clean and divided into three primary functional areas:</p>
<h3 id="1-directory-navigation">1. Directory Navigation</h3>
<p>On the left side, there&rsquo;s a hierarchical tree view of my document directories. This component:</p>
<ul>
<li>Uses a customized file system model that shows only folders (no files)</li>
<li>Automatically expands folders when directories are loaded</li>
<li>Displays only the directory name column, keeping the interface clean</li>
<li>Has my document root folder hardcoded to <code>/home/mh/mnt/user/mh/private/Haushalt</code></li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span>fsModel<span style="color:#f92672">-&gt;</span>setFilter(QDir<span style="color:#f92672">::</span>AllDirs <span style="color:#f92672">|</span> QDir<span style="color:#f92672">::</span>NoDotAndDotDot);
</span></span><span style="display:flex;"><span>fsModel<span style="color:#f92672">-&gt;</span>setRootPath(<span style="color:#e6db74">&#34;/home/mh/mnt/user/mh/private/Haushalt&#34;</span>);
</span></span></code></pre></div><h3 id="2-pdf-viewer">2. PDF Viewer</h3>
<p>The central component is a PDF viewer built on Poppler-Qt5, offering:</p>
<ul>
<li>Clear, high-quality rendering of PDF documents</li>
<li>Adjustable zoom level via a slider control</li>
<li>Support for multi-page PDFs with pagination controls</li>
<li>Quick rendering of each page as you navigate</li>
<li>Persistence of zoom level when switching between documents</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">void</span> PdfViewer<span style="color:#f92672">::</span>renderPage()
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    scene<span style="color:#f92672">-&gt;</span>clear();
</span></span><span style="display:flex;"><span>    QImage image <span style="color:#f92672">=</span> currentDocument<span style="color:#f92672">-&gt;</span>page(currentPage)<span style="color:#f92672">-&gt;</span>renderToImage(currentZoom, currentZoom);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">auto</span> pixmap <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> QGraphicsPixmapItem(QPixmap<span style="color:#f92672">::</span>fromImage(image));
</span></span><span style="display:flex;"><span>    scene<span style="color:#f92672">-&gt;</span>addItem(pixmap);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="3-filename-input">3. Filename Input</h3>
<p>At the top, a simple input field allows me to quickly name each document:</p>
<ul>
<li>I enter my preferred YYmmdd_description format</li>
<li>Pressing Enter commits the name and moves the file</li>
<li>The field clears automatically, ready for the next document</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">void</span> MainWindow<span style="color:#f92672">::</span>moveFile(<span style="color:#66d9ef">const</span> QString<span style="color:#f92672">&amp;</span> name)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (targetPath <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        QDir().rename(currentFilename, targetPath <span style="color:#f92672">+</span> name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;.pdf&#34;</span>);
</span></span><span style="display:flex;"><span>        nextFile();
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="the-workflow-in-action">The Workflow in Action</h2>
<p>When I need to process documents (which happens every now and then rather than daily), my routine follows these simple steps:</p>
<ol>
<li>Paper documents go through my scanner, which saves them as PDFs to my INBOX directory on my Samba share</li>
<li>I launch DocMan from within the same network</li>
<li>The application automatically loads the first document from my INBOX</li>
<li>I examine the document using the viewer (zooming or paging through it as needed)</li>
<li>I navigate to the appropriate destination folder in the directory tree</li>
<li>I type a descriptive filename in my preferred format (e.g., &ldquo;250508_electric_bill&rdquo;)</li>
<li>Pressing Enter moves the file to the selected location and loads the next document</li>
</ol>
<p>The efficiency comes from the tight integration of these components. I can process a stack of documents in minutes without context switching between applications.</p>
<h2 id="technical-implementation">Technical Implementation</h2>
<p>DocMan is quite lean, implemented in C++ with Qt5. Here are some notable technical aspects:</p>
<ul>
<li>Uses Poppler-Qt5 for high-quality PDF rendering</li>
<li>Employs a custom QFileSystemModel to show only directories</li>
<li>Includes a specialized QLineEdit that captures Enter key presses</li>
<li>Utilizes signals and slots for component communication</li>
<li>The file list class automatically populates from my INBOX directory:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span>PdfList<span style="color:#f92672">::</span>PdfList(QObject <span style="color:#f92672">*</span>parent) <span style="color:#f92672">:</span> QObject(parent)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    files <span style="color:#f92672">=</span> QDir(<span style="color:#e6db74">&#34;/home/mh/mnt/user/mh/private/INBOX&#34;</span>).entryList(QDir<span style="color:#f92672">::</span>Files);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="why-qt">Why Qt?</h2>
<p>I chose Qt for a few practical reasons:</p>
<ul>
<li>I was already familiar with the framework, reducing the development time</li>
<li>Poppler-Qt5 offered the best PDF rendering capabilities I could find</li>
<li>Qt&rsquo;s signal/slot mechanism made component communication straightforward</li>
<li>The cross-platform nature meant I could run it on different systems if needed</li>
</ul>
<h2 id="who-needs-ocr-anyway">Who Needs OCR Anyway?</h2>
<p>One notable feature I deliberately omitted was OCR (Optical Character Recognition). While many document management systems emphasize searchable text, I&rsquo;ve found that my simpler approach works perfectly for my needs. By using consistent naming conventions with dates and brief descriptions, I can easily locate documents later by browsing the logical folder structure or using basic file search tools.</p>
<p>For example, I know roughly when I received a particular utility bill and in which folder it should be, so finding &ldquo;2504_electric_bill.pdf&rdquo; is straightforward without needing to search through the document content itself.</p>
<h2 id="why-build-a-custom-tool">Why Build a Custom Tool?</h2>
<p>I could have used existing solutions, but creating DocMan gave me several advantages:</p>
<ol>
<li><strong>Perfect workflow fit</strong> - It does exactly what I need with no extra features</li>
<li><strong>Speed</strong> - The dedicated interface makes document processing extremely efficient</li>
<li><strong>Control</strong> - I can modify it whenever my requirements change</li>
<li><strong>Simplicity</strong> - No complex setup, cloud dependencies, or maintenance headaches</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>Sometimes the best tool is one you build yourself. DocMan demonstrates that a focused application solving a specific workflow problem can be more effective than generic solutions. While it&rsquo;s admittedly simple, it saves me time whenever I need it by streamlining an occasional but necessary task.</p>
<p>What&rsquo;s been most surprising is its longevity—I built this tool several years ago, and it continues to serve me perfectly without requiring any maintenance or updates. In a world of constantly changing software, there&rsquo;s something deeply satisfying about a purpose-built tool that just keeps working.</p>
<p>The greatest productivity gains often come not from complex systems but from reducing friction in common workflows. By transforming document management from a chore into a quick, smooth process, DocMan helps me focus on the content of my documents rather than the mechanics of organizing them—which is especially valuable when dealing with the mountain of paperwork that seems to be Germany&rsquo;s national hobby!</p>
<h2 id="a-final-note-on-physical-document-storage">A Final Note on Physical Document Storage</h2>
<p>What happens to all those physical papers after scanning? My approach is refreshingly straightforward: they go into cardboard boxes and get archived away. No complex filing cabinets, no elaborate categorization systems—just simple storage.</p>
<p>If I ever need the original paper document, I know roughly where to find it based on when I scanned it. But here&rsquo;s the thing: I&rsquo;ve rarely had to retrieve the physical copies. The digital system works so well that the originals become just a backup, safely tucked away without needing to be meticulously organized.</p>
<p><img alt="outro" loading="lazy" src="/post/document-management/outro.jpg"></p>
<p>This pragmatic approach speaks to an important principle: optimize the parts of your workflow that matter most. For me, that&rsquo;s quick processing and reliable digital retrieval—not creating a perfect physical filing system that I&rsquo;ll rarely use. Sometimes the best solution isn&rsquo;t the most elaborate one, but the one that solves your actual problems with minimal overhead.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Generic XML Parsing in C&#43;&#43;: A Template Metaprogramming Adventure</title>
      <link>https://blog.0x17.de/post/generic-xml-parsing-cpp/</link>
      <pubDate>Thu, 01 May 2025 00:00:00 +0200</pubDate>
      <guid>https://blog.0x17.de/post/generic-xml-parsing-cpp/</guid>
      <description>&lt;p&gt;Some years ago, I found myself experimenting with template metaprogramming in C++. One of the most intriguing projects that came out of this tinkering was a generic XML parser that combined several powerful features: declarative syntax, automatic validation, and bidirectional serialization/deserialization—all without requiring duplicate code.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;intro&#34; loading=&#34;lazy&#34; src=&#34;https://blog.0x17.de/post/generic-xml-parsing-cpp/intro.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;Today I&amp;rsquo;m dusting off this experiment to share how it works and reflect on how modern C++ features could simplify this approach even further.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Some years ago, I found myself experimenting with template metaprogramming in C++. One of the most intriguing projects that came out of this tinkering was a generic XML parser that combined several powerful features: declarative syntax, automatic validation, and bidirectional serialization/deserialization—all without requiring duplicate code.</p>
<p><img alt="intro" loading="lazy" src="/post/generic-xml-parsing-cpp/intro.jpg"></p>
<p>Today I&rsquo;m dusting off this experiment to share how it works and reflect on how modern C++ features could simplify this approach even further.</p>
<h2 id="the-goal-declarative-xml-handling">The Goal: Declarative XML Handling</h2>
<p>The primary goal of this project was to create a system where:</p>
<ol>
<li>You could define an XML structure once, using a declarative syntax</li>
<li>The same definition would handle both parsing and serialization</li>
<li>Validation would be built-in (required vs. optional elements)</li>
<li>The code would be type-safe and leverage compile-time checking</li>
</ol>
<p>What I wanted to avoid was the typical approach where you write separate code for parsing, validation, and serialization—an approach that often leads to duplicated logic and inconsistencies.</p>
<p>For example, consider this XML document:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#f92672">&lt;root</span> <span style="color:#a6e22e">key=</span><span style="color:#e6db74">&#34;mykey&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;data</span> <span style="color:#a6e22e">id=</span><span style="color:#e6db74">&#34;1&#34;</span><span style="color:#f92672">&gt;</span>D1<span style="color:#f92672">&lt;/data&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;data</span> <span style="color:#a6e22e">id=</span><span style="color:#e6db74">&#34;2&#34;</span><span style="color:#f92672">&gt;</span>D2<span style="color:#f92672">&lt;/data&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/root&gt;</span>
</span></span></code></pre></div><p>In a traditional approach, you might write separate parsing code, validation logic, and serialization functions for this structure. But with this template-based approach, you define the structure once and get all these capabilities automatically.</p>
<p>Here&rsquo;s a glimpse of what the final API looked like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">auto</span> xml <span style="color:#f92672">=</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;root&#34;</span>_node(
</span></span><span style="display:flex;"><span>        Required(),
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;key&#34;</span>_attr(Required()),
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;client_id&#34;</span>_attr(),
</span></span><span style="display:flex;"><span>        NodeList(
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;data&#34;</span>_node(
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;id&#34;</span>_attr(Required()),
</span></span><span style="display:flex;"><span>                Text(Required()))));
</span></span></code></pre></div><p>With this single definition, you could:</p>
<ul>
<li>Parse XML strings into a structured <code>NodeData</code> object</li>
<li>Validate that all required elements exist</li>
<li>Serialize the data structure back to XML</li>
</ul>
<h2 id="how-it-works-template-metaprogramming-magic">How It Works: Template Metaprogramming Magic</h2>
<p>The implementation relies heavily on several C++ template metaprogramming techniques:</p>
<h3 id="1-the-nodedata-structure">1. The NodeData Structure</h3>
<p>The core of the system is a generic <code>NodeData</code> structure that acts as an intermediate representation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">struct</span> <span style="color:#a6e22e">NodeData</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    std<span style="color:#f92672">::</span>string name;
</span></span><span style="display:flex;"><span>    std<span style="color:#f92672">::</span>string text;
</span></span><span style="display:flex;"><span>    std<span style="color:#f92672">::</span>map<span style="color:#f92672">&lt;</span>std<span style="color:#f92672">::</span>string, std<span style="color:#f92672">::</span>vector<span style="color:#f92672">&lt;</span>NodeData<span style="color:#f92672">&gt;&gt;</span> subnodes;
</span></span><span style="display:flex;"><span>    std<span style="color:#f92672">::</span>map<span style="color:#f92672">&lt;</span>std<span style="color:#f92672">::</span>string, std<span style="color:#f92672">::</span>string<span style="color:#f92672">&gt;</span> attributes;
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>This structure stores everything we need about an XML node: its name, text content, child nodes, and attributes.</p>
<h3 id="2-node-types-and-traits">2. Node Types and Traits</h3>
<p>The system defines several node types that know how to interact with both XML and the <code>NodeData</code> structure:</p>
<ul>
<li><code>Node&lt;name, Args...&gt;</code>: Represents an XML element</li>
<li><code>Attribute&lt;name, Args...&gt;</code>: Represents an XML attribute</li>
<li><code>Text&lt;Args...&gt;</code>: Represents text content within an element</li>
<li><code>NodeList&lt;SubNodeType, Args...&gt;</code>: Represents a list of similar child nodes</li>
</ul>
<p>Each node type implements several key methods:</p>
<ul>
<li><code>subnode()</code>: Retrieves the relevant part of an XML document</li>
<li><code>validate()</code>: Checks if the node meets requirements</li>
<li><code>parse()</code>: Extracts data from XML into <code>NodeData</code></li>
<li><code>serialize()</code>: Writes data from <code>NodeData</code> back to XML</li>
</ul>
<h3 id="3-type-deduction-and-user-defined-literals">3. Type Deduction and User-Defined Literals</h3>
<p>One of the key challenges in C++14 was that constructors couldn&rsquo;t deduce template parameters from arguments. This limitation required a creative workaround using user-defined literals and builder classes:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">template</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">CharT</span>, CharT... chars<span style="color:#f92672">&gt;</span> <span style="color:#66d9ef">auto</span> <span style="color:#66d9ef">operator</span><span style="color:#e6db74">&#34;&#34;</span>_node()
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">char</span> name[] <span style="color:#f92672">=</span> {chars..., <span style="color:#ae81ff">0</span>};
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> NodeBuilder<span style="color:#f92672">&lt;</span>name<span style="color:#f92672">&gt;</span>();
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This trick allows us to write <code>&quot;root&quot;_node()</code> which creates a <code>NodeBuilder&lt;&quot;root&quot;&gt;</code> that can then build a <code>Node&lt;&quot;root&quot;, Args...&gt;</code> with the proper template parameters.</p>
<h3 id="4-variadic-templates-for-complex-structures">4. Variadic Templates for Complex Structures</h3>
<p>Variadic templates allow us to handle arbitrary nesting and combinations of nodes:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">template</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">class</span><span style="color:#960050;background-color:#1e0010">... </span><span style="color:#a6e22e">Args</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">struct</span> <span style="color:#a6e22e">is_required</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">template</span><span style="color:#f92672">&lt;&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">struct</span> <span style="color:#a6e22e">is_required</span><span style="color:#f92672">&lt;&gt;</span> <span style="color:#f92672">:</span> std<span style="color:#f92672">::</span>false_type { };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">template</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Arg</span>, <span style="color:#66d9ef">class</span><span style="color:#960050;background-color:#1e0010">... </span><span style="color:#a6e22e">Args</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">struct</span> <span style="color:#a6e22e">is_required</span><span style="color:#f92672">&lt;</span>Arg, Args...<span style="color:#f92672">&gt;</span> <span style="color:#f92672">:</span> 
</span></span><span style="display:flex;"><span>    std<span style="color:#f92672">::</span>conditional_t<span style="color:#f92672">&lt;</span>std<span style="color:#f92672">::</span>is_same_v<span style="color:#f92672">&lt;</span>Arg, Required<span style="color:#f92672">&gt;</span>, 
</span></span><span style="display:flex;"><span>                      std<span style="color:#f92672">::</span>true_type, 
</span></span><span style="display:flex;"><span>                      is_required<span style="color:#f92672">&lt;</span>Args...<span style="color:#f92672">&gt;&gt;</span> { };
</span></span></code></pre></div><p>This recursive template specialization checks if <code>Required</code> is among the arguments passed to a node, enabling us to handle validation elegantly.</p>
<h2 id="example-in-action">Example in Action</h2>
<p>Let&rsquo;s see how the system handles various XML inputs:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">auto</span> examples <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&lt;wrong /&gt;&#34;</span>s,                                                            <span style="color:#75715e">// Wrong root element
</span></span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&lt;root /&gt;&#34;</span>s,                                                             <span style="color:#75715e">// Missing required attribute
</span></span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&lt;root key=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">mykey</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74"> /&gt;&#34;</span>s,                                               <span style="color:#75715e">// Valid minimal example
</span></span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&lt;root key=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">mykey</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&gt;&lt;data id=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">1</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74"> /&gt;&lt;/root&gt;&#34;</span>s,                         <span style="color:#75715e">// Missing required text
</span></span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&lt;root key=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">mykey</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&gt;&lt;data id=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">1</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&gt;D1&lt;/data&gt;&lt;data id=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">2</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&gt;D2&lt;/data&gt;&lt;/root&gt;&#34;</span>s  <span style="color:#75715e">// Fully valid
</span></span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>The system will:</p>
<ol>
<li>Reject <code>&lt;wrong /&gt;</code> because it has the wrong root element name</li>
<li>Reject <code>&lt;root /&gt;</code> because the required &ldquo;key&rdquo; attribute is missing</li>
<li>Accept <code>&lt;root key=&quot;mykey&quot; /&gt;</code> as a minimal valid document</li>
<li>Reject <code>&lt;root key=&quot;mykey&quot;&gt;&lt;data id=&quot;1&quot; /&gt;&lt;/root&gt;</code> because the data node is missing required text</li>
<li>Accept the full example with two data nodes, each with their required attributes and text</li>
</ol>
<h2 id="modern-c-improvements">Modern C++ Improvements</h2>
<p>This code was written in the early days of C++17. If I were to revisit it today with C++20, several improvements would be possible:</p>
<h3 id="1-class-template-argument-deduction-ctad">1. Class Template Argument Deduction (CTAD)</h3>
<p>C++17 introduced CTAD, which would eliminate the need for the <code>NodeBuilder</code> approach. With C++20 we could directly write:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#75715e">// Instead of &#34;root&#34;_node(Required())
</span></span></span><span style="display:flex;"><span>Node<span style="color:#f92672">&lt;</span><span style="color:#e6db74">&#34;root&#34;</span><span style="color:#f92672">&gt;</span>(Required())
</span></span></code></pre></div><h3 id="2-string-literals-as-template-parameters">2. String Literals as Template Parameters</h3>
<p>C++20 allows string literals as template parameters, which would greatly simplify the implementation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">template</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">auto</span> name<span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Node</span> { <span style="color:#75715e">/* ... */</span> };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Usage:
</span></span></span><span style="display:flex;"><span>Node<span style="color:#f92672">&lt;</span><span style="color:#e6db74">&#34;root&#34;</span><span style="color:#f92672">&gt;</span>
</span></span></code></pre></div><h3 id="3-concepts-and-constraints">3. Concepts and Constraints</h3>
<p>C++20 concepts would allow for more precise constraints on template parameters, making error messages clearer and improving compile times:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">template</span><span style="color:#f92672">&lt;</span>NodeConcept... Children<span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Node</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Implementation
</span></span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><h3 id="4-stdvisit-and-variant">4. <code>std::visit</code> and Variant</h3>
<p>Using <code>std::variant</code> from C++17 could provide a more type-safe approach to handling different node types:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">using</span> NodeVariant <span style="color:#f92672">=</span> std<span style="color:#f92672">::</span>variant<span style="color:#f92672">&lt;</span>Element, Attribute, Text<span style="color:#f92672">&gt;</span>;
</span></span></code></pre></div><h3 id="5-if-constexpr">5. <code>if constexpr</code></h3>
<p>C++17&rsquo;s <code>if constexpr</code> should eliminate the need for some of the template specializations:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">constexpr</span> (is_required_v<span style="color:#f92672">&lt;</span>Args...<span style="color:#f92672">&gt;</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Handle required case
</span></span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Handle optional case
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="conclusion">Conclusion</h2>
<p>This XML parsing experiment demonstrates the power of template metaprogramming in C++. By combining user-defined literals, variadic templates, and SFINAE, we can create a declarative API for XML handling that provides type safety, validation, and bidirectional conversion.</p>
<p>While the implementation might seem complex, it delivers significant benefits:</p>
<ol>
<li>Write once, use for both parsing and serialization</li>
<li>Built-in validation enforced at runtime</li>
<li>Declarative syntax that mirrors the structure of XML</li>
<li>Type safety that catches errors at compile time</li>
</ol>
<p>Modern C++ could make this implementation even more elegant and concise, but the core ideas remain valuable. Template metaprogramming allows us to create domain-specific languages within C++ that can dramatically reduce boilerplate and improve code reliability.</p>
<p>The next time you find yourself writing separate code for parsing, validating, and serializing a data format, consider whether a template-based approach might let you define the structure once and get all those operations for free.</p>
<hr>
<p><em>See the full code example <a href="https://www.0x17.de/tools/generic_xml_parser.html">here</a></em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Building a Modular Emacs Configuration</title>
      <link>https://blog.0x17.de/post/modular-emacs-config/</link>
      <pubDate>Sat, 26 Apr 2025 00:00:00 +0200</pubDate>
      <guid>https://blog.0x17.de/post/modular-emacs-config/</guid>
      <description>&lt;p&gt;Maintaining a clean, organized Emacs configuration becomes increasingly important as your setup grows. A modular approach separates concerns, simplifies troubleshooting, and makes your configuration more maintainable over time. In this article, I&amp;rsquo;ll share how I structure my Emacs configuration for maximum flexibility and sustainability.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;intro&#34; loading=&#34;lazy&#34; src=&#34;https://blog.0x17.de/post/modular-emacs-config/intro.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;the-problem-with-monolithic-configurations&#34;&gt;The Problem with Monolithic Configurations&lt;/h2&gt;
&lt;p&gt;Many Emacs users start with a single &lt;code&gt;.emacs&lt;/code&gt; or &lt;code&gt;init.el&lt;/code&gt; file. This works fine at first, but as your configuration grows to accommodate different programming languages, workflows, and packages, a single file becomes unwieldy. Common issues include:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Maintaining a clean, organized Emacs configuration becomes increasingly important as your setup grows. A modular approach separates concerns, simplifies troubleshooting, and makes your configuration more maintainable over time. In this article, I&rsquo;ll share how I structure my Emacs configuration for maximum flexibility and sustainability.</p>
<p><img alt="intro" loading="lazy" src="/post/modular-emacs-config/intro.png"></p>
<h2 id="the-problem-with-monolithic-configurations">The Problem with Monolithic Configurations</h2>
<p>Many Emacs users start with a single <code>.emacs</code> or <code>init.el</code> file. This works fine at first, but as your configuration grows to accommodate different programming languages, workflows, and packages, a single file becomes unwieldy. Common issues include:</p>
<ul>
<li>Hard-to-find settings when you need to make changes</li>
<li>Difficult debugging when something breaks</li>
<li>Challenging to selectively enable/disable features</li>
<li>Poor organization making it hard to understand your own setup</li>
</ul>
<h2 id="a-modular-approach">A Modular Approach</h2>
<p>Let&rsquo;s explore a modular configuration structure based on <a href="https://github.com/0x17de/emacs-config">my personal setup</a> that addresses these issues.</p>
<h3 id="root-configuration-file">Root Configuration File</h3>
<p>The entry point for my configuration is <code>_0x17de-emacs.el</code>, which defines the base setup and loads other modules:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(setq initial-scratch-message <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>      ring-bell-function <span style="color:#a6e22e">#&#39;</span>ignore)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(defvar _0x17de/load-path (<span style="color:#a6e22e">file-name-directory</span> (or load-file-name <span style="color:#a6e22e">buffer-file-name</span>))
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Path of the _0x17de emacs config&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(defun _0x17de/load (paths)
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Load files relative to this emacs config.&#34;</span>
</span></span><span style="display:flex;"><span>  (when (not (<span style="color:#a6e22e">listp</span> paths))
</span></span><span style="display:flex;"><span>    (setq paths (<span style="color:#a6e22e">list</span> paths)))
</span></span><span style="display:flex;"><span>  (dolist (path paths)
</span></span><span style="display:flex;"><span>    (condition-case err
</span></span><span style="display:flex;"><span>        (<span style="color:#a6e22e">load</span> (<span style="color:#a6e22e">expand-file-name</span> path _0x17de/load-path))
</span></span><span style="display:flex;"><span>      (<span style="color:#a6e22e">error</span> (<span style="color:#a6e22e">message</span> <span style="color:#e6db74">&#34;Failed to load _0x17de-config sub-library at %S: %S&#34;</span> path err)))))
</span></span></code></pre></div><p>The key here is the <code>_0x17de/load</code> function that loads modules with error handling, preventing a failure in one module from breaking your entire configuration.</p>
<h3 id="directory-structure">Directory Structure</h3>
<p>My configuration is organized into clear directories:</p>
<ul>
<li><code>init/</code> - Core settings (startup, UI, packages)</li>
<li><code>langs/</code> - Language-specific configurations</li>
<li><code>utils/</code> - General utilities and enhancements</li>
<li><code>ext/</code> - External packages not available in package repositories</li>
</ul>
<p>This structure makes it immediately clear where to find specific settings.</p>
<h3 id="core-initialization">Core Initialization</h3>
<p>The initialization sequence loads modules in logical groups:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(_0x17de/load
</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#39;</span>(<span style="color:#e6db74">&#34;./init/custom.el&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;./init/speedup.el&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;./init/package.el&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;./init/encoding.el&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;./init/gui.el&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;./init/exwm.el&#34;</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(_0x17de/load
</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#39;</span>(<span style="color:#e6db74">&#34;./utils/exec-path-from-shell&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;./utils/minibuffer&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;./utils/indention&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#75715e">;; ... more utilities ... </span>
</span></span><span style="display:flex;"><span>   ))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(_0x17de/load
</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#39;</span>(<span style="color:#e6db74">&#34;./langs/common&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;./langs/ansible&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;./langs/plantuml&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#75715e">;; ... more languages ...</span>
</span></span><span style="display:flex;"><span>   ))
</span></span></code></pre></div><p>This approach lets you:</p>
<ol>
<li>Control the order of initialization</li>
<li>Group related components</li>
<li>Easily comment out modules you don&rsquo;t want to load</li>
</ol>
<h3 id="graceful-error-handling">Graceful Error Handling</h3>
<p>A crucial aspect of this setup is the error handling in the <code>_0x17de/load</code> function. If a module fails to load, it displays a message but continues loading other modules:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(condition-case err
</span></span><span style="display:flex;"><span>    (<span style="color:#a6e22e">load</span> (<span style="color:#a6e22e">expand-file-name</span> path _0x17de/load-path))
</span></span><span style="display:flex;"><span>  (<span style="color:#a6e22e">error</span> (<span style="color:#a6e22e">message</span> <span style="color:#e6db74">&#34;Failed to load _0x17de-config sub-library at %S: %S&#34;</span> path err)))
</span></span></code></pre></div><p>This prevents a single module failure from breaking your entire Emacs experience.</p>
<h2 id="specialized-module-examples">Specialized Module Examples</h2>
<p>Let&rsquo;s examine a few module types to see how they encapsulate specific functionality.</p>
<h3 id="language-support">Language Support</h3>
<p>Language-specific modules handle everything needed for a particular language. For example, my Go configuration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(use-package go-mode
</span></span><span style="display:flex;"><span>  :defer <span style="color:#66d9ef">t</span>
</span></span><span style="display:flex;"><span>  :bind
</span></span><span style="display:flex;"><span>  (:map go-mode-map
</span></span><span style="display:flex;"><span>        ([tab] <span style="color:#f92672">.</span> <span style="color:#e6db74">&#39;company-indent-or-complete-common</span>)
</span></span><span style="display:flex;"><span>        ([f1] <span style="color:#f92672">.</span> lsp-describe-thing-at-point)
</span></span><span style="display:flex;"><span>        ([f12] <span style="color:#f92672">.</span> lsp-find-definition)
</span></span><span style="display:flex;"><span>        (<span style="color:#e6db74">&#34;S-&lt;f12&gt;&#34;</span> <span style="color:#f92672">.</span> lsp-find-references))
</span></span><span style="display:flex;"><span>  :hook
</span></span><span style="display:flex;"><span>  (go-mode <span style="color:#f92672">.</span> (lambda ()
</span></span><span style="display:flex;"><span>               (setq tab-width <span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>               (setq company-backends <span style="color:#f92672">&#39;</span>(company-capf
</span></span><span style="display:flex;"><span>                                        company-files))
</span></span><span style="display:flex;"><span>               (setq gofmt-command <span style="color:#e6db74">&#34;goimports&#34;</span>)
</span></span><span style="display:flex;"><span>               (add-hook <span style="color:#e6db74">&#39;before-save-hook</span> <span style="color:#e6db74">&#39;gofmt-before-save</span>)
</span></span><span style="display:flex;"><span>               (go-guru-hl-identifier-mode)
</span></span><span style="display:flex;"><span>               (lsp-deferred)
</span></span><span style="display:flex;"><span>               <span style="color:#75715e">;; ... more settings ...</span>
</span></span><span style="display:flex;"><span>               )))
</span></span></code></pre></div><p>This module:</p>
<ul>
<li>Loads only when editing Go files (<code>:defer t</code>)</li>
<li>Sets up keybindings specific to Go files</li>
<li>Configures language-specific tools and formatting</li>
<li>Integrates with <a href="https://en.wikipedia.org/wiki/Language_Server_Protocol">LSP</a> for auto-completions and advanced IDE features</li>
</ul>
<h3 id="utility-modules">Utility Modules</h3>
<p>Utilities enhance the core editing experience regardless of the file type you&rsquo;re editing. For example, my multiple-cursors configuration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defun _0x17de/add-multiple-cursors-to-non-empty-lines (start end)
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Add a cursor in each non-empty line of selection&#34;</span>
</span></span><span style="display:flex;"><span>  (interactive <span style="color:#e6db74">&#34;r&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">;; ... implementation ...</span>
</span></span><span style="display:flex;"><span>  )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(use-package multiple-cursors
</span></span><span style="display:flex;"><span>  :init
</span></span><span style="display:flex;"><span>  (global-unset-key (kbd <span style="color:#e6db74">&#34;M-&lt;down-mouse-1&gt;&#34;</span>))
</span></span><span style="display:flex;"><span>  :bind
</span></span><span style="display:flex;"><span>  ((<span style="color:#e6db74">&#34;C-M-z C-c&#34;</span> <span style="color:#f92672">.</span> mc/edit-lines)
</span></span><span style="display:flex;"><span>   (<span style="color:#e6db74">&#34;C-M-z C-M-c&#34;</span> <span style="color:#f92672">.</span> _0x17de/add-multiple-cursors-to-non-empty-lines)
</span></span><span style="display:flex;"><span>   (<span style="color:#e6db74">&#34;C-M-z &gt;&#34;</span> <span style="color:#f92672">.</span> mc/mark-next-like-this)
</span></span><span style="display:flex;"><span>   <span style="color:#75715e">;; ... more bindings ...</span>
</span></span><span style="display:flex;"><span>   ))
</span></span></code></pre></div><p>This module encapsulates a complete feature set with custom functions and keybindings.</p>
<h3 id="feature-flags">Feature Flags</h3>
<p>For optional features, I use custom variables as feature flags:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defcustom _0x17de/use-exwm <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Non-nil means EXWM will be enabled.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">When this option is enabled, EXWM will be loaded and configured
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">as the window manager for this session.&#34;</span>
</span></span><span style="display:flex;"><span>  :type <span style="color:#e6db74">&#39;boolean</span>
</span></span><span style="display:flex;"><span>  :group <span style="color:#e6db74">&#39;_0x17de</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(when _0x17de/use-exwm
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">;; ... EXWM configuration ...</span>
</span></span><span style="display:flex;"><span>  )
</span></span></code></pre></div><p>This allows toggling features without commenting out code or editing multiple files.</p>
<h2 id="benefits-of-this-approach">Benefits of This Approach</h2>
<p>This modular approach offers several advantages:</p>
<ol>
<li><strong>Maintainability</strong>: Each module has a single responsibility</li>
<li><strong>Performance</strong>: Lazy loading modules when needed</li>
<li><strong>Portability</strong>: Easy to share modules between configurations</li>
<li><strong>Resilience</strong>: Failures in one module don&rsquo;t break everything</li>
<li><strong>Clarity</strong>: Clear organization makes finding settings easier</li>
<li><strong>Flexibility</strong>: Enable/disable features without editing code</li>
</ol>
<h2 id="leveraging-built-in-emacs-utilities">Leveraging Built-in Emacs Utilities</h2>
<p>Emacs provides several powerful utilities that make modular configurations easier to implement and maintain.</p>
<h3 id="use-package-package-management-made-simple">use-package: Package Management Made Simple</h3>
<p>While not built into Emacs core (but bundled with Emacs 29+), <code>use-package</code> is essential for modular configurations:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(use-package python
</span></span><span style="display:flex;"><span>  :ensure <span style="color:#66d9ef">nil</span>  <span style="color:#75715e">; Don&#39;t try to install built-in packages</span>
</span></span><span style="display:flex;"><span>  :defer <span style="color:#66d9ef">t</span>     <span style="color:#75715e">; Only load when needed</span>
</span></span><span style="display:flex;"><span>  :bind (:map python-mode-map
</span></span><span style="display:flex;"><span>          ([f12] <span style="color:#f92672">.</span> lsp-find-definition)
</span></span><span style="display:flex;"><span>          (<span style="color:#e6db74">&#34;S-&lt;f12&gt;&#34;</span> <span style="color:#f92672">.</span> lsp-find-references))
</span></span><span style="display:flex;"><span>  :hook ((python-mode <span style="color:#f92672">.</span> (lambda ()
</span></span><span style="display:flex;"><span>                          (company-mode <span style="color:#66d9ef">t</span>)
</span></span><span style="display:flex;"><span>                          (flycheck-mode <span style="color:#66d9ef">t</span>)
</span></span><span style="display:flex;"><span>                          (lsp-deferred))))
</span></span><span style="display:flex;"><span>  :config     <span style="color:#75715e">; Executed after the package loads</span>
</span></span><span style="display:flex;"><span>  (setq python-indent-offset <span style="color:#ae81ff">4</span>))
</span></span></code></pre></div><p>Key <code>use-package</code> keywords:</p>
<ul>
<li><code>:ensure</code> - Controls package installation</li>
<li><code>:defer</code> - Enables lazy loading</li>
<li><code>:bind</code> - Sets up keybindings (with automatic lazy loading)</li>
<li><code>:hook</code> - Adds mode hooks (with automatic lazy loading)</li>
<li><code>:init</code> - Code executed before loading</li>
<li><code>:config</code> - Code executed after loading</li>
<li><code>:custom</code> - Sets custom variables</li>
<li><code>:commands</code> - Creates autoloaded commands (callable via M-x or keybindings)</li>
</ul>
<p>This declarative approach keeps package configuration concentrated in one place rather than scattered throughout your init file.</p>
<h3 id="defcustom-user-configurable-variables">defcustom: User-Configurable Variables</h3>
<p><code>defcustom</code> creates proper user options with documentation, custom types, and integration with Emacs&rsquo; customization interface:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(defcustom _0x17de/python-global-virtualenv-dir <span style="color:#e6db74">&#34;~/.venv&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Default directory for Python virtual environments.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">This is used by pyvenv to locate and activate virtual environments.&#34;</span>
</span></span><span style="display:flex;"><span>  :type <span style="color:#e6db74">&#39;directory</span>
</span></span><span style="display:flex;"><span>  :group <span style="color:#e6db74">&#39;_0x17de</span>
</span></span><span style="display:flex;"><span>  :safe <span style="color:#a6e22e">#&#39;stringp</span>)
</span></span></code></pre></div><p>Benefits of <code>defcustom</code> over regular variables:</p>
<ol>
<li><strong>Type checking</strong> - The <code>:type</code> property validates values</li>
<li><strong>Documentation</strong> - Self-documents the option</li>
<li><strong>UI integration</strong> - Works with <code>customize-*</code> commands</li>
<li><strong>Safety</strong> - The <code>:safe</code> property helps with security</li>
<li><strong>Organization</strong> - Options can be grouped logically</li>
</ol>
<h3 id="define-minor-mode-creating-toggleable-features">define-minor-mode: Creating Toggleable Features</h3>
<p>For modular features that can be enabled/disabled, <code>define-minor-mode</code> is perfect:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(define-minor-mode latex-compile-on-save-mode
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Refresh the preview on save&#34;</span>
</span></span><span style="display:flex;"><span>  :lighter <span style="color:#e6db74">&#34; LTeXcos&#34;</span>
</span></span><span style="display:flex;"><span>  :group latex-compile-on-save
</span></span><span style="display:flex;"><span>  (if latex-compile-on-save-mode
</span></span><span style="display:flex;"><span>      (add-hook <span style="color:#e6db74">&#39;after-save-hook</span> <span style="color:#e6db74">&#39;latex-compile-on-save--compile</span> <span style="color:#66d9ef">nil</span> <span style="color:#66d9ef">t</span>)
</span></span><span style="display:flex;"><span>    (remove-hook <span style="color:#e6db74">&#39;after-save-hook</span> <span style="color:#e6db74">&#39;latex-compile-on-save--compile</span> <span style="color:#66d9ef">t</span>)))
</span></span></code></pre></div><p>This creates a toggleable mode with:</p>
<ul>
<li>A proper minor mode that can be enabled/disabled with <code>M-x</code></li>
<li>Automatic hook management when enabled/disabled</li>
<li>Integration with the mode line via <code>:lighter</code></li>
</ul>
<h3 id="eval-after-load-targeted-configuration">eval-after-load: Targeted Configuration</h3>
<p>For smaller configurations without <code>use-package</code>, <code>eval-after-load</code> provides similar lazy-loading capabilities:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(eval-after-load <span style="color:#e6db74">&#39;python-mode</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#39;</span>(progn
</span></span><span style="display:flex;"><span>     (<span style="color:#a6e22e">define-key</span> python-mode-map (kbd <span style="color:#e6db74">&#34;C-c C-r&#34;</span>) <span style="color:#e6db74">&#39;python-shell-send-region</span>)
</span></span><span style="display:flex;"><span>     (setq python-indent-offset <span style="color:#ae81ff">4</span>)))
</span></span></code></pre></div><p>This defers execution until the specified feature is loaded, improving startup times.</p>
<h3 id="with-eval-after-load-modern-alternative">with-eval-after-load: Modern Alternative</h3>
<p>A more modern, macro-based version of <code>eval-after-load</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(with-eval-after-load <span style="color:#e6db74">&#39;org</span>
</span></span><span style="display:flex;"><span>  (setq org-hide-emphasis-markers <span style="color:#66d9ef">t</span>)
</span></span><span style="display:flex;"><span>  (add-hook <span style="color:#e6db74">&#39;org-mode-hook</span> <span style="color:#a6e22e">#&#39;</span>visual-line-mode))
</span></span></code></pre></div><p>This is cleaner than <code>eval-after-load</code> because it doesn&rsquo;t require quoting the body.</p>
<h3 id="autoload-manual-lazy-loading">autoload: Manual Lazy Loading</h3>
<p>For maximum control over lazy loading:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span><span style="color:#75715e">;;;###autoload</span>
</span></span><span style="display:flex;"><span>(defun my/open-config-file ()
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;Open the main configuration file.&#34;</span>
</span></span><span style="display:flex;"><span>  (interactive)
</span></span><span style="display:flex;"><span>  (find-file (<span style="color:#a6e22e">expand-file-name</span> <span style="color:#e6db74">&#34;_0x17de-emacs.el&#34;</span> _0x17de/load-path)))
</span></span></code></pre></div><p>The <code>;;;###autoload</code> cookie tells Emacs to make the function available without loading the entire file.</p>
<h2 id="implementation-tips">Implementation Tips</h2>
<p>If you want to build a similar configuration:</p>
<ol>
<li><strong>Start small</strong>: Begin with a single file and add more as needed</li>
<li><strong>Use consistent naming</strong>: Establish a convention for module files</li>
<li><strong>Leverage use-package</strong>: For declarative, lazy-loaded package configuration</li>
<li><strong>Add error handling</strong>: Ensure graceful recovery from failures</li>
<li><strong>Document with defcustom</strong>: For user-facing configuration options</li>
<li><strong>Use minor modes</strong>: For toggleable features</li>
<li><strong>Embrace lazy loading</strong>: With autoload, eval-after-load, and with-eval-after-load</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>A modular Emacs configuration significantly improves maintainability and flexibility. By separating concerns into discrete modules with clear responsibilities, you can build a robust, personalized environment that evolves with your needs while remaining manageable.</p>
<p>This approach has served me well, allowing my configuration to grow from a few basic settings to a comprehensive development environment without becoming unwieldy. The time invested in proper organization pays dividends in the long term through improved stability and ease of modification.</p>
<p>Feel free to adapt this structure to your own needs or explore my complete configuration for more ideas and inspiration.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Puppet vs. Ansible: Game-Changing Differences in Modern Infrastructure Automation</title>
      <link>https://blog.0x17.de/post/puppet-automation-insights/</link>
      <pubDate>Mon, 31 Mar 2025 23:00:00 +0200</pubDate>
      <guid>https://blog.0x17.de/post/puppet-automation-insights/</guid>
      <description>&lt;p&gt;After working extensively with both Puppet and Ansible, I&amp;rsquo;ve discovered some powerful features in the Puppet ecosystem that deserve more attention. In this post, I&amp;rsquo;ll share my experiences with Hiera and Puppet Bolt, highlighting how they compare to Ansible&amp;rsquo;s approach.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;intro&#34; loading=&#34;lazy&#34; src=&#34;https://blog.0x17.de/post/puppet-automation-insights/intro.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;hiera-vs-ansibles-host_vars-and-group_vars&#34;&gt;Hiera vs. Ansible&amp;rsquo;s host_vars and group_vars&lt;/h2&gt;
&lt;p&gt;One of the most significant differences between Puppet and Ansible is how they handle variable hierarchies. Ansible uses &lt;code&gt;host_vars&lt;/code&gt; and &lt;code&gt;group_vars&lt;/code&gt; directories, which provide a straightforward but somewhat rigid approach to configuration data. In contrast, Puppet&amp;rsquo;s &lt;a href=&#34;https://www.puppet.com/docs/puppet/8/hiera_intro.html&#34;&gt;Hiera&lt;/a&gt; offers a much more sophisticated system.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>After working extensively with both Puppet and Ansible, I&rsquo;ve discovered some powerful features in the Puppet ecosystem that deserve more attention. In this post, I&rsquo;ll share my experiences with Hiera and Puppet Bolt, highlighting how they compare to Ansible&rsquo;s approach.</p>
<p><img alt="intro" loading="lazy" src="/post/puppet-automation-insights/intro.png"></p>
<h2 id="hiera-vs-ansibles-host_vars-and-group_vars">Hiera vs. Ansible&rsquo;s host_vars and group_vars</h2>
<p>One of the most significant differences between Puppet and Ansible is how they handle variable hierarchies. Ansible uses <code>host_vars</code> and <code>group_vars</code> directories, which provide a straightforward but somewhat rigid approach to configuration data. In contrast, Puppet&rsquo;s <a href="https://www.puppet.com/docs/puppet/8/hiera_intro.html">Hiera</a> offers a much more sophisticated system.</p>
<h3 id="why-hiera-shines">Why Hiera Shines</h3>
<p>Hiera&rsquo;s approach to configuration lookup is fundamentally different and more flexible:</p>
<ol>
<li>
<p><strong>Fact-based lookups</strong>: Hiera can use any fact about a system to determine which configuration files to load. This goes far beyond Ansible&rsquo;s group-based approach, allowing for configurations based on operating system, hardware details, custom facts, or any combination thereof.</p>
</li>
<li>
<p><strong>Hierarchical merging</strong>: Hiera doesn&rsquo;t just stop at the first match like Ansible often does. Instead, it can merge data from multiple levels of the hierarchy, allowing for elegant inheritance patterns.</p>
</li>
<li>
<p><strong>Lookup specificity</strong>: When Hiera finds a key at one level of the hierarchy, it doesn&rsquo;t override it with values from less specific levels. This &ldquo;first match wins&rdquo; approach ensures that the most specific configuration always takes precedence.</p>
</li>
</ol>
<p>This flexibility makes Hiera an incredibly powerful tool for managing configurations across diverse environments. For instance, you can define a base configuration that applies to all systems, override specific settings for a particular OS, and then further customize for individual hosts—all without repetition.</p>
<h3 id="hiera-in-action-a-real-world-example">Hiera in Action: A Real-World Example</h3>
<p>Let&rsquo;s look at an actual Hiera configuration to understand its power (example taken from the <a href="https://www.puppet.com/docs/puppet/8/hiera_intro.html">official Puppet documentation</a>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">version</span>: <span style="color:#ae81ff">5</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">defaults</span>:  <span style="color:#75715e"># Used for any hierarchy level that omits these keys.</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">datadir</span>: <span style="color:#ae81ff">data        </span> <span style="color:#75715e"># This path is relative to hiera.yaml&#39;s directory.</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">data_hash</span>: <span style="color:#ae81ff">yaml_data </span> <span style="color:#75715e"># Use the built-in YAML backend.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">hierarchy</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Per-node data&#34;</span>                   <span style="color:#75715e"># Human-readable name.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;nodes/%{trusted.certname}.yaml&#34;</span>  <span style="color:#75715e"># File path, relative to datadir.</span>
</span></span><span style="display:flex;"><span>                                   <span style="color:#75715e"># ^^^ IMPORTANT: include the file extension!</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Per-datacenter business group data&#34;</span> <span style="color:#75715e"># Uses custom facts.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;location/%{facts.whereami}/%{facts.group}.yaml&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Global business group data&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;groups/%{facts.group}.yaml&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Per-datacenter secret data (encrypted)&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">lookup_key</span>: <span style="color:#ae81ff">eyaml_lookup_key  </span> <span style="color:#75715e"># Uses non-default backend.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;secrets/%{facts.whereami}.eyaml&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">options</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">pkcs7_private_key</span>: <span style="color:#ae81ff">/etc/puppetlabs/puppet/eyaml/private_key.pkcs7.pem</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">pkcs7_public_key</span>:  <span style="color:#ae81ff">/etc/puppetlabs/puppet/eyaml/public_key.pkcs7.pem</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Per-OS defaults&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;os/%{facts.os.family}.yaml&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Common data&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;common.yaml&#34;</span>
</span></span></code></pre></div><p>This configuration demonstrates several key strengths of Hiera:</p>
<ol>
<li>
<p><strong>Multiple layers of specificity</strong>: The hierarchy starts with node-specific data (<code>nodes/%{trusted.certname}.yaml</code>), then moves through various levels of abstraction (datacenter + business group, business group alone, datacenter-specific secrets), down to OS-specific settings, and finally to common data that applies to all systems.</p>
</li>
<li>
<p><strong>Dynamic path generation</strong>: Notice how paths include variables like <code>%{trusted.certname}</code>, <code>%{facts.whereami}</code>, and <code>%{facts.os.family}</code>. These are interpolated with actual system facts at runtime, creating a dynamic configuration system that adapts to each node.</p>
</li>
<li>
<p><strong>Mixed backend support</strong>: While most data comes from YAML files, the configuration demonstrates how you can use different backends (like the encrypted <code>eyaml_lookup_key</code>) for sensitive data, with backend-specific options.</p>
</li>
<li>
<p><strong>Clear progression</strong>: When Hiera looks up a value, it starts at the top of this hierarchy and works its way down. Once it finds a value, it stops looking (unless you&rsquo;re using a merge strategy). This means more specific configurations naturally override more general ones.</p>
</li>
</ol>
<p>In Ansible, achieving this level of configuration flexibility would require complex variable precedence rules and possibly custom plugins. Hiera makes it declarative and straightforward.</p>
<h2 id="puppet-bolt-a-fresh-approach-to-remote-execution">Puppet Bolt: A Fresh Approach to Remote Execution</h2>
<p><a href="https://www.puppet.com/docs/bolt/latest/bolt">Puppet Bolt</a> is another gem in the Puppet ecosystem that offers an interesting alternative to Ansible for ad-hoc tasks and orchestration.</p>
<h3 id="speed-and-flexibility">Speed and Flexibility</h3>
<p>Bolt takes a different approach to remote execution that brings both advantages and limitations:</p>
<ul>
<li>
<p><strong>Script-based execution</strong>: While Ansible relies heavily on Python modules, Bolt embraces a more universal approach by simply executing scripts on remote systems. This might seem less sophisticated at first, but it brings remarkable flexibility.</p>
</li>
<li>
<p><strong>Language agnosticism</strong>: Bolt doesn&rsquo;t care what language your script is written in. Bash, Python, PowerShell, Ruby—it&rsquo;s all the same to Bolt. This removes the dependency on specific Python versions that can sometimes complicate Ansible deployments.</p>
</li>
<li>
<p><strong>Simplicity in execution</strong>: Sometimes a simple bash script is the easiest solution to a problem. Bolt acknowledges this reality and makes it trivial to run whatever makes the most sense for a given task.</p>
</li>
</ul>
<h3 id="limitations-and-considerations">Limitations and Considerations</h3>
<p>Despite its strengths, Bolt isn&rsquo;t without challenges:</p>
<ul>
<li>
<p><strong>JSON string conversion</strong>: One frustration I&rsquo;ve encountered is that converting JSON strings to objects isn&rsquo;t natively supported. You either need to use modules or extend Bolt with custom functions. If you&rsquo;re not interested in diving into Ruby or managing additional module dependencies, this can be a drawback.</p>
</li>
<li>
<p><strong>Less granular modules</strong>: We do lose some of the fine-grained task-level module support that Ansible provides. This is the trade-off for Bolt&rsquo;s flexibility.</p>
</li>
</ul>
<h3 id="interactive-use-and-documentation">Interactive Use and Documentation</h3>
<p>Where Bolt really shines is in its usability:</p>
<ul>
<li>
<p><strong>Standalone operation</strong>: Using Bolt without the broader Puppet ecosystem works perfectly fine, making it accessible even if you&rsquo;re not fully committed to Puppet.</p>
</li>
<li>
<p><strong>Interactive experience</strong>: Bolt feels designed for more interactive use than Ansible, with features like the <code>prompt::menu</code> function that provides interactive dropdown options, allowing users to make selections directly in the terminal during plan execution.</p>
</li>
<li>
<p><strong>Superior plan discovery</strong>: The <code>bolt plan show</code> command provides a clean, organized list of all available plans. When viewing a specific plan, Bolt displays detailed documentation that&rsquo;s easily added through comments in the code.</p>
</li>
<li>
<p><strong>Task targeting flexibility</strong>: Plans in Bolt are conceptually similar to Ansible playbooks, but with a key difference in execution model. In Bolt, each task within a plan can target a different set of hosts, giving you greater flexibility. This contrasts with Ansible, where a play defines a list of tasks that all run against the same list of hosts. This distinction makes Bolt plans more adaptable when you need different actions on different host groups within the same workflow.</p>
<p>Here&rsquo;s a simple example showing how Bolt plans can target different sets of hosts for different tasks:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-puppet" data-lang="puppet"><span style="display:flex;"><span><span style="color:#a6e22e">plan</span> <span style="color:#a6e22e">my_module</span>::<span style="color:#a6e22e">my_deployment</span>(<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">TargetSpec</span> $web_servers,<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">TargetSpec</span> $db_servers,<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">String</span> $version<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>) {<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">  # First, update the database servers
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">  # Using catch_errors to prevent plan failure on error</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>  $db_results <span style="color:#f92672">=</span> <span style="color:#a6e22e">run_task</span>(<span style="color:#e6db74">&#39;database::update&#39;</span>, $db_servers, { <span style="color:#e6db74">&#39;version&#39;</span> <span style="color:#f92672">=&gt;</span> $version }, <span style="color:#a6e22e">_catch_errors</span> <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">true</span>)<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">  # Check if database update was successful</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> $db_results.ok {<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Run a simple command on the web servers to verify they&#39;re online</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    $web_status <span style="color:#f92672">=</span> <span style="color:#a6e22e">run_command</span>(<span style="color:#e6db74">&#39;systemctl status nginx&#39;</span>, $web_servers)<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Deploy the new application version to web servers</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">run_task</span>(<span style="color:#e6db74">&#39;webapp::deploy&#39;</span>, $web_servers, {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#39;version&#39;</span> <span style="color:#f92672">=&gt;</span> $version,<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#39;db_endpoint&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">get_db_endpoint</span>($db_results)<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    })<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Send success notification</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">run_task</span>(<span style="color:#e6db74">&#39;notification::send&#39;</span>, <span style="color:#e6db74">&#39;monitoring.example.com&#39;</span>, {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#39;message&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#34;Deployment of version ${version} completed successfully&#34;</span>,<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#39;status&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;success&#39;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    })<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">else</span> {<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Send failure notification with details</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    $error_msg <span style="color:#f92672">=</span> $db_results.error_set.first.error.message<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">run_task</span>(<span style="color:#e6db74">&#39;notification::send&#39;</span>, <span style="color:#e6db74">&#39;monitoring.example.com&#39;</span>, {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#39;message&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#34;Deployment of version ${version} failed during database update: ${error_msg}&#34;</span>,<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#39;status&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;failure&#39;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    })<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Log details for investigation</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">out</span>::<span style="color:#a6e22e">message</span>(<span style="color:#e6db74">&#34;Database update failed: ${error_msg}&#34;</span>)<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Raise an exception that the plan failed</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fail_plan</span>(<span style="color:#e6db74">&#34;Deployment failed at database stage&#34;</span>)<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>  }<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>Notice how <code>run_task()</code> and <code>run_command()</code> take a target parameter (<code>$db_servers</code>, <code>$web_servers</code>, or even a specific hostname), allowing each operation to be directed at precisely the right group of hosts. This syntax <code>run_task($task_name, $targets, $args)</code> makes the targeting explicit and flexible.</p>
</li>
</ul>
<h3 id="plan-functions-and-built-in-capabilities">Plan Functions and Built-in Capabilities</h3>
<p>Bolt comes with a rich set of built-in plan functions that extend its capabilities. The <a href="https://www.puppet.com/docs/bolt/latest/plan_functions">Bolt Plan Functions documentation</a> provides a comprehensive reference of these functions, including file manipulation, error handling, and interactive prompts like <code>prompt::menu</code>. These functions make Bolt plans powerful and flexible without requiring external dependencies.</p>
<h3 id="parameter-handling-and-variable-scope">Parameter Handling and Variable Scope</h3>
<p>The way Bolt handles parameters and variables feels more like proper programming:</p>
<ul>
<li>
<p><strong>Function-like parameters</strong>: Plans have proper parameters, similar to functions in programming languages, with validation checks at the beginning of execution.</p>
</li>
<li>
<p><strong>Cleaner than Ansible&rsquo;s <code>-e</code></strong>: Compared to the common practice of injecting variables with <code>-e</code> in Ansible, Bolt&rsquo;s approach feels more correct and maintainable. It asserts that all arguments are defined and used properly.</p>
</li>
<li>
<p><strong>Sensible variable scoping</strong>: Ansible&rsquo;s variable precedence can sometimes lead to confusion, with <code>-e</code> variables overriding <code>set_fact</code> values in ways that might be unexpected. Bolt follows traditional programming scopes, where variables have clear context and shouldn&rsquo;t be redeclared.</p>
</li>
</ul>
<h3 id="inventory-handling">Inventory Handling</h3>
<p>Like Ansible, Bolt supports dynamic inventories:</p>
<ul>
<li>
<p><strong>Language flexibility</strong>: Again embracing its language-agnostic approach, Bolt allows you to write dynamic inventories in any programming language. The only requirement is that they return properly formatted JSON.</p>
</li>
<li>
<p><strong>Task-based inventory sources</strong>: Bolt makes it incredibly simple to use a task as an inventory source. As documented in the <a href="https://www.puppet.com/docs/bolt/latest/bolt_inventory_reference#example-file">Bolt inventory reference</a>, you can create a dynamic inventory by simply adding a <code>_plugin: task</code> key at the root level of your inventory file, and then specifying a <code>task</code> key with the name of a module::task to execute. This task returns the inventory data, making it easy to generate target lists from external sources like cloud providers, CMDBs, or custom databases.</p>
<p>Here&rsquo;s a minimal example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># inventory.yaml</span>
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">_plugin</span>: <span style="color:#ae81ff">task</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">task</span>: <span style="color:#ae81ff">my_module::get_targets</span>
</span></span></code></pre></div><p>This simple configuration tells Bolt to execute the <code>my_module::get_targets</code> task and use its output as the inventory for the current run.</p>
</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>While Ansible remains a popular and powerful tool, my experience with Puppet&rsquo;s Hiera and Bolt has revealed significant advantages in certain scenarios. Hiera&rsquo;s sophisticated data hierarchy provides exceptional flexibility for configuration management, while Bolt offers a refreshing approach to remote execution that balances simplicity with power.</p>
<p><img alt="conclusion" loading="lazy" src="/post/puppet-automation-insights/conclusion.png"></p>
<p>If you&rsquo;re deep in the Ansible ecosystem, it&rsquo;s still valuable to understand what benefits other tools can bring. Staying open-minded about different approaches helps broaden your technical perspective, can lead to fresh ideas for solving problems, and ultimately makes you a more versatile infrastructure developer. Even if you don&rsquo;t switch tools, exploring alternative design philosophies can inspire creative solutions and innovations in your own workflows and automation strategies.</p>
]]></content:encoded>
    </item>
    <item>
      <title>SaltStack from Zero to Efficient: A Practical Journey</title>
      <link>https://blog.0x17.de/post/saltstack-from-zero-to-efficient/</link>
      <pubDate>Sun, 30 Mar 2025 00:00:00 +0200</pubDate>
      <guid>https://blog.0x17.de/post/saltstack-from-zero-to-efficient/</guid>
      <description>&lt;p&gt;Let&amp;rsquo;s face it: infrastructure management can be tedious. But what if I told you that &lt;a href=&#34;https://saltproject.io/&#34;&gt;SaltStack&lt;/a&gt; has some seriously useful features that can transform your experience from mundane to efficient?&lt;/p&gt;
&lt;p&gt;While many configuration management tools like Ansible, Chef, or Puppet offer similar functionality, SaltStack distinguishes itself through its powerful &lt;a href=&#34;https://docs.saltproject.io/en/latest/topics/event/index.html&#34;&gt;event-driven architecture&lt;/a&gt;, &lt;a href=&#34;https://docs.saltproject.io/en/latest/topics/topology/index.html&#34;&gt;efficient minion communication&lt;/a&gt;, and &lt;a href=&#34;https://docs.saltproject.io/en/latest/topics/jinja/index.html&#34;&gt;exceptional templating capabilities&lt;/a&gt;. These features make it particularly well-suited for managing complex, dynamic infrastructure at scale.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;intro&#34; loading=&#34;lazy&#34; src=&#34;https://blog.0x17.de/post/saltstack-from-zero-to-efficient/intro.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;In this article, I&amp;rsquo;ll show you how to make SaltStack more pleasant to use, establish a proper development workflow for modules, and maintain a clean main branch while doing so.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Let&rsquo;s face it: infrastructure management can be tedious. But what if I told you that <a href="https://saltproject.io/">SaltStack</a> has some seriously useful features that can transform your experience from mundane to efficient?</p>
<p>While many configuration management tools like Ansible, Chef, or Puppet offer similar functionality, SaltStack distinguishes itself through its powerful <a href="https://docs.saltproject.io/en/latest/topics/event/index.html">event-driven architecture</a>, <a href="https://docs.saltproject.io/en/latest/topics/topology/index.html">efficient minion communication</a>, and <a href="https://docs.saltproject.io/en/latest/topics/jinja/index.html">exceptional templating capabilities</a>. These features make it particularly well-suited for managing complex, dynamic infrastructure at scale.</p>
<p><img alt="intro" loading="lazy" src="/post/saltstack-from-zero-to-efficient/intro.png"></p>
<p>In this article, I&rsquo;ll show you how to make SaltStack more pleasant to use, establish a proper development workflow for modules, and maintain a clean main branch while doing so.</p>
<h2 id="integrating-git-with-salt-for-better-version-control">Integrating Git with Salt for Better Version Control</h2>
<p>Imagine having your entire infrastructure configuration tracked, versioned, and deployable with the same tools you use for application code. This isn&rsquo;t just possible with Salt—it&rsquo;s seamless.</p>
<p>Instead of manually copying files to your Salt master or using rsync jobs, GitFS allows you to use Git repositories directly as your source of truth for states and pillar data. This means automatic versioning, change tracking, and the ability to roll back to any previous state of your infrastructure. For configuration options and credential management, see the official <a href="https://docs.saltproject.io/en/latest/topics/tutorials/gitfs.html">documentation on using GitFS</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># Salt master configuration for GitFS integration</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">fileserver_backend</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">gitfs                      </span> <span style="color:#75715e"># Enable the GitFS backend</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">gitfs_provider</span>: <span style="color:#ae81ff">gitpython      </span> <span style="color:#75715e"># Use GitPython for Git operations</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">gitfs_base</span>: <span style="color:#ae81ff">main               </span> <span style="color:#75715e"># Default branch to use</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">gitfs_update_interval</span>: <span style="color:#ae81ff">60</span>       <span style="color:#75715e"># Check for updates every 60 seconds</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">git_pillar_provider</span>: <span style="color:#ae81ff">gitpython </span> <span style="color:#75715e"># Use GitPython for pillar as well</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">git_pillar_update_interval</span>: <span style="color:#ae81ff">60</span>  <span style="color:#75715e"># Check for pillar updates every 60 seconds</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">git_pillar_base</span>: <span style="color:#ae81ff">main          </span> <span style="color:#75715e"># Default branch for pillar data</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">gitfs_remotes</span>:
</span></span><span style="display:flex;"><span>   - <span style="color:#f92672">ssh://git@gitlab.example.com/username/salt-control.git</span>:
</span></span><span style="display:flex;"><span>     - <span style="color:#f92672">root</span>: <span style="color:#ae81ff">state             </span> <span style="color:#75715e"># Root directory for state files in the repo</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">ext_pillar</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">git</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">__env__ ssh://git@gitlab.example.com/username/salt-control.git</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">root</span>: <span style="color:#ae81ff">pillar           </span> <span style="color:#75715e"># Root directory for pillar data in the repo</span>
</span></span></code></pre></div><p>With this setup, your entire infrastructure becomes a Git repository. Every change is tracked. Every deployment is versioned. Every rollback is a simple <code>git revert</code> away.</p>
<h3 id="understanding-the-__env__-parameter">Understanding the <code>__env__</code> Parameter</h3>
<p>The <code>__env__</code> parameter in the Git pillar configuration serves an important role in environment management. When Salt executes, it replaces <code>__env__</code> with the current environment name (like &ldquo;prod&rdquo;, &ldquo;dev&rdquo;, or a Git branch name), automatically ensuring your pillar data matches your state files.</p>
<p>This parameter enables consistent environment isolation by:</p>
<ul>
<li>Matching pillar data to the same environment as your state files</li>
<li>Preventing accidental deployment of development configurations to production</li>
<li>Enabling branch-specific pillar data that stays synchronized with your state files</li>
</ul>
<p>For example, when you run <code>salt '*' state.apply saltenv=feature-branch</code>, the <code>__env__</code> parameter ensures that pillar data from the <code>feature-branch</code> branch is used, maintaining perfect consistency between your states and configuration.</p>
<p>This environment separation is crucial for maintaining a reliable configuration deployment process and preventing environment-specific data from being applied to the wrong targets.</p>
<h3 id="overcoming-gitfs-synchronization-challenges">Overcoming GitFS Synchronization Challenges</h3>
<p>While GitFS is powerful, it&rsquo;s important to note that synchronization can be challenging. Salt has a documented issue (<a href="https://github.com/saltstack/salt/issues/66793">Salt Issue #66793</a>) that makes it difficult to update the fileserver from Salt orchestration.</p>
<p>I&rsquo;ve developed a custom solution that addresses this issue without requiring any patches to Salt&rsquo;s code. Here&rsquo;s my implementation, which is derived from Salt&rsquo;s own <a href="https://github.com/saltstack/salt/blob/develop/salt/runners/fileserver.py">fileserver.py update function</a> but modified to work in orchestration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># /my/salt/repo/states/_runners/fileserver_hotfix.py</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> salt.fileserver
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">update</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    Update the Salt fileserver cache.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    my_opts <span style="color:#f92672">=</span> dict(__opts__)
</span></span><span style="display:flex;"><span>    my_opts<span style="color:#f92672">.</span>pop(<span style="color:#e6db74">&#39;__pub_user&#39;</span>, <span style="color:#66d9ef">None</span>)  <span style="color:#75715e"># Remove the problematic key</span>
</span></span><span style="display:flex;"><span>    fs <span style="color:#f92672">=</span> salt<span style="color:#f92672">.</span>fileserver<span style="color:#f92672">.</span>Fileserver(my_opts)
</span></span><span style="display:flex;"><span>    fs<span style="color:#f92672">.</span>update()
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">True</span>
</span></span></code></pre></div><p>This simple module removes the <code>__pub_user</code> key that causes the issue. After committing and pushing this file, synchronize your GitFS:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>salt-run fileserver.update
</span></span><span style="display:flex;"><span>salt-run saltutil.sync_runners
</span></span></code></pre></div><p>Now, you can create an orchestration state file that handles the complete synchronization process:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># /my/salt/repo/states/orch/sync_all.sls</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">update_fileserver</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">salt.runner</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">fileserver_hotfix.update</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">update_git_pillar</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">salt.runner</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">git_pillar.update</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">require</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">salt</span>: <span style="color:#ae81ff">update_fileserver</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">sync_all_modules</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">salt.function</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">saltutil.sync_all</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">tgt</span>: <span style="color:#e6db74">&#39;*&#39;</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">tgt_type</span>: <span style="color:#ae81ff">glob</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">require</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">salt</span>: <span style="color:#ae81ff">update_git_pillar</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">refresh_pillar</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">salt.function</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">saltutil.refresh_pillar</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">tgt</span>: <span style="color:#e6db74">&#39;*&#39;</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">tgt_type</span>: <span style="color:#ae81ff">glob</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">require</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">salt</span>: <span style="color:#ae81ff">sync_all_modules</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">update_mine</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">salt.function</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">mine.update</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">tgt</span>: <span style="color:#e6db74">&#39;*&#39;</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">tgt_type</span>: <span style="color:#ae81ff">glob</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">require</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">salt</span>: <span style="color:#ae81ff">refresh_pillar</span>
</span></span></code></pre></div><p>After committing and synchronizing again, you can <a href="https://docs.saltproject.io/salt/user-guide/en/latest/topics/runners-orchestration.html">run this orchestration</a> to update everything:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>salt-run state.orchestrate orch.sync_all
</span></span></code></pre></div><p>This approach ensures that your GitFS, pillar data, and mine information stay in sync, resolving one of the more challenging aspects of working with Git and Salt.</p>
<p>Note that in high-availability setups with multiple Salt masters, you may need to adjust this approach to ensure all masters are synchronized properly. Consider adding a salt-run command that targets all masters in such scenarios.</p>
<h2 id="repository-organization-single-vs-multiple">Repository Organization: Single vs. Multiple</h2>
<p>&ldquo;Should I keep my states and pillars in the same repository or separate them?&rdquo; (See <a href="https://docs.saltproject.io/en/latest/topics/best_practices.html">Salt&rsquo;s best practices</a> for more guidance.)</p>
<p>This is a common question in the Salt community, and there are compelling arguments for both approaches:</p>
<h3 id="the-single-repository-approach">The Single-Repository Approach</h3>
<p>When your states and pillars live together:</p>
<ul>
<li>Changes to your infrastructure and its configuration happen in one atomic commit</li>
<li>Your Git history tells a complete story of how your infrastructure evolved</li>
<li>You can make sweeping changes across your entire system without fear of misalignment</li>
<li>Testing becomes dramatically simpler since everything moves together</li>
</ul>
<p><strong>Example scenario</strong>: A team managing a consistent application stack across multiple environments (development, staging, production) would benefit from a single repository. When updating the application&rsquo;s configuration, they can change both the state files (how the application is installed and configured) and the pillar data (environment-specific variables like database credentials) in a single commit, ensuring everything stays in sync.</p>
<h3 id="when-to-use-multiple-repositories">When to Use Multiple Repositories</h3>
<ul>
<li>When different teams own the infrastructure code versus the configuration data</li>
<li>When your security requirements demand stricter access controls for sensitive data</li>
<li>When your pillar data changes frequently, but your states are relatively stable</li>
</ul>
<p><strong>Example scenario</strong>: An organization where the security team manages credentials and sensitive configuration while the operations team manages infrastructure code. In this case, keeping pillar data in a separate repository with stricter access controls allows the security team to update credentials without requiring changes to the infrastructure code, while still leveraging the same deployment mechanisms.</p>
<p>The beauty of Salt is that it supports both approaches equally well. You can start with a single repository and split later if needed, or vice versa.</p>
<h2 id="effective-testing-with-saltenv">Effective Testing with SaltEnv</h2>
<p>Here&rsquo;s where Salt really shines compared to other configuration management tools: the ability to test changes in complete isolation using Git branches.</p>
<h3 id="before-traditional-testing-workflow">Before: Traditional Testing Workflow</h3>
<ol>
<li>Develop changes locally</li>
<li>Push to testing environment</li>
<li>Test changes</li>
<li>If issues arise, revert changes or fix in place</li>
<li>Deploy to production</li>
</ol>
<h3 id="after-salt-branch-based-testing-workflow">After: Salt Branch-Based Testing Workflow</h3>
<ol>
<li>Create a feature branch in your Git repository</li>
<li>Develop and commit changes</li>
<li>Push the branch to your Git remote</li>
<li>Test using the branch name as the environment:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>salt <span style="color:#e6db74">&#39;*&#39;</span> state.apply saltenv<span style="color:#f92672">=</span>new-feature
</span></span></code></pre></div></li>
<li>Iterate on the branch until everything works</li>
<li>Merge to main branch only when fully tested</li>
</ol>
<p>This command tells Salt to use the <code>new-feature</code> branch for both your state files and pillar data, completely isolated from your production environment. It&rsquo;s like having a parallel universe where you can experiment freely without fear of breaking production.</p>
<p>For even more focused testing, you can apply a <a href="https://docs.saltproject.io/en/3006/ref/modules/all/salt.modules.state.html#salt.modules.state.sls">single state file</a> instead of running the entire top.sls configuration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>salt <span style="color:#e6db74">&#39;*&#39;</span> state.sls mystate saltenv<span style="color:#f92672">=</span>new-feature
</span></span></code></pre></div><p>This allows you to test only the specific component you&rsquo;re modifying, making the testing process faster and more targeted.</p>
<h3 id="the-test-mode-simulate-before-you-apply">The Test Mode: Simulate Before You Apply</h3>
<p>One of Salt&rsquo;s most valuable features for testing is the <code>test=true</code> parameter. This parameter performs a dry run of your state execution, showing you exactly what would change without actually making any modifications to your systems.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>salt <span style="color:#e6db74">&#39;*&#39;</span> state.sls mystate test<span style="color:#f92672">=</span>true
</span></span></code></pre></div><p>With <code>test=true</code>, Salt will:</p>
<ul>
<li>Connect to your target systems</li>
<li>Load all state files and pillar data</li>
<li>Check current system state against desired state</li>
<li>Report what would change (added, modified, removed)</li>
<li>Exit without making any actual changes</li>
</ul>
<p>For more complex state files, you can combine <code>test=true</code> with your branch testing:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>salt <span style="color:#e6db74">&#39;*&#39;</span> state.sls mystate saltenv<span style="color:#f92672">=</span>new-feature test<span style="color:#f92672">=</span>true
</span></span></code></pre></div><p>This powerful combination lets you:</p>
<ol>
<li>Test against an isolated Git branch without affecting production code</li>
<li>Simulate the execution without making actual changes</li>
<li>Verify that your states target the correct systems</li>
<li>Confirm that the changes match your expectations</li>
</ol>
<p>The test mode is particularly valuable when working with destructive operations or when deploying to critical production systems, as it provides an extra layer of verification before committing to changes. Once you&rsquo;re confident in the changes, you can run the same command without the <code>test=true</code> parameter to apply them for real.</p>
<p>With <a href="https://docs.saltproject.io/en/latest/ref/configuration/master.html#pillarenv-from-saltenv"><code>pillarenv_from_saltenv: True</code></a> in your configuration, Salt automatically keeps your test data synchronized with your test code, ensuring consistent testing across environments.</p>
<p>This approach allows for thorough testing of complex infrastructure changes before merging to the main branch, significantly reducing the risk of issues in production environments.</p>
<h2 id="essential-debugging-tools-for-salt-states">Essential Debugging Tools for Salt States</h2>
<p>Ever wished you could see exactly what Salt is thinking? You can, with these debugging superpowers:</p>
<h3 id="show_sls---peek-behind-the-curtain"><a href="https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.state.html#salt.modules.state.show_sls"><code>show_sls</code></a> - Peek Behind the Curtain</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>salt <span style="color:#e6db74">&#39;*&#39;</span> state.show_sls my_state
</span></span></code></pre></div><p>This reveals the raw SLS data structure after Salt has processed it, letting you verify your states are defined correctly.</p>
<p>Example output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">my_host</span>:
</span></span><span style="display:flex;"><span>    ----------
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">nginx</span>:
</span></span><span style="display:flex;"><span>        ----------
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">__env__</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">base</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">__sls__</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">pkg</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">|_</span>
</span></span><span style="display:flex;"><span>              ----------
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">name</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#ae81ff">installed</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ae81ff">|_</span>
</span></span><span style="display:flex;"><span>              ----------
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">order</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#ae81ff">10019</span>
</span></span></code></pre></div><h3 id="show_low_sls---see-the-final-plan"><a href="https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.state.html#salt.modules.state.show_low_sls"><code>show_low_sls</code></a> - See the Final Plan</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>salt <span style="color:#e6db74">&#39;*&#39;</span> state.show_low_sls my_state
</span></span></code></pre></div><p>The &ldquo;low state&rdquo; is Salt&rsquo;s final internal representation after all preprocessing is done. It&rsquo;s especially useful when debugging complex states with multiple includes or extensive Jinja templating because it shows exactly what Salt will execute after all rendering and inheritance has been resolved.</p>
<p>Example output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">my_host</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">|_</span>
</span></span><span style="display:flex;"><span>      ----------
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">__env__</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ae81ff">base</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">__id__</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">__sls__</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">fun</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ae81ff">installed</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ae81ff">nginx</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">order</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ae81ff">10019</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">state</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ae81ff">pkg</span>
</span></span></code></pre></div><h3 id="show_pillar---expose-all-secrets"><a href="https://docs.saltproject.io/en/latest/ref/runners/all/salt.runners.pillar.html#salt.runners.pillar.show_pillar"><code>show_pillar</code></a> - Expose All Secrets</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>salt-run pillar.show_pillar my_host
</span></span></code></pre></div><p>This displays all pillar data available to a minion, which is crucial for tracking down missing or incorrect configuration values.</p>
<p>Example output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">my_host</span>:
</span></span><span style="display:flex;"><span>    ----------
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">nginx</span>:
</span></span><span style="display:flex;"><span>        ----------
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">port</span>: <span style="color:#ae81ff">80</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">worker_processes</span>: <span style="color:#ae81ff">4</span>
</span></span></code></pre></div><h3 id="show_top---understand-the-hierarchy"><a href="https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.state.html#salt.modules.state.show_top"><code>show_top</code></a> - Understand the Hierarchy</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>salt <span style="color:#e6db74">&#39;*&#39;</span> state.show_top
</span></span></code></pre></div><p>This displays which top files are applied to the minion and in what order, helping you untangle complex state inheritance.</p>
<p>Example output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">my_host</span>:
</span></span><span style="display:flex;"><span>    ----------
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">base</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#ae81ff">ssh</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ae81ff">nginx</span>
</span></span></code></pre></div><p>These commands have saved me days of troubleshooting. When something isn&rsquo;t working as expected, I don&rsquo;t have to guess—I can see exactly what Salt sees.</p>
<h2 id="practical-jinja2-template-debugging">Practical <a href="https://docs.saltproject.io/en/latest/topics/jinja/index.html">Jinja2 Template</a> Debugging</h2>
<p>Jinja2 templates are powerful but can be frustrating when they don&rsquo;t behave as expected. Here&rsquo;s my secret weapon for debugging them:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Interactive Python shell example for debugging complex Jinja2 templates</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&gt;&gt;&gt;</span> <span style="color:#f92672">import</span> jinja2
</span></span><span style="display:flex;"><span><span style="color:#f92672">&gt;&gt;&gt;</span> context <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>     <span style="color:#e6db74">&#34;users&#34;</span>: [
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>         {<span style="color:#e6db74">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;alice&#34;</span>, <span style="color:#e6db74">&#34;groups&#34;</span>: [<span style="color:#e6db74">&#34;admin&#34;</span>, <span style="color:#e6db74">&#34;dev&#34;</span>]},
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>         {<span style="color:#e6db74">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;bob&#34;</span>, <span style="color:#e6db74">&#34;groups&#34;</span>: [<span style="color:#e6db74">&#34;dev&#34;</span>]},
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>         {<span style="color:#e6db74">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;charlie&#34;</span>, <span style="color:#e6db74">&#34;groups&#34;</span>: [<span style="color:#e6db74">&#34;ops&#34;</span>]}
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>     ]
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span> }
</span></span><span style="display:flex;"><span><span style="color:#f92672">&gt;&gt;&gt;</span> template <span style="color:#f92672">=</span> jinja2<span style="color:#f92672">.</span>Template(<span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">... {</span><span style="color:#e6db74">% f</span><span style="color:#e6db74">or user in users %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">... {</span><span style="color:#e6db74">% i</span><span style="color:#e6db74">f &#39;admin&#39; in user.groups %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">... {{ user.name }} is an admin
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">... {</span><span style="color:#e6db74">% e</span><span style="color:#e6db74">ndif %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">... {</span><span style="color:#e6db74">% e</span><span style="color:#e6db74">ndfor %}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">... &#34;&#34;&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#f92672">&gt;&gt;&gt;</span> print(template<span style="color:#f92672">.</span>render(<span style="color:#f92672">**</span>context))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>alice <span style="color:#f92672">is</span> an admin
</span></span></code></pre></div><p>Notice the extra blank lines in the output? This is where whitespace control becomes important, as we&rsquo;ll see in the next section.</p>
<p>This approach lets you test complex templates outside of Salt, iterating quickly until you get exactly what you want. I&rsquo;ve solved in minutes what would have taken hours of trial and error directly in Salt states.</p>
<h2 id="optimizing-whitespace-in-jinja2-templates">Optimizing Whitespace in Jinja2 Templates</h2>
<p>This might seem trivial, but proper whitespace handling in Jinja2 templates can make your Salt states dramatically more readable and maintainable.</p>
<p>Consider this example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-jinja" data-lang="jinja"><span style="display:flex;"><span># Users
</span></span><span style="display:flex;"><span><span style="color:#75715e">{%</span> <span style="color:#66d9ef">for</span> user <span style="color:#66d9ef">in</span> users <span style="color:#75715e">%}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{</span> user.name <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{%</span> <span style="color:#66d9ef">endfor</span> <span style="color:#75715e">%}</span>
</span></span></code></pre></div><p>The rendered output would include an extra blank line before the first user:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span># Users
</span></span><span style="display:flex;"><span>(blank line here)
</span></span><span style="display:flex;"><span>Alice
</span></span><span style="display:flex;"><span>Bob
</span></span><span style="display:flex;"><span>Charlie
</span></span></code></pre></div><p>However, with <a href="https://jinja.palletsprojects.com/en/3.1.x/templates/#whitespace-control"><code>{%-</code> syntax</a> (note the dash):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-jinja" data-lang="jinja"><span style="display:flex;"><span># Users
</span></span><span style="display:flex;"><span><span style="color:#75715e">{%</span>- <span style="color:#66d9ef">for</span> user <span style="color:#66d9ef">in</span> users <span style="color:#75715e">%}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{</span> user.name <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{%</span>- <span style="color:#66d9ef">endfor</span> <span style="color:#75715e">%}</span>
</span></span></code></pre></div><p>The output is cleaner without the extra empty line:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span># Users
</span></span><span style="display:flex;"><span>Alice
</span></span><span style="display:flex;"><span>Bob
</span></span><span style="display:flex;"><span>Charlie
</span></span></code></pre></div><p>This might seem like a small detail, but when you&rsquo;re working with complex, nested templates, proper whitespace control becomes essential for maintaining sanity.</p>
<h2 id="conclusion-making-infrastructure-management-more-efficient">Conclusion: Making Infrastructure Management More Efficient</h2>
<p>After using Salt extensively, these features have transformed infrastructure management from a challenging task into something much more manageable and satisfying. The combination of Git integration, environment isolation, robust debugging tools, and a methodology for testing Jinja templating creates a process that&rsquo;s not just efficient but actually enjoyable.</p>
<p>The next time you approach an infrastructure change, remember these Salt features. They can significantly improve your configuration management experience.</p>
<p><img alt="conclusion" loading="lazy" src="/post/saltstack-from-zero-to-efficient/conclusion.png"></p>
<p>As DevOps practices continue to evolve toward more <a href="https://www.gitops.tech/">GitOps-focused workflows</a> and infrastructure-as-code becomes the standard, mastering these Salt techniques positions you well for the future. The ability to test infrastructure changes in isolation while maintaining strict version control aligns perfectly with modern continuous integration and delivery practices.</p>
<p>Try them out, and you might find that Salt becomes an essential tool in your DevOps toolkit.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Getting Started with Emacs Lisp: Core Concepts Explained</title>
      <link>https://blog.0x17.de/post/emacs-list-introduction/</link>
      <pubDate>Sat, 29 Mar 2025 00:00:00 +0100</pubDate>
      <guid>https://blog.0x17.de/post/emacs-list-introduction/</guid>
      <description>&lt;p&gt;Emacs Lisp (often abbreviated as Elisp) is the programming language that powers the extensibility of the Emacs text editor. It&amp;rsquo;s a dialect of Lisp that has been adapted specifically for text editing tasks. While it might look unusual at first glance, understanding a few key concepts opens up a world of possibilities for customizing your Emacs experience.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;elisp&#34; loading=&#34;lazy&#34; src=&#34;https://blog.0x17.de/post/emacs-list-introduction/elisp.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;In this article, we&amp;rsquo;ll explore some of the fundamental concepts in Emacs Lisp that often confuse newcomers.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Emacs Lisp (often abbreviated as Elisp) is the programming language that powers the extensibility of the Emacs text editor. It&rsquo;s a dialect of Lisp that has been adapted specifically for text editing tasks. While it might look unusual at first glance, understanding a few key concepts opens up a world of possibilities for customizing your Emacs experience.</p>
<p><img alt="elisp" loading="lazy" src="/post/emacs-list-introduction/elisp.png"></p>
<p>In this article, we&rsquo;ll explore some of the fundamental concepts in Emacs Lisp that often confuse newcomers.</p>
<h2 id="variables-global-vs-local-scope">Variables: Global vs Local Scope</h2>
<p>Elisp provides several ways to define variables, each with different scoping behaviors.</p>
<h3 id="setq-global-variables"><code>setq</code>: Global Variables</h3>
<p>The <code>setq</code> function is used to define and set global variables:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(setq my-variable <span style="color:#e6db74">&#34;Hello, world!&#34;</span>)
</span></span></code></pre></div><p>These variables are accessible from anywhere in your Emacs session once defined. They&rsquo;re ideal for configuration settings or values that need to persist.</p>
<h3 id="let-and-let-local-variables"><code>let</code> and <code>let*</code>: Local Variables</h3>
<p>For variables with limited scope, Elisp provides <code>let</code> and <code>let*</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span><span style="color:#75715e">;; Using let for local binding</span>
</span></span><span style="display:flex;"><span>(let ((x <span style="color:#ae81ff">5</span>)
</span></span><span style="display:flex;"><span>      (y <span style="color:#ae81ff">10</span>))
</span></span><span style="display:flex;"><span>  (<span style="color:#a6e22e">message</span> <span style="color:#e6db74">&#34;Sum: %d&#34;</span> (<span style="color:#a6e22e">+</span> x y)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">;; x and y are not accessible here</span>
</span></span></code></pre></div><p>The difference between <code>let</code> and <code>let*</code> is significant:</p>
<ul>
<li><code>let</code> evaluates all variable expressions before binding</li>
<li><code>let*</code> evaluates in sequence, allowing later variables to reference earlier ones</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span><span style="color:#75715e">;; This won&#39;t work with let</span>
</span></span><span style="display:flex;"><span>(let ((x <span style="color:#ae81ff">5</span>)
</span></span><span style="display:flex;"><span>      (y (<span style="color:#a6e22e">+</span> x <span style="color:#ae81ff">2</span>)))  <span style="color:#75715e">; Error: x is not yet bound when this is evaluated</span>
</span></span><span style="display:flex;"><span>  (<span style="color:#a6e22e">message</span> <span style="color:#e6db74">&#34;y: %d&#34;</span> y))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">;; This works with let*</span>
</span></span><span style="display:flex;"><span>(let* ((x <span style="color:#ae81ff">5</span>)
</span></span><span style="display:flex;"><span>       (y (<span style="color:#a6e22e">+</span> x <span style="color:#ae81ff">2</span>)))  <span style="color:#75715e">; Works because x is already bound</span>
</span></span><span style="display:flex;"><span>  (<span style="color:#a6e22e">message</span> <span style="color:#e6db74">&#34;y: %d&#34;</span> y))  <span style="color:#75715e">; Output: &#34;y: 7&#34;</span>
</span></span></code></pre></div><h2 id="understanding-quote-">Understanding Quote (<code>'</code>)</h2>
<p>One of the most distinctive features of Lisp is the quote character (<code>'</code>). It prevents evaluation of an expression, instead treating it as data.</p>
<h3 id="quoting-variables">Quoting Variables</h3>
<p>When you place a quote in front of a symbol:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span><span style="color:#e6db74">&#39;my-symbol</span>
</span></span></code></pre></div><p>You&rsquo;re telling Elisp, &ldquo;Don&rsquo;t evaluate this symbol, just give me the symbol itself.&rdquo; This is similar to references in other programming languages.</p>
<p>Without the quote, Elisp would try to look up the value of the variable <code>my-symbol</code>.</p>
<h3 id="list-syntax--vs-list--and-cons-">List Syntax: <code>'(...)</code> vs <code>(list ...)</code> and <code>(cons ...)</code></h3>
<p>Lists are fundamental data structures in Lisp. There are several ways to create them:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span><span style="color:#75715e">;; These are equivalent:</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&#39;</span>(a b c)
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">list</span> <span style="color:#e6db74">&#39;a</span> <span style="color:#e6db74">&#39;b</span> <span style="color:#e6db74">&#39;c</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">;; These are also equivalent:</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&#39;</span>(a <span style="color:#f92672">.</span> b)
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">cons</span> <span style="color:#e6db74">&#39;a</span> <span style="color:#e6db74">&#39;b</span>)
</span></span></code></pre></div><p>The quoted form <code>'(...)</code> is concise but has a limitation: everything inside is automatically quoted. This means you can&rsquo;t mix evaluated and non-evaluated expressions.</p>
<p>When to use each:</p>
<ul>
<li>Use <code>'(...)</code> for literal lists that don&rsquo;t need evaluation</li>
<li>Use <code>(list ...)</code> when you need to mix literals and evaluated expressions:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(setq name <span style="color:#e6db74">&#34;John&#34;</span>)
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">list</span> <span style="color:#e6db74">&#39;person</span> name <span style="color:#e6db74">&#39;age</span> <span style="color:#ae81ff">30</span>)  <span style="color:#75715e">; =&gt; (person &#34;John&#34; age 30)</span>
</span></span></code></pre></div><h2 id="understanding-cons-cells-car-and-cdr">Understanding Cons Cells: CAR and CDR</h2>
<p>At the heart of Lisp&rsquo;s data structures is the &ldquo;cons cell&rdquo; - a simple pair of values. Each cons cell has two parts, traditionally accessed with the functions <code>car</code> and <code>cdr</code> (pronounced &ldquo;could-er&rdquo;):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(setq my-pair (<span style="color:#a6e22e">cons</span> <span style="color:#e6db74">&#39;a</span> <span style="color:#e6db74">&#39;b</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">car</span> my-pair)  <span style="color:#75715e">; =&gt; a</span>
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">cdr</span> my-pair)  <span style="color:#75715e">; =&gt; b</span>
</span></span></code></pre></div><p>These peculiarly named functions are historical artifacts from the original Lisp implementation on IBM machines:</p>
<ul>
<li><code>car</code>: Contents of the Address part of Register</li>
<li><code>cdr</code>: Contents of the Decrement part of Register</li>
</ul>
<p>In modern terms, think of them as:</p>
<ul>
<li><code>car</code>: head (first element)</li>
<li><code>cdr</code>: tail (rest of the list)</li>
</ul>
<p>Lists in Lisp are actually chains of cons cells that always terminate with <code>nil</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span><span style="color:#75715e">;; This list:</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&#39;</span>(<span style="color:#ae81ff">1</span> <span style="color:#ae81ff">2</span> <span style="color:#ae81ff">3</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">;; Is actually structured as:</span>
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">cons</span> <span style="color:#ae81ff">1</span> (<span style="color:#a6e22e">cons</span> <span style="color:#ae81ff">2</span> (<span style="color:#a6e22e">cons</span> <span style="color:#ae81ff">3</span> <span style="color:#66d9ef">nil</span>)))
</span></span></code></pre></div><p>Visualized as pairs:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>(1 . (2 . (3 . nil)))
</span></span></code></pre></div><p>This implicit <code>nil</code> at the end is crucial - it&rsquo;s what defines a &ldquo;proper list&rdquo; in Lisp. Without this nil termination, you would have a different data structure. The presence of this nil terminator is what allows functions like <code>length</code> to work properly and what lets Lisp know where a list ends.</p>
<p>You can extract elements from lists using these functions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(setq numbers <span style="color:#f92672">&#39;</span>(<span style="color:#ae81ff">1</span> <span style="color:#ae81ff">2</span> <span style="color:#ae81ff">3</span> <span style="color:#ae81ff">4</span>))
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">car</span> numbers)       <span style="color:#75715e">; =&gt; 1</span>
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">cdr</span> numbers)       <span style="color:#75715e">; =&gt; (2 3 4)</span>
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">car</span> (<span style="color:#a6e22e">cdr</span> numbers)) <span style="color:#75715e">; =&gt; 2</span>
</span></span></code></pre></div><p>Elisp provides convenient shorthand functions like <code>cadr</code> (car of cdr) for common combinations:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(cadr numbers)      <span style="color:#75715e">; =&gt; 2 (same as (car (cdr numbers)))</span>
</span></span><span style="display:flex;"><span>(caddr numbers)     <span style="color:#75715e">; =&gt; 3 (car of cdr of cdr)</span>
</span></span></code></pre></div><h2 id="the-dot-syntax-in-pairs">The Dot Syntax in Pairs</h2>
<p>In Emacs Lisp, you might encounter constructs with dot notation like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(add-to-list <span style="color:#e6db74">&#39;auto-mode-alist</span> <span style="color:#f92672">&#39;</span>(<span style="color:#e6db74">&#34;\\.md\\&#39;&#34;</span> <span style="color:#f92672">.</span> markdown-mode))
</span></span></code></pre></div><p>This dot notation represents a &ldquo;cons cell&rdquo; or pair. In this example, we&rsquo;re creating a pair where the car is <code>&quot;\\.md\\'&quot;</code> and the cdr is <code>markdown-mode</code>.</p>
<p>The following expressions are equivalent:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span><span style="color:#f92672">&#39;</span>(<span style="color:#e6db74">&#34;\\.md\\&#39;&#34;</span> <span style="color:#f92672">.</span> markdown-mode)
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">cons</span> <span style="color:#e6db74">&#34;\\.md\\&#39;&#34;</span> <span style="color:#e6db74">&#39;markdown-mode</span>)
</span></span></code></pre></div><p>The dot creates a direct pair between two values. This syntax is commonly used in alists (like <code>auto-mode-alist</code>), mode hooks, and other paired data structures in Emacs.</p>
<p>When you see a dot in the middle of a list, it means &ldquo;the rest of this list is the cdr of this cons cell&rdquo; rather than continuing the list structure with an implicit nil at the end.</p>
<p>To understand the difference clearly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span><span style="color:#75715e">;; A list with two elements (has an implicit nil at the end)</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&#39;</span>(<span style="color:#ae81ff">1</span> <span style="color:#ae81ff">2</span>)  <span style="color:#75715e">; =&gt; equivalent to (cons 1 (cons 2 nil))</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">;; A single cons cell (a pair) - NOT a proper list</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&#39;</span>(<span style="color:#ae81ff">1</span> <span style="color:#f92672">.</span> <span style="color:#ae81ff">2</span>)  <span style="color:#75715e">; =&gt; equivalent to (cons 1 2)</span>
</span></span></code></pre></div><p>The difference is significant:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(<span style="color:#a6e22e">length</span> <span style="color:#f92672">&#39;</span>(<span style="color:#ae81ff">1</span> <span style="color:#ae81ff">2</span>))     <span style="color:#75715e">; =&gt; 2</span>
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">length</span> <span style="color:#f92672">&#39;</span>(<span style="color:#ae81ff">1</span> <span style="color:#f92672">.</span> <span style="color:#ae81ff">2</span>))   <span style="color:#75715e">; =&gt; Error: Wrong type argument: listp, (1 . 2)</span>
</span></span></code></pre></div><p>This distinction is fundamental to understanding how Lisp data structures work.</p>
<h2 id="function-quoting--vs-">Function Quoting: <code>#'</code> vs <code>'</code></h2>
<p>In Elisp, you&rsquo;ll see both <code>'function-name</code> and <code>#'function-name</code> when referring to functions. The difference is important:</p>
<ul>
<li><code>'function-name</code> simply quotes the symbol</li>
<li><code>#'function-name</code> is shorthand for <code>(function function-name)</code>, which specifically tells Elisp that the symbol represents a function</li>
</ul>
<p>When should you use <code>#'</code>? Best practice is to use it whenever you&rsquo;re referring to a function as a function, especially with higher-order functions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span><span style="color:#75715e">;; Good practice</span>
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">mapcar</span> <span style="color:#a6e22e">#&#39;1+</span> <span style="color:#f92672">&#39;</span>(<span style="color:#ae81ff">1</span> <span style="color:#ae81ff">2</span> <span style="color:#ae81ff">3</span>))  <span style="color:#75715e">; =&gt; (2 3 4)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">;; Works but less explicit</span>
</span></span><span style="display:flex;"><span>(<span style="color:#a6e22e">mapcar</span> <span style="color:#e6db74">&#39;1+</span> <span style="color:#f92672">&#39;</span>(<span style="color:#ae81ff">1</span> <span style="color:#ae81ff">2</span> <span style="color:#ae81ff">3</span>))
</span></span></code></pre></div><p>Using <code>#'</code> helps the byte-compiler optimize your code and makes your intent clearer to other programmers.</p>
<h2 id="association-lists-alists">Association Lists (alists)</h2>
<p>Association lists (alists) are lists of key-value pairs used for simple lookups:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(setq my-alist <span style="color:#f92672">&#39;</span>((name <span style="color:#f92672">.</span> <span style="color:#e6db74">&#34;John&#34;</span>)
</span></span><span style="display:flex;"><span>                 (age <span style="color:#f92672">.</span> <span style="color:#ae81ff">30</span>)
</span></span><span style="display:flex;"><span>                 (city <span style="color:#f92672">.</span> <span style="color:#e6db74">&#34;Boston&#34;</span>)))
</span></span></code></pre></div><p>You can access values using functions like <code>assoc</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(<span style="color:#a6e22e">cdr</span> (<span style="color:#a6e22e">assoc</span> <span style="color:#e6db74">&#39;age</span> my-alist))  <span style="color:#75715e">; =&gt; 30</span>
</span></span></code></pre></div><p>Alists are frequently used in Emacs for configuration options, especially when the list is relatively small and frequently modified. They&rsquo;re simple but not as efficient for large datasets.</p>
<h2 id="property-lists-plists">Property Lists (plists)</h2>
<p>Property lists are another key-value structure in Elisp, but with a different syntax:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(setq my-plist <span style="color:#f92672">&#39;</span>(:name <span style="color:#e6db74">&#34;John&#34;</span> :age <span style="color:#ae81ff">30</span> :city <span style="color:#e6db74">&#34;Boston&#34;</span>))
</span></span></code></pre></div><p>Notice how the keys are prefixed with colons. These are called keywords, similar to Ruby&rsquo;s symbols. You can access values using <code>plist-get</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(<span style="color:#a6e22e">plist-get</span> my-plist :age)  <span style="color:#75715e">; =&gt; 30</span>
</span></span></code></pre></div><p>Other languages with similar concepts include:</p>
<ul>
<li>Ruby with its symbols (<code>:symbol</code>)</li>
<li>Clojure with keywords (<code>:keyword</code>)</li>
<li>Elixir with atoms (<code>:atom</code>)</li>
</ul>
<p>Plists are often used for function arguments that have default values or are optional.</p>
<h2 id="summary">Summary</h2>
<p>Understanding these fundamental Emacs Lisp concepts will help you:</p>
<ol>
<li>Write more effective Elisp code</li>
<li>Understand existing Emacs configurations</li>
<li>Create your own Emacs extensions</li>
</ol>
<p><img alt="summary" loading="lazy" src="/post/emacs-list-introduction/summary.png"></p>
<p>Emacs Lisp&rsquo;s power comes from its simplicity and consistency. Once you grasp these basic concepts, you&rsquo;ll find that the language follows logical patterns that make learning advanced features much easier.</p>
<p>Happy hacking!</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Essential Emacs Hotkeys Every User Should Know</title>
      <link>https://blog.0x17.de/post/essential-emacs-hotkeys/</link>
      <pubDate>Fri, 28 Mar 2025 20:00:00 +0100</pubDate>
      <guid>https://blog.0x17.de/post/essential-emacs-hotkeys/</guid>
      <description>&lt;p&gt;For over four decades, Emacs has stood as a pillar in the text editing world, beloved by programmers, writers, and power users alike. What makes Emacs particularly powerful is its extensive keyboard shortcut system that allows users to perform complex operations without ever touching the mouse.&lt;/p&gt;
&lt;p&gt;Emacs comes with an excellent built-in tutorial that can be accessed with &lt;code&gt;C-h t&lt;/code&gt; (Control-h followed by t). I highly recommend all new users complete this tutorial to get comfortable with Emacs&amp;rsquo; fundamentals. That said, the tutorial is comprehensive and takes time to complete.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>For over four decades, Emacs has stood as a pillar in the text editing world, beloved by programmers, writers, and power users alike. What makes Emacs particularly powerful is its extensive keyboard shortcut system that allows users to perform complex operations without ever touching the mouse.</p>
<p>Emacs comes with an excellent built-in tutorial that can be accessed with <code>C-h t</code> (Control-h followed by t). I highly recommend all new users complete this tutorial to get comfortable with Emacs&rsquo; fundamentals. That said, the tutorial is comprehensive and takes time to complete.</p>
<p><img alt="hotkeys" loading="lazy" src="/post/essential-emacs-hotkeys/hotkeys.png"></p>
<p>This guide serves as a TL;DR and quick reference of the most essential hotkeys you should memorize. Consider it a short recap of what&rsquo;s worth knowing by heart to be immediately productive in Emacs, whether you&rsquo;re new to the editor or just need a refresher.</p>
<h2 id="understanding-emacs-notation">Understanding Emacs Notation</h2>
<p>Before diving into the hotkeys themselves, let&rsquo;s decode how to read Emacs key notation:</p>
<ul>
<li><strong>C-</strong> represents the Control key. For example, <code>C-a</code> means &ldquo;hold Control and press a&rdquo;.</li>
<li><strong>M-</strong> represents the Meta key, which on modern keyboards is typically the Alt key. So <code>M-f</code> means &ldquo;hold Alt and press f&rdquo;.</li>
<li><strong>C-x</strong> followed by another key represents a two-key sequence. Press Control and x together, release, then press the next key.</li>
<li><strong>M-x</strong> similarly starts a command sequence where you type the command name after pressing Alt and x.</li>
</ul>
<p>Multiple modifier keys can be combined. For example, <code>C-M-f</code> means holding both Control and Alt while pressing f.</p>
<p>With this foundation, let&rsquo;s explore the essential hotkeys that will dramatically improve your Emacs efficiency.</p>
<h2 id="essential-emacs-hotkeys-by-category">Essential Emacs Hotkeys by Category</h2>
<h3 id="text-navigation">Text Navigation</h3>
<p>Efficient text navigation is the foundation of productive editing. These shortcuts let you move through your document with precision:</p>
<table>
  <thead>
      <tr>
          <th>Hotkey</th>
          <th>Action Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>C-a</code></td>
          <td>Move to beginning of line</td>
      </tr>
      <tr>
          <td><code>C-e</code></td>
          <td>Move to end of line</td>
      </tr>
      <tr>
          <td><code>C-f</code></td>
          <td>Move forward one character</td>
      </tr>
      <tr>
          <td><code>C-b</code></td>
          <td>Move backward one character</td>
      </tr>
      <tr>
          <td><code>M-f</code></td>
          <td>Move forward one word</td>
      </tr>
      <tr>
          <td><code>M-b</code></td>
          <td>Move backward one word</td>
      </tr>
      <tr>
          <td><code>C-n</code></td>
          <td>Move to next line</td>
      </tr>
      <tr>
          <td><code>C-p</code></td>
          <td>Move to previous line</td>
      </tr>
      <tr>
          <td><code>C-v</code></td>
          <td>Page down</td>
      </tr>
      <tr>
          <td><code>M-v</code></td>
          <td>Page up</td>
      </tr>
      <tr>
          <td><code>M-&lt;</code></td>
          <td>Move to beginning of buffer</td>
      </tr>
      <tr>
          <td><code>M-&gt;</code></td>
          <td>Move to end of buffer</td>
      </tr>
      <tr>
          <td><code>C-l</code></td>
          <td>Recenter buffer around cursor</td>
      </tr>
  </tbody>
</table>
<p>The navigation keys follow a pattern: <code>f</code> for forward, <code>b</code> for backward, <code>p</code> for previous, and <code>n</code> for next. Once you internalize these patterns, they become second nature.</p>
<h3 id="text-editing">Text Editing</h3>
<p>These shortcuts handle the core editing operations that you&rsquo;ll use hundreds of times daily:</p>
<table>
  <thead>
      <tr>
          <th>Hotkey</th>
          <th>Action Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>C-d</code></td>
          <td>Delete character at point</td>
      </tr>
      <tr>
          <td><code>M-&lt;backspace&gt;</code></td>
          <td>Delete word backward</td>
      </tr>
      <tr>
          <td><code>M-d</code></td>
          <td>Delete word forward</td>
      </tr>
      <tr>
          <td><code>C-k</code></td>
          <td>Kill (cut) from cursor to end of line</td>
      </tr>
      <tr>
          <td><code>C-w</code></td>
          <td>Kill (cut) selected region</td>
      </tr>
      <tr>
          <td><code>M-w</code></td>
          <td>Copy selected region</td>
      </tr>
      <tr>
          <td><code>C-y</code></td>
          <td>Yank (paste) most recently killed text</td>
      </tr>
      <tr>
          <td><code>M-y</code></td>
          <td>Cycle through kill ring after yanking</td>
      </tr>
      <tr>
          <td><code>C-/</code> or <code>C-_</code></td>
          <td>Undo</td>
      </tr>
  </tbody>
</table>
<p>A unique aspect of Emacs is its &ldquo;kill ring&rdquo; - a clipboard system that remembers multiple deleted items. After using <code>C-y</code> to paste the most recent item, you can press <code>M-y</code> repeatedly to cycle through previously killed text.</p>
<h3 id="region-and-selection">Region and Selection</h3>
<p>Working with selections in Emacs involves understanding the concept of the &ldquo;mark&rdquo; and &ldquo;point&rdquo;:</p>
<table>
  <thead>
      <tr>
          <th>Hotkey</th>
          <th>Action Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>C-space</code></td>
          <td>Set mark (start selection)</td>
      </tr>
      <tr>
          <td><code>C-x C-x</code></td>
          <td>Exchange point and mark (swap selection endpoints)</td>
      </tr>
      <tr>
          <td><code>C-x h</code></td>
          <td>Select entire buffer</td>
      </tr>
  </tbody>
</table>
<p>The mark is where a selection begins, and the point is your cursor position. Together they define the &ldquo;region&rdquo; - Emacs&rsquo; term for a selection.</p>
<h3 id="file-operations">File Operations</h3>
<p>These commands handle file management tasks:</p>
<table>
  <thead>
      <tr>
          <th>Hotkey</th>
          <th>Action Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>C-x C-f</code></td>
          <td>Find (open) file</td>
      </tr>
      <tr>
          <td><code>C-x C-s</code></td>
          <td>Save current buffer</td>
      </tr>
      <tr>
          <td><code>C-x s</code></td>
          <td>Save all buffers</td>
      </tr>
      <tr>
          <td><code>C-x C-c</code></td>
          <td>Exit Emacs</td>
      </tr>
      <tr>
          <td><code>C-x d</code></td>
          <td>Open directory (dired)</td>
      </tr>
  </tbody>
</table>
<p>The <code>C-x C-f</code> command is particularly powerful as it allows you to create new files by entering paths that don&rsquo;t exist yet.</p>
<h3 id="buffer-management">Buffer Management</h3>
<p>In Emacs, a buffer is essentially an open file or space for editing:</p>
<table>
  <thead>
      <tr>
          <th>Hotkey</th>
          <th>Action Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>C-x b</code></td>
          <td>Switch buffer</td>
      </tr>
      <tr>
          <td><code>C-x k</code></td>
          <td>Kill (close) buffer</td>
      </tr>
  </tbody>
</table>
<p>Efficient buffer navigation is crucial when working with multiple files.</p>
<h3 id="window-management">Window Management</h3>
<p>Emacs allows for sophisticated window layouts:</p>
<table>
  <thead>
      <tr>
          <th>Hotkey</th>
          <th>Action Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>C-x 2</code></td>
          <td>Split window horizontally (one above the other)</td>
      </tr>
      <tr>
          <td><code>C-x 3</code></td>
          <td>Split window vertically (side by side)</td>
      </tr>
      <tr>
          <td><code>C-x o</code></td>
          <td>Switch to other window</td>
      </tr>
      <tr>
          <td><code>C-x 0</code></td>
          <td>Close current window</td>
      </tr>
      <tr>
          <td><code>C-x 1</code></td>
          <td>Close all windows except current</td>
      </tr>
      <tr>
          <td><code>C-x 5 2</code></td>
          <td>Create new frame (GUI window)</td>
      </tr>
  </tbody>
</table>
<p>The ability to split the editor into multiple windows, each showing different buffers, is one of Emacs&rsquo; most powerful features.</p>
<h3 id="search-and-replace">Search and Replace</h3>
<p>Finding and replacing text efficiently is essential for editing:</p>
<table>
  <thead>
      <tr>
          <th>Hotkey</th>
          <th>Action Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>C-s</code></td>
          <td>Incremental search forward</td>
      </tr>
      <tr>
          <td><code>C-r</code></td>
          <td>Incremental search backward</td>
      </tr>
      <tr>
          <td><code>M-%</code></td>
          <td>Query replace</td>
      </tr>
  </tbody>
</table>
<p>Emacs&rsquo; incremental search updates as you type, making it quick to locate specific text.</p>
<h3 id="help-system">Help System</h3>
<p>When you inevitably need guidance, Emacs has built-in help:</p>
<table>
  <thead>
      <tr>
          <th>Hotkey</th>
          <th>Action Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>C-h f</code></td>
          <td>Describe function</td>
      </tr>
      <tr>
          <td><code>C-h k</code></td>
          <td>Describe key</td>
      </tr>
      <tr>
          <td><code>C-h t</code></td>
          <td>Start tutorial</td>
      </tr>
  </tbody>
</table>
<p>The self-documenting nature of Emacs means help is always just a few keystrokes away.</p>
<h3 id="macros">Macros</h3>
<p>Macros are powerful tools for automating repetitive tasks:</p>
<table>
  <thead>
      <tr>
          <th>Hotkey</th>
          <th>Action Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>F3</code></td>
          <td>Start recording a keyboard macro</td>
      </tr>
      <tr>
          <td><code>F4</code></td>
          <td>Stop recording or execute the most recent keyboard macro</td>
      </tr>
  </tbody>
</table>
<p>To use macros: press F3, perform the sequence of actions you want to repeat, press F4 to finish recording, then press F4 again each time you want to replay those exact keystrokes. This is incredibly useful for mass operations on text.</p>
<h3 id="command-execution">Command Execution</h3>
<p>Finally, the gateway to thousands of Emacs commands:</p>
<table>
  <thead>
      <tr>
          <th>Hotkey</th>
          <th>Action Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>M-x</code></td>
          <td>Execute command by name</td>
      </tr>
  </tbody>
</table>
<p>This shortcut unlocks Emacs&rsquo; full potential, giving you access to any command by name.</p>
<h2 id="building-your-muscle-memory">Building Your Muscle Memory</h2>
<p>Learning these hotkeys will take time, but the productivity payoff is immense. Start by mastering the navigation and basic editing commands, then gradually incorporate the others. Consider printing this list and keeping it near your workspace until the keystrokes become automatic.</p>
<p>Remember that Emacs is highly customizable - once you&rsquo;re comfortable with these basics, you can create your own keybindings for commands you use frequently.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The beauty of Emacs lies in how it becomes an extension of your thinking process once you&rsquo;ve internalized these commands. Text manipulation becomes effortless, allowing you to focus on your actual work rather than the mechanics of editing.</p>
<p><img alt="emacs-mind" loading="lazy" src="/post/essential-emacs-hotkeys/emacs-mind.png"></p>
<p>While this guide covers the essential hotkeys, it barely scratches the surface of what Emacs can do. As you grow more comfortable with these basics, explore specialized modes for your particular work, whether that&rsquo;s programming in specific languages, writing prose, managing projects, or organizing your life.</p>
<p>What began as a seemingly steep learning curve will transform into a powerful skillset that follows you throughout your computing life. Happy editing!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Lessons Learned from Journaling with Emacs org-journal-mode</title>
      <link>https://blog.0x17.de/post/lessons-org-journaling/</link>
      <pubDate>Fri, 28 Mar 2025 00:00:00 +0100</pubDate>
      <guid>https://blog.0x17.de/post/lessons-org-journaling/</guid>
      <description>&lt;p&gt;As I delve into the intricacies of Emacs and its powerful org-mode ecosystem, I&amp;rsquo;ve discovered that org-journal has transformed my daily note-taking and task management workflow. Looking at the &lt;a href=&#34;https://github.com/0x17de/emacs-config&#34;&gt;configuration shared&lt;/a&gt;, there are several valuable insights worth highlighting for anyone looking to enhance their personal knowledge management system.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;Journaling with org-mode&#34; loading=&#34;lazy&#34; src=&#34;https://blog.0x17.de/post/lessons-org-journaling/org-mode.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;the-power-of-daily-granularity&#34;&gt;The Power of Daily Granularity&lt;/h2&gt;
&lt;p&gt;One of the most impactful lessons I&amp;rsquo;ve learned is the importance of splitting journals into smaller, more manageable chunks. The configuration sets up org-journal with daily entries rather than maintaining one massive file for an entire year&amp;rsquo;s worth of thoughts and tasks. This approach offers several benefits:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>As I delve into the intricacies of Emacs and its powerful org-mode ecosystem, I&rsquo;ve discovered that org-journal has transformed my daily note-taking and task management workflow. Looking at the <a href="https://github.com/0x17de/emacs-config">configuration shared</a>, there are several valuable insights worth highlighting for anyone looking to enhance their personal knowledge management system.</p>
<p><img alt="Journaling with org-mode" loading="lazy" src="/post/lessons-org-journaling/org-mode.png"></p>
<h2 id="the-power-of-daily-granularity">The Power of Daily Granularity</h2>
<p>One of the most impactful lessons I&rsquo;ve learned is the importance of splitting journals into smaller, more manageable chunks. The configuration sets up org-journal with daily entries rather than maintaining one massive file for an entire year&rsquo;s worth of thoughts and tasks. This approach offers several benefits:</p>
<ul>
<li><strong>Improved Performance</strong>: Smaller files load faster and are less likely to cause Emacs to slow down</li>
<li><strong>Better Organization</strong>: Daily or weekly files create natural boundaries for your thoughts</li>
<li><strong>Easier Navigation</strong>: Finding specific entries becomes simpler when they&rsquo;re organized by date</li>
</ul>
<p>My configuration explicitly sets <code>org-journal-dir</code> to &ldquo;~/org/journal&rdquo; and uses the format (YYYYMMDD) for consistency and easy sorting.</p>
<h2 id="task-migration-never-lose-track-again">Task Migration: Never Lose Track Again</h2>
<p>A game-changing feature in org-journal is its ability to automatically move unfinished tasks to the next day&rsquo;s entry. This prevents tasks from being forgotten in past journal entries and keeps your current day&rsquo;s focus on what still needs attention.</p>
<h2 id="effective-agenda-management">Effective Agenda Management</h2>
<p>The configuration reveals a crucial insight: simply journaling isn&rsquo;t enough—you need to leverage org&rsquo;s scheduling and deadline functions to create an effective overview of your commitments. The custom function <code>_0x17de/org-highlight-todays-deadlines</code> applies special highlighting to items due today, creating visual urgency for time-sensitive tasks.</p>
<h2 id="enhanced-task-states-with-custom-keywords">Enhanced Task States with Custom Keywords</h2>
<p>The standard TODO/DONE paradigm often feels inadequate for complex workflows. This configuration expands the vocabulary of task states:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(setq org-todo-keywords <span style="color:#f92672">&#39;</span>((sequence <span style="color:#e6db74">&#34;TODO&#34;</span> <span style="color:#e6db74">&#34;WAITING&#34;</span> <span style="color:#e6db74">&#34;DOING&#34;</span> <span style="color:#e6db74">&#34;|&#34;</span> <span style="color:#e6db74">&#34;DONE&#34;</span> <span style="color:#e6db74">&#34;CANCELLED&#34;</span>)))
</span></span></code></pre></div><p>Adding intermediate states like &ldquo;WAITING&rdquo; and &ldquo;DOING&rdquo; provides more context about where each task stands without requiring additional notes. The vertical bar separates active states from terminal states, a subtle but important distinction.</p>
<h2 id="visual-differentiation-through-custom-styling">Visual Differentiation Through Custom Styling</h2>
<p>The configuration goes beyond mere keywords by defining custom faces for different task states:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-elisp" data-lang="elisp"><span style="display:flex;"><span>(org-modern-todo-faces <span style="color:#f92672">&#39;</span>((<span style="color:#e6db74">&#34;TODO&#34;</span> :foreground <span style="color:#e6db74">&#34;white&#34;</span> :background <span style="color:#e6db74">&#34;darkgreen&#34;</span> :weight bold)
</span></span><span style="display:flex;"><span>                        (<span style="color:#e6db74">&#34;DOING&#34;</span> :foreground <span style="color:#e6db74">&#34;white&#34;</span> :background <span style="color:#e6db74">&#34;orange&#34;</span> :weight bold)
</span></span><span style="display:flex;"><span>                        (<span style="color:#e6db74">&#34;WAITING&#34;</span> :foreground <span style="color:#e6db74">&#34;white&#34;</span> :background <span style="color:#e6db74">&#34;blue&#34;</span>)))
</span></span></code></pre></div><p>This color-coding creates immediate visual feedback, making it easy to scan a journal page and instantly understand what&rsquo;s in progress versus what&rsquo;s waiting on external factors.</p>
<h2 id="modern-interface-enhancements">Modern Interface Enhancements</h2>
<p>The use of <code>org-modern-mode</code> suggests an appreciation for clean, contemporary styling. This package enhances the visual appeal of org files with improved typography and modern design elements, making the journaling experience more pleasant.</p>
<h2 id="exploring-new-frontiers-org-node">Exploring New Frontiers: org-node</h2>
<p>The configuration hints at exploration of <code>org-node</code>, a powerful package that enables treating org headings as nodes in a graph. This approach opens up interesting possibilities for knowledge management and connecting ideas across different journal entries and documents.</p>
<h2 id="convenient-keybindings-for-quick-capture">Convenient Keybindings for Quick Capture</h2>
<p>Setting up a global keybinding (<code>C-M-S-j</code>) for creating new journal entries reduces friction in the journaling process. The fewer barriers to capturing thoughts, the more likely you are to maintain the practice consistently.</p>
<h2 id="integration-with-the-agenda">Integration with the Agenda</h2>
<p>The configuration enables agenda integration with <code>org-journal-enable-agenda-integration</code>, ensuring that tasks and events from journal entries appear in your org-agenda views. This creates a unified system where your journal and task management seamlessly work together.</p>
<h2 id="whats-next">What&rsquo;s Next?</h2>
<p>For those inspired to explore this setup further, my complete Emacs configuration is available at <a href="https://github.com/0x17de/emacs-config">https://github.com/0x17de/emacs-config</a>. It offers additional insights into how these journaling practices fit into a broader productivity system.</p>
<p>As my own journey with org-journal continues, I&rsquo;m particularly interested in diving deeper into <code>org-node</code> and exploring how graph-based knowledge management might enhance connections between journal entries over time.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Rust Programming Language and the Advent of Code 2018</title>
      <link>https://blog.0x17.de/post/rust-and-adventofcode2018/</link>
      <pubDate>Fri, 07 Dec 2018 02:56:41 +0100</pubDate>
      <guid>https://blog.0x17.de/post/rust-and-adventofcode2018/</guid>
      <description>&lt;p&gt;I recently got reminded about &lt;a href=&#34;https://adventofcode.com/&#34;&gt;AdventOfCode2018&lt;/a&gt;. Be sure to check it out before end of december. Till now i did the challenges in rust, perl, bash - no specific language - just depending on the time i have. If you have plenty of time, the challenges from the last years are still available.&lt;/p&gt;
&lt;p&gt;Additionally i was looking a little into the programming language
rust.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I recently got reminded about <a href="https://adventofcode.com/">AdventOfCode2018</a>. Be sure to check it out before end of december. Till now i did the challenges in rust, perl, bash - no specific language - just depending on the time i have. If you have plenty of time, the challenges from the last years are still available.</p>
<p>Additionally i was looking a little into the programming language
rust.</p>
<p>The rust package manager <a href="https://doc.rust-lang.org/cargo/">cargo</a> feels well designed. Also the language solves quite common issues in programming which makes classic inheritance unnecessary. The <a href="https://doc.rust-lang.org/book/index.html">tutorial</a> is easy to read and understand but in advanced topics you can see that things still change. The verbose annotation of object lifetime of references was reduced in recent releases and things that should not compile now finally do without issues. The basic data types quite bring everything you need out of the box, except some language features like <a href="https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain_filter">drain_filter</a>, quite similar to <a href="https://en.cppreference.com/w/cpp/algorithm/remove">std::remove_if</a>, are only available as unstable features. Therefore I would not recommend using the language in production, but i can already see its potential in the future. Memory related problems are reduced to ownership problems. It must be clear who is allowed to borrow a variable (keeping a reference) for later writing or read only operations. You can not have two writable references, nor have references that outlive the instance. This might cause a lot of confusion and iterations of code if you try to write code in your usual style. The ownership problem looks even worse when trying to communicate between threads. When a thread stops can be decided at runtime, either it is joined or it could be detached and even outlive the main thread. Therefore the classic thread&rsquo;s lifetime is considered <code>'static</code>. Solutions, for having threads with a defined lifetime existed: like <a href="https://doc.rust-lang.org/1.0.0/std/thread/fn.scoped.html">std:&#x1f9f5;:scoped</a>, but it had <a href="https://github.com/rust-lang/rust/issues/24292">unforseen issues</a>, related to accessing freed memory. To solve the issue, the library <a href="https://github.com/crossbeam-rs/crossbeam">crossbeam</a> provides a different approach to threads with a clearly defined lifetime. Still sharing objects as readable between threads is a challenge. One solution to the object being shared across threads could be by using <code>std::sync::Arc&lt;std::sync::Mutex&lt;Foo&gt;&gt;</code>. An <a href="https://doc.rust-lang.org/std/sync/struct.Arc.html">Arc</a> is for atomically accessing things from different threads, while the <a href="https://doc.rust-lang.org/std/sync/struct.Mutex.html">mutex</a> is required to clearly define who is writing the memory. Otherwise for <a href="https://doc.rust-lang.org/std/net/struct.TcpStream.html">TcpStreams</a> the handles can simply be cloned (see <a href="https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.try_clone">try_clone</a>) without being in need of using a mutex object.</p>
<p>After reading this article you sure feel the complexity of that quite new programming language rust. But even if the language might not be ready for production, I recommend having at least a first look through the entire <a href="https://doc.rust-lang.org/book/index.html">tutorial</a> or even to learn it. The concepts might be different, but if you do you will be prepared for the future that is likely to come.</p>]]></content:encoded>
    </item>
    <item>
      <title>Hand written letters from 1941 and neural networks</title>
      <link>https://blog.0x17.de/post/letter-cnn/</link>
      <pubDate>Sat, 20 May 2017 17:12:05 +0200</pubDate>
      <guid>https://blog.0x17.de/post/letter-cnn/</guid>
      <description>&lt;p&gt;As another side project i am currently trying to convert a letter into
computer readable text just from a scanned image.&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://www.0x17.de/proj/cnn_letter.png&#34;&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>As another side project i am currently trying to convert a letter into
computer readable text just from a scanned image.</p>
<p><img loading="lazy" src="https://www.0x17.de/proj/cnn_letter.png"></p>
<p>The original images which will be preprocessed and passed to a
convolutional neural network are scanned A4 letters with a resolution
of about 2550x3500 pixels at 300dpi.</p>
<p>At first some letters will be manually converted into text for
labeling, which is required for the training process. A self written
tool is used to assign a position to each character in the
transcription. This process can be seen in the screenshot above. The
tool was written with OpenCV and allows a preview of the next two
words, highlights the current letter which is to be labeled and shows
some bounding box with hints for the later cropping process. (If you
want the source just write me some message but i also plan to release
the project after some results)</p>
<p>After each letter has some assigned position, the image still needs to
be preprocessed/enhanced as the color of the font is quite close to
the background color. This step can be done automatically in the
future. For each labeled character a 32x32px sub image will be
cropped and additionally for each of those images small pixel
translations, scaling and sinus wave transformations will be applied
to generate a lot more training data for the learning process.</p>
<p>For the CNN i planned to use Tensorflow as it has been proven to be
quite flexible for this task.</p>
<p>I will present the network configuration and further results in the next blog posts :)</p>
<p>Current letter counts after labeling two letters:</p>
<pre>
     16 ,
      2 !
     24 .
      2 0
      4 1
      3 2
      1 3
      2 4
      1 9
     71 a
      3 ä
     22 b
     54 c
     33 d
    162 e
     17 f
     26 g
     76 h
     81 i
      5 j
     13 k
     41 l
     22 m
    100 n
     26 o
      2 ö
      5 p
     65 r
     48 s
      4 ß
     57 t
     22 u
      4 ü
      1 Ü
      7 v
     31 w
     13 z
</pre>]]></content:encoded>
    </item>
    <item>
      <title>The official Harpoon page went online</title>
      <link>https://blog.0x17.de/post/harpoon-page-online/</link>
      <pubDate>Mon, 16 Jan 2017 00:55:55 +0100</pubDate>
      <guid>https://blog.0x17.de/post/harpoon-page-online/</guid>
      <description>&lt;p&gt;The official Harpoon website is now online and available at &lt;a href=&#34;https://harpoon.0x17.de/&#34;&gt;https://harpoon.0x17.de/&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The official Harpoon website is now online and available at <a href="https://harpoon.0x17.de/">https://harpoon.0x17.de/</a>.</p>
<p>You can follow the project on the blog, github or via rss with the following urls:</p>
<ul>
<li>Blog: <a href="https://harpoon.0x17.de/blog/">https://harpoon.0x17.de/blog/</a></li>
<li>RSS: <a href="https://harpoon.0x17.de/blog/index.xml">https://harpoon.0x17.de/blog/index.xml</a></li>
<li>GitHub Harpoon: <a href="https://github.com/HarpoonOrg/Harpoon">https://github.com/HarpoonOrg/Harpoon</a></li>
<li>GitHub HarpoonClient: <a href="https://github.com/HarpoonOrg/HarpoonClient">https://github.com/HarpoonOrg/HarpoonClient</a></li>
</ul>
<p>Have fun reading! :)</p>]]></content:encoded>
    </item>
    <item>
      <title>Pixel Animations</title>
      <link>https://blog.0x17.de/post/pixel_animations/</link>
      <pubDate>Thu, 12 Jan 2017 03:03:28 +0100</pubDate>
      <guid>https://blog.0x17.de/post/pixel_animations/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m trying to learn more about pixel animations, which could help to build some basic sprites for a small game that could happen sometime later in my life.
Currently i&amp;rsquo;m trying to reproduce some of the tutorials from &lt;a href=&#34;https://twitter.com/saint11&#34;&gt;saint11&lt;/a&gt;, which can be found &lt;a href=&#34;https://www.patreon.com/saint11/posts&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;m trying to learn more about pixel animations, which could help to build some basic sprites for a small game that could happen sometime later in my life.
Currently i&rsquo;m trying to reproduce some of the tutorials from <a href="https://twitter.com/saint11">saint11</a>, which can be found <a href="https://www.patreon.com/saint11/posts">here</a>.</p>
<p>Here are two animations i created with spriter:</p>
<img src="https://www.0x17.de/proj/anim_expl.gif" style="width:128px;height:128px" />
<img src="https://www.0x17.de/proj/anim_smoke.gif" style="width:128px;height:128px" />
<p>The tool i used here is <a href="https://www.aseprite.org/">aseprite</a></p>
<p>And here&rsquo;s how i&rsquo;d start with drawing these: I&rsquo;d use primitive shapes like a filled ellipse/circle and draw some highlights with a contour tool (lasso which fills the selected region). For the next frames i will reduce sections using the lasso or eraser and also move subsections using the lasso tool.</p>]]></content:encoded>
    </item>
    <item>
      <title>Harpoon: Current State</title>
      <link>https://blog.0x17.de/post/harpoon-current-state/</link>
      <pubDate>Mon, 17 Oct 2016 15:39:30 +0200</pubDate>
      <guid>https://blog.0x17.de/post/harpoon-current-state/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/0x17de/Harpoon/&#34;&gt;Harpoon&lt;/a&gt; is a flexible chat client for common protocols - written in C++ - and will stay online when you aren&amp;rsquo;t. You will get push notifications for private messages when you are on the run.&lt;/p&gt;
&lt;p&gt;Here are some screenshots of the current state:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://github.com/0x17de/Harpoon/">Harpoon</a> is a flexible chat client for common protocols - written in C++ - and will stay online when you aren&rsquo;t. You will get push notifications for private messages when you are on the run.</p>
<p>Here are some screenshots of the current state:</p>
<p><img loading="lazy" src="https://www.0x17.de/proj/harpoonClient6.png">
<img loading="lazy" src="https://www.0x17.de/proj/harpoonClientQt3.png"></p>
<p>Those screenshots need a little description, so here you go:</p>
<p>Harpoon consists of a server program and three different clients: Web/Desktop/Mobile.</p>
<p>In the first screenshot you can see the browser interface (topleft), whereas in the second screenshot you can see the desktop client.</p>
<p>The project is still work in progress as some essential parts are missing. Those are showing the backlog, user login and yet server configuration. In the next few updates those topics will be adressed and i will keep you up to date.</p>
<p>If you have questions you can find me on IRC. Just click the following link: <a href="https://webchat.freenode.net/?channels=harpoon">freenode.net channel #harpoon</a>.</p>
<p>Follow me on <a href="https://www.twitter.com/0x17de">twitter</a>, watch the project on <a href="https://github.com/0x17de/Harpoon/">github</a>, read my <a href="/index.xml">RSS</a> or join #harpoon on freenode.net to receieve status updates. :)</p>]]></content:encoded>
    </item>
    <item>
      <title>index</title>
      <link>https://blog.0x17.de/about/</link>
      <pubDate>Mon, 17 Oct 2016 15:21:27 +0200</pubDate>
      <guid>https://blog.0x17.de/about/</guid>
      <description>&lt;p&gt;About me will follow - See &lt;a href=&#34;https://www.manuel-herrmann.de/#Projects&#34;&gt;https://www.manuel-herrmann.de/#Projects&lt;/a&gt; in the meantime :)&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>About me will follow - See <a href="https://www.manuel-herrmann.de/#Projects">https://www.manuel-herrmann.de/#Projects</a> in the meantime :)</p>
]]></content:encoded>
    </item>
    <item>
      <title>reboot</title>
      <link>https://blog.0x17.de/post/reboot/</link>
      <pubDate>Mon, 17 Oct 2016 15:02:20 +0200</pubDate>
      <guid>https://blog.0x17.de/post/reboot/</guid>
      <description>&lt;p&gt;This is a reboot of my blog. Contents will follow :)&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>This is a reboot of my blog. Contents will follow :)</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
