<?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/"
xmlns:media="http://search.yahoo.com/mrss/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
  <channel>
    <title>Systemd on @jelloeater👾</title>
    <link>https://jelloeater.me/tags/systemd/</link>
    <description>Recent content in Systemd on @jelloeater👾</description>
    <generator>Hugo</generator>
    <language>en</language>
    
    <copyright>Jelloeater 2024</copyright>
    <image>
      <title>Systemd on @jelloeater👾</title>
      <url>https://jelloeater.me/tags/systemd/favicon-32x32.png</url>
      <link>https://jelloeater.me/tags/systemd/</link>
      <description>Recent content in Systemd on @jelloeater👾</description>
    </image>
    <lastBuildDate>Sat, 04 Jul 2026 00:00:00 +0000</lastBuildDate>
    
    <atom:link href="https://jelloeater.me/tags/systemd/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Less is More: How an AI Agent Taught Me to Write Better systemd Services</title>
      <link>
      https://jelloeater.me/blog/less-is-more-systemd/
      </link>
      <dc:creator>Jelloeater</dc:creator>
      <comments>
      https://jelloeater.me/blog/less-is-more-systemd/#comments
      </comments>
      <pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate>
      <category>ai</category>
      
      <category>devops</category>
      
      <category>linux</category>
      
      <category>systemd</category>
      <guid>https://jelloeater.me/blog/less-is-more-systemd/</guid>
      
      <media:content url="https://eod-image.s3.amazonaws.com/2022/02/Max_Pixels.png" medium="image" type="image/png"/>
      <media:thumbnail url="https://eod-image.s3.amazonaws.com/2022/02/Max_Pixels.png"/>
      


    
    



      
      
      <description><![CDATA[A session of iteratively simplifying until it hurts]]></description>
      
      <content:encoded>
        <![CDATA[
        
          <p><img src="https://eod-image.s3.amazonaws.com/2022/02/Max_Pixels.png" alt="Featured Image"/></p>
        
        

  
    <blockquote>
      <p><strong><a href="https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it">YAGNI</a> — You Ain&rsquo;t Gonna Need It.</strong></p>
    </blockquote>
  
<h2 id="-rambling">💬 Rambling</h2>
<p>Welp, if you haven&rsquo;t been living under a rock, AI now cost money yo&hellip;
WTF is a token&hellip; well it&rsquo;s a magical thing that makes Sam Altman ALOT of money&hellip; in theory?</p>
<p>That being said&hellip; There are a lot of things I&rsquo;d rather do with my Saturday than sit and fuck around with Linux services.</p>
<p>I dunno, I think I have a pretty weird idea with what I consider fun.
I should probably be cleaning or doing some chores, but I digress&hellip;
AND&hellip; to be honest, I&rsquo;m pretty far behind with working on my German. I really need to catch up. Das ist nicht gut.</p>
<p>I also spent a good chunk of yesterday trying to fix my Hermes install. That poor thing really got messed up. I mean it doesn&rsquo;t know how to UN-fuck itself sometimes and you gotta go in and then use a different agent to go fix it, which is really annoying. Like literally I&rsquo;ve used Crush and Goose to fix OpenCode before&hellip; and half the time Crush won&rsquo;t make system level changes, becuase it&rsquo;s too careful&hellip; normally good. Then I just break out Goose, which is HONKING MAD&hellip; 🪿🔪💻 That &hellip; thing&hellip; will do whatever bat shit instructions you tell it, more or less. IIRC only Ante or Pi might be more pants on head&hellip;?</p>
<p>I&rsquo;m also using <a href="https://handy.computer/">handy</a> to write some of this because I think it&rsquo;s easier for me to try to say what I&rsquo;m thinking and ramble on versus trying to type it out.</p>
<p>&hellip; ANYWAY&hellip;</p>
<h2 id="-what-you-came-here-for">📏 What you came here for</h2>
<p>I needed to run an LLM proxy stack on my home server: <a href="https://www.getbifrost.ai">Bifrost</a> (AI gateway), <a href="https://headroom-ai.com">Headroom</a> (caching proxy), and optionally <a href="https://github.com/features/copilot">copilot-api</a>. I wanted proper systemd services — restart on failure, logging, the works.</p>
<p>So threw OpenCode at it. What followed was a masterclass in over-engineering, followed by a <a href="https://github.com/DietrichGebert/ponytail">Ponytail</a> beatdown.</p>
<h2 id="-round-1-the-enterprise-edition-545-lines">🏅 Round 1: The &ldquo;Enterprise&rdquo; Edition (545 lines)</h2>
<p>The AI&rsquo;s first pass was <em>impressive</em>. Three services, dependency chains, readiness checks with curl loops, PATH detection, wrapper scripts, environment file generation, pre-install dependency caching&hellip; it looked like something a cloud provider would ship.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># (545 lines of... this)</span>
</span></span><span style="display:flex;"><span>wait_for_port<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    local host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span> port<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$2<span style="color:#e6db74">&#34;</span> timeout<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>3<span style="color:#66d9ef">:-</span>30<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> i in <span style="color:#66d9ef">$(</span>seq <span style="color:#ae81ff">1</span> <span style="color:#e6db74">&#34;</span>$timeout<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> curl -sf <span style="color:#e6db74">&#34;http://</span><span style="color:#e6db74">${</span>host<span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span>port<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> &gt; /dev/null 2&gt;&amp;1; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>        sleep <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>It was <em>correct</em>. It was <em>thorough</em>. It was also fucking overkill for three services on a laptop. Not even on my home server&hellip;</p>
<h2 id="-round-2-the-middle-ground">🫠 Round 2: The Middle Ground</h2>
<p>I pushed back. The AI collapsed it to a single service running <code>llm_proxy.sh</code> with <code>trap</code> + <code>wait</code> so bash stayed alive. Cleaner, but still had a wrapper 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;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>trap <span style="color:#e6db74">&#39;exit 0&#39;</span> SIGINT SIGTERM
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>wait
</span></span></code></pre></div><p>Better, but I asked myself: wait, why do I need a wrapper at all?</p>
<h2 id="-round-3-three-lines-of-execstart">💯 Round 3: Three Lines of ExecStart</h2>
<p>The final version (189 lines for the <em>installer</em>, and the service definitions themselves are practically nothing):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[Service]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Type</span><span style="color:#f92672">=</span><span style="color:#e6db74">simple</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">bunx @maximhq/bifrost -port 11434 -host 0.0.0.0</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Restart</span><span style="color:#f92672">=</span><span style="color:#e6db74">on-failure</span>
</span></span></code></pre></div><p>That&rsquo;s it. No wrapper. No PATH detection. No readiness checks. No env file for the first pass. Each service is just:</p>
<table>
  <thead>
      <tr>
          <th>Service</th>
          <th>Command</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>llm-proxy-bifrost</code></td>
          <td><code>bunx @maximhq/bifrost -port 11434 -host 0.0.0.0</code></td>
      </tr>
      <tr>
          <td><code>llm-proxy-headroom</code></td>
          <td><code>uvx --from &quot;headroom-ai[all]&quot; headroom proxy ...</code></td>
      </tr>
      <tr>
          <td><code>llm-proxy-copilot</code></td>
          <td><code>npx copilot-api@latest start</code> (disabled by default)</td>
      </tr>
  </tbody>
</table>
<p>Type=simple. Raw ExecStart. <code>Restart=on-failure</code>. DONE.</p>
<h2 id="-the-headroom-standalone-the-real-lesson">📖 The Headroom Standalone (the real lesson)</h2>
<p>After all that, I realized I wanted Headroom minus Bifrost. So we made a standalone Headroom service with a swappable upstream via <code>.env</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;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[Service]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Environment</span><span style="color:#f92672">=</span><span style="color:#e6db74">HEADROOM_UPSTREAM_URL=http://127.0.0.1:11434</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">EnvironmentFile</span><span style="color:#f92672">=</span><span style="color:#e6db74">-/opt/llm-proxy/.env</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/bin/sh -c &#39;exec uvx --from &#34;headroom-ai[all]&#34; headroom proxy --no-telemetry --code-aware --openai-api-url &#34;$HEADROOM_UPSTREAM_URL&#34;&#39;</span>
</span></span></code></pre></div><p>The <strong>one</strong> shell wrapper I have, and it&rsquo;s just to expand <code>$HEADROOM_UPSTREAM_URL</code> from the env file. Switching between local and OpenRouter:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>echo <span style="color:#e6db74">&#39;HEADROOM_UPSTREAM_URL=https://openrouter.ai/api/v1&#39;</span> | sudo tee /opt/llm-proxy/.env
</span></span><span style="display:flex;"><span>sudo systemctl restart llm-proxy-headroom
</span></span></code></pre></div><h2 id="-what-i-learned">🧠 What I learned</h2>
<p>Everything I cut had a reason:</p>
<ul>
<li><strong>Readiness checks?</strong> <code>Restart=on-failure</code> handles that. If the port isn&rsquo;t up, the service errors and retries. systemd already has a loop.</li>
<li><strong>Wrapper scripts?</strong> If your command fits in <code>ExecStart</code>, put it there. One fewer file to maintain.</li>
<li><strong>PATH detection?</strong> Systemd has <code>Environment=PATH=...</code>. Explicit beats magic.</li>
<li><strong>Hard dependencies?</strong> <code>Wants=</code> (soft) over <code>Requires=</code> (hard). If Headroom starts before Bifrost, it&rsquo;ll fail once and restart fine.</li>
<li><strong>Env file for the first version?</strong> Nope. Default baked into <code>Environment=</code>. Only add the file when you need to override.</li>
</ul>
<p>The AI kept adding things &ldquo;just in case.&rdquo; I kept deleting them. What remained was what I <em>actually</em> needed.</p>
<h2 id="-verdict">⚖️ Verdict</h2>
<p>Less code is better code. The best systemd service is the one you can read in one screen.</p>
<p><strong>4/5 deletions 🪓</strong></p>
<p>&hellip; 5/5 w/ rice 🥡</p>
<h2 id="-tldr">💾 TL;DR</h2>
<p><code>/etc/systemd/system/headroom.service</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;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#f92672">[</span>Unit<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>Description<span style="color:#f92672">=</span>Headroom Caching Proxy
</span></span><span style="display:flex;"><span>After<span style="color:#f92672">=</span>network-online.target
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>Service<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>Type<span style="color:#f92672">=</span>simple
</span></span><span style="display:flex;"><span>User<span style="color:#f92672">=</span>USENAME_HERE
</span></span><span style="display:flex;"><span>Environment<span style="color:#f92672">=</span>PATH<span style="color:#f92672">=</span>/home/USENAME_HERE/.bun/bin:/home/USENAME_HERE/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
</span></span><span style="display:flex;"><span>Environment<span style="color:#f92672">=</span>HOME<span style="color:#f92672">=</span>/home/USENAME_HERE
</span></span><span style="display:flex;"><span>Environment<span style="color:#f92672">=</span>HEADROOM_OUTPUT_SHAPER<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>Environment<span style="color:#f92672">=</span>HEADROOM_UPSTREAM_URL<span style="color:#f92672">=</span>https://openrouter.ai/api/v1
</span></span><span style="display:flex;"><span>EnvironmentFile<span style="color:#f92672">=</span>-/etc/headroom.env
</span></span><span style="display:flex;"><span>ExecStart<span style="color:#f92672">=</span>/bin/sh -c <span style="color:#e6db74">&#39;exec uvx --from &#34;headroom-ai[all]&#34; headroom proxy --no-telemetry --no-code-aware --openai-api-url &#34;$HEADROOM_UPSTREAM_URL&#34;&#39;</span>
</span></span><span style="display:flex;"><span>Restart<span style="color:#f92672">=</span>on-failure
</span></span><span style="display:flex;"><span>RestartSec<span style="color:#f92672">=</span><span style="color:#ae81ff">5</span>
</span></span><span style="display:flex;"><span>StandardOutput<span style="color:#f92672">=</span>append:/var/log/headroom/headroom.log
</span></span><span style="display:flex;"><span>StandardError<span style="color:#f92672">=</span>append:/var/log/headroom/headroom.log
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>Install<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>WantedBy<span style="color:#f92672">=</span>multi-user.target
</span></span></code></pre></div><h2 id="other-posts-like-this">Other posts like this</h2>
<ul>
<li><a href="/review/zmx">Review - ZMX</a></li>
</ul>

        <br>
<div class="rss_avatar">
    
<a href="https://www.gravatar.com/avatar/38b1e3a9923c356e248e749ace56d4fd?s=800" target="_blank" rel="noopener noreferrer">
  <img
  class="gravatar-img"
  
  alt="Avatar"
  loading="lazy"
  src="https://www.gravatar.com/avatar/38b1e3a9923c356e248e749ace56d4fd?s=250"
  />
</a>

</div>
<style>
.rss_avatar{
    float: right;
}
.rss_avatar img{
    border-radius: 100%;
    max-height: 150px;
}
</style>
<br>
<hr>
Reply:
<a href="https://mastodon.social/@jelloeater">🐘 @jelloeater - Masto</a> |
<a href="https://bsky.app/profile/jelloeater.bsky.social">🦋 @jelloeater - Bsky</a> |
<a href="https://bsky.app/profile/did:plc:srmltlrjqfr7mbfdejuxrbss/rss">🦋 @jelloeater - Bsky RSS</a> |
<a href="mailto:jello@jelloeater.me?subject=RE:">📧 Email Me</a> |
<a href="https://jelloeater.me/blog/less-is-more-systemd/#comments">💬 Comment</a>
<br>
<a href="https://jelloeater.me/link/">🌳 LinkTree</a> |
<a href="https://ko-fi.com/jelloeater">☕ Buy me a coffee</a>
<hr>


Tags:
<a href="https://jelloeater.me/tags/ai/">#ai</a>

<a href="https://jelloeater.me/tags/devops/">#devops</a>

<a href="https://jelloeater.me/tags/linux/">#linux</a>

<a href="https://jelloeater.me/tags/systemd/">#systemd</a>

        ]]>
        </content:encoded>
    </item>
  </channel>
</rss>
