<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://gierdo.astounding.technology/feed.xml" rel="self" type="application/atom+xml" /><link href="https://gierdo.astounding.technology/" rel="alternate" type="text/html" /><updated>2026-05-07T08:11:16+00:00</updated><id>https://gierdo.astounding.technology/feed.xml</id><title type="html">gierdo’s blog</title><subtitle>Software is weird.</subtitle><author><name>Dominikus Gierlach</name></author><entry><title type="html">AI: Agentic vim - local, cloudy, losely coupled?</title><link href="https://gierdo.astounding.technology/blog/2026/04/23/agentic-vim" rel="alternate" type="text/html" title="AI: Agentic vim - local, cloudy, losely coupled?" /><published>2026-04-23T00:00:00+00:00</published><updated>2026-04-23T00:00:00+00:00</updated><id>https://gierdo.astounding.technology/blog/2026/04/23/agentic-vim</id><content type="html" xml:base="https://gierdo.astounding.technology/blog/2026/04/23/agentic-vim"><![CDATA[<h1 id="ai-agentic-vim---local-cloudy-losely-coupled">AI: Agentic vim - local, cloudy, losely coupled?</h1>

<p>I have been playing around with / working productively with <em>AI</em> in vim for a
while now. For religious and practical reasons <a href="/blog/2024/03/02/llama-igpu-amd-container">with local llms</a>, but also with <em>AI agents</em>,
namely <a href="https://kiro.dev/cli/"><code class="language-plaintext highlighter-rouge">kiro-cli</code></a>,
<a href="https://opencode.ai/de"><code class="language-plaintext highlighter-rouge">opencode</code></a> and <a href="https://geminicli.com/"><code class="language-plaintext highlighter-rouge">gemini
cli</code></a>, integrated into my workflow and environment with
<a href="https://github.com/folke/sidekick.nvim"><code class="language-plaintext highlighter-rouge">sidekick</code></a>.</p>

<p>Through <code class="language-plaintext highlighter-rouge">sidekick</code>, the different AI agents were integrated in a separate
buffer but with common and uniform keybindings, context integration (file
content, diagnostics, ..) etc.</p>

<p>Still, every AI agent has its own interaction model, UI details, look and feel
etc, and environment integration by <code class="language-plaintext highlighter-rouge">sidekick</code>, while impressive and mostly
surprisingly stable, was rather hacky.</p>

<p>For my personal environment, I’ve been using <code class="language-plaintext highlighter-rouge">gemini</code>. My employer only
provides <code class="language-plaintext highlighter-rouge">kiro-cli</code> and (limited) <code class="language-plaintext highlighter-rouge">opencode</code>, and I want to have an as-uniform
experience as possible. So, for a while I would have wanted to have an
abstraction over the cli agents in my environment.</p>

<p>Theoretically, that’s what
<a href="https://agentclientprotocol.com/get-started/introduction"><em>ACP</em></a> is for.
However, <em>ACP</em> support in <code class="language-plaintext highlighter-rouge">kiro-cli</code> has not been present from the start, and I
became rather familiar with the setup I had in the meantime.</p>

<p>Until I had a bit of time at my hands to touch the setup again last week.</p>

<h2 id="the-problem">The Problem</h2>

<ul>
  <li>I use <code class="language-plaintext highlighter-rouge">nvim</code></li>
  <li>I (have to) use different AI agents (<code class="language-plaintext highlighter-rouge">kiro-cli</code>, <code class="language-plaintext highlighter-rouge">opencode</code>, <code class="language-plaintext highlighter-rouge">gemini</code>)</li>
  <li>I want a uniform look and feel, configuration, capabilities, etc. in my
<code class="language-plaintext highlighter-rouge">nvim</code> environment, no matter which AI agent powers my AI workflow</li>
  <li>In most cases, the environment integration is more important to me than the
agent specific capabilities and advantages that might be lost with it,
especially when it comes to agent autonomy and orchestration</li>
</ul>

<h2 id="possible-solution">Possible solution</h2>

<p>The Agent Client Protocol
<a href="https://agentclientprotocol.com/get-started/introduction">(<em>ACP</em>)</a>
standardizes communication between code editors/IDEs and coding agents.</p>

<p>If all used agents support the <em>ACP</em>, a single <em>ACP</em> client configuration could
provide the solution I want.</p>

<p>Sadly, when I started the fun with “my” main agent <code class="language-plaintext highlighter-rouge">kiro-cli</code>, it has not
supported <em>ACP</em> yet.</p>

<h2 id="solution">Solution</h2>

<p>A while ago, <code class="language-plaintext highlighter-rouge">kiro-cli</code> added support for <em>ACP</em> 🥳!</p>

<p>No more reason not to try it out.</p>

<p>After a bit of research, I decided to use
<a href="https://codecompanion.olimorris.dev/"><em>codecompanion</em></a> to replace <code class="language-plaintext highlighter-rouge">sidekick</code>
for the AI integration.</p>

<p>The full configuration can be found
<a href="https://github.com/gierdo/dotfiles/blob/8992099423a5cbea84bc48d51918e50ea826955d/.config/nvim/lua/plugins/ai.lua">here</a>,
but I will write about a few details of the configuration.</p>

<h3 id="keybindings">Keybindings</h3>

<p>I had the following <code class="language-plaintext highlighter-rouge">sidekick</code> keybindings:</p>

<p><code class="language-plaintext highlighter-rouge">A-a</code> to open/toggle the agent window, including the selection as context, and
<code class="language-plaintext highlighter-rouge">A-i</code> to toggle with the predefined prompt catalogue.</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">keys</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">{</span>
      <span class="s2">"&lt;A-a&gt;"</span><span class="p">,</span>
      <span class="k">function</span><span class="p">()</span>
        <span class="nb">require</span><span class="p">(</span><span class="s2">"sidekick.cli"</span><span class="p">).</span><span class="n">toggle</span><span class="p">({</span> <span class="n">filter</span> <span class="o">=</span> <span class="p">{</span> <span class="n">installed</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">}</span> <span class="p">})</span>
      <span class="k">end</span><span class="p">,</span>
      <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Sidekick Toggle"</span><span class="p">,</span>
      <span class="n">mode</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"n"</span><span class="p">,</span> <span class="s2">"t"</span><span class="p">,</span> <span class="s2">"i"</span> <span class="p">},</span>
    <span class="p">},</span>
    <span class="p">{</span>
      <span class="s2">"&lt;A-a&gt;"</span><span class="p">,</span>
      <span class="k">function</span><span class="p">()</span>
        <span class="nb">require</span><span class="p">(</span><span class="s2">"sidekick.cli"</span><span class="p">).</span><span class="n">send</span><span class="p">({</span>
          <span class="n">filter</span> <span class="o">=</span> <span class="p">{</span> <span class="n">installed</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">},</span>
          <span class="n">msg</span> <span class="o">=</span> <span class="s2">"The current file is {file}, this is the current selection: \n\n```\n{selection}\n```"</span><span class="p">,</span>
        <span class="p">})</span>
      <span class="k">end</span><span class="p">,</span>
      <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Sidekick Toggle with context"</span><span class="p">,</span>
      <span class="n">mode</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"x"</span> <span class="p">},</span>
    <span class="p">},</span>
    <span class="p">{</span>
      <span class="s2">"&lt;A-i&gt;"</span><span class="p">,</span>
      <span class="k">function</span><span class="p">()</span>
        <span class="nb">require</span><span class="p">(</span><span class="s2">"sidekick.cli"</span><span class="p">).</span><span class="n">prompt</span><span class="p">({</span> <span class="n">filter</span> <span class="o">=</span> <span class="p">{</span> <span class="n">installed</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">}</span> <span class="p">})</span>
      <span class="k">end</span><span class="p">,</span>
      <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Sidekick Select Prompt"</span><span class="p">,</span>
      <span class="n">mode</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"n"</span><span class="p">,</span> <span class="s2">"t"</span><span class="p">,</span> <span class="s2">"i"</span><span class="p">,</span> <span class="s2">"x"</span> <span class="p">},</span>
    <span class="p">},</span>
  <span class="p">},</span>
</code></pre></div></div>

<p>For <code class="language-plaintext highlighter-rouge">codecompanion</code>, the same can be achieved with these keybindings:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">({</span> <span class="s2">"n"</span><span class="p">,</span> <span class="s2">"t"</span><span class="p">,</span> <span class="s2">"i"</span> <span class="p">},</span> <span class="s2">"&lt;A-a&gt;"</span><span class="p">,</span> <span class="s2">"&lt;cmd&gt;CodeCompanionChat Toggle&lt;cr&gt;"</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">"CodeCompanion Toggle"</span> <span class="p">})</span>
      <span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">({</span> <span class="s2">"x"</span> <span class="p">},</span> <span class="s2">"&lt;A-a&gt;"</span><span class="p">,</span> <span class="s2">"&lt;cmd&gt;CodeCompanionChat Add&lt;cr&gt;"</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">"CodeCompanion add selection"</span> <span class="p">})</span>
      <span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span>
        <span class="p">{</span> <span class="s2">"n"</span><span class="p">,</span> <span class="s2">"t"</span><span class="p">,</span> <span class="s2">"i"</span><span class="p">,</span> <span class="s2">"x"</span> <span class="p">},</span>
        <span class="s2">"&lt;A-i&gt;"</span><span class="p">,</span>
        <span class="s2">"&lt;cmd&gt;CodeCompanionActions&lt;cr&gt;"</span><span class="p">,</span>
        <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">"CodeCompanion Actions"</span> <span class="p">}</span>
      <span class="p">)</span>
</code></pre></div></div>

<h3 id="buffer-guard">Buffer guard</h3>

<p>The <code class="language-plaintext highlighter-rouge">codecompanion</code> buffer behaves like a normal buffer. Which means that if I
jump in the jumplist while being in it, it would be replaced. Or if a custom /
weird LSP action is triggered. Or…</p>

<p>So, I wanted to protect the <code class="language-plaintext highlighter-rouge">codecompanion</code> buffer, as identified through its
virtual filetype.</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">init</span> <span class="o">=</span> <span class="k">function</span><span class="p">()</span>
      <span class="c1">-- If new buffers are opened or jumps being triggered while focus is on codecompanion, the action should be triggered outside of the codecompanion buffer</span>
      <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_create_autocmd</span><span class="p">(</span><span class="s2">"FileType"</span><span class="p">,</span> <span class="p">{</span>
        <span class="n">pattern</span> <span class="o">=</span> <span class="s2">"codecompanion"</span><span class="p">,</span>
        <span class="n">callback</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
          <span class="n">vim</span><span class="p">.</span><span class="n">bo</span><span class="p">[</span><span class="n">args</span><span class="p">.</span><span class="n">buf</span><span class="p">].</span><span class="n">buflisted</span> <span class="o">=</span> <span class="kc">false</span>
          <span class="kd">local</span> <span class="n">win</span> <span class="o">=</span> <span class="n">vim</span><span class="p">.</span><span class="n">fn</span><span class="p">.</span><span class="n">bufwinid</span><span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">buf</span><span class="p">)</span>
          <span class="k">if</span> <span class="n">win</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span> <span class="k">then</span>
            <span class="k">return</span>
          <span class="k">end</span>
          <span class="c1">-- Guard this window: redirect any non-cc buffer that lands here</span>
          <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_create_autocmd</span><span class="p">(</span><span class="s2">"BufWinEnter"</span><span class="p">,</span> <span class="p">{</span>
            <span class="n">callback</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">ev</span><span class="p">)</span>
              <span class="k">if</span> <span class="ow">not</span> <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_win_is_valid</span><span class="p">(</span><span class="n">win</span><span class="p">)</span> <span class="k">then</span>
                <span class="k">return</span> <span class="kc">true</span>
              <span class="k">end</span>
              <span class="k">if</span> <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_get_current_win</span><span class="p">()</span> <span class="o">~=</span> <span class="n">win</span> <span class="k">then</span>
                <span class="k">return</span>
              <span class="k">end</span>
              <span class="k">if</span> <span class="n">ev</span><span class="p">.</span><span class="n">buf</span> <span class="o">==</span> <span class="n">args</span><span class="p">.</span><span class="n">buf</span> <span class="k">then</span>
                <span class="k">return</span>
              <span class="k">end</span>
              <span class="c1">-- Restore cc buffer in this window, move new buffer elsewhere</span>
              <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_win_set_buf</span><span class="p">(</span><span class="n">win</span><span class="p">,</span> <span class="n">args</span><span class="p">.</span><span class="n">buf</span><span class="p">)</span>
              <span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">w</span> <span class="k">in</span> <span class="nb">ipairs</span><span class="p">(</span><span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_tabpage_list_wins</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span> <span class="k">do</span>
                <span class="k">if</span> <span class="n">w</span> <span class="o">~=</span> <span class="n">win</span> <span class="ow">and</span> <span class="n">vim</span><span class="p">.</span><span class="n">bo</span><span class="p">[</span><span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_win_get_buf</span><span class="p">(</span><span class="n">w</span><span class="p">)].</span><span class="n">filetype</span> <span class="o">~=</span> <span class="s2">"codecompanion"</span> <span class="k">then</span>
                  <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_win_set_buf</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="n">ev</span><span class="p">.</span><span class="n">buf</span><span class="p">)</span>
                  <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_set_current_win</span><span class="p">(</span><span class="n">w</span><span class="p">)</span>
                  <span class="k">return</span>
                <span class="k">end</span>
              <span class="k">end</span>
              <span class="n">vim</span><span class="p">.</span><span class="n">cmd</span><span class="p">(</span><span class="s2">"split | buffer "</span> <span class="o">..</span> <span class="n">ev</span><span class="p">.</span><span class="n">buf</span><span class="p">)</span>
            <span class="k">end</span><span class="p">,</span>
          <span class="p">})</span>
        <span class="k">end</span><span class="p">,</span>
      <span class="p">})</span>
    <span class="k">end</span>
</code></pre></div></div>

<h2 id="tldr">tl;dr</h2>

<ul>
  <li><em>ACP</em> is nice for Agent/Client integration</li>
  <li>setting up an work environment to use agent integration through ACP allows
for the definition of common behaviour and user experience, even with
different agents underneath it all</li>
  <li>nvim can be set up to use ACP with plugins, e.g. codecompanion</li>
  <li>?</li>
  <li>profit</li>
</ul>]]></content><author><name>Dominikus Gierlach</name></author><category term="linux" /><category term="programming" /><category term="vim" /><category term="ai" /><category term="acp" /><category term="agent" /><summary type="html"><![CDATA[AI: Agentic vim - local, cloudy, losely coupled?]]></summary></entry><entry><title type="html">LSP mix and match - cherry-pick capabilities from different LSP servers</title><link href="https://gierdo.astounding.technology/blog/2025/12/14/lsp-mix-and-match" rel="alternate" type="text/html" title="LSP mix and match - cherry-pick capabilities from different LSP servers" /><published>2025-12-14T00:00:00+00:00</published><updated>2025-12-14T00:00:00+00:00</updated><id>https://gierdo.astounding.technology/blog/2025/12/14/lsp-mix-and-match</id><content type="html" xml:base="https://gierdo.astounding.technology/blog/2025/12/14/lsp-mix-and-match"><![CDATA[<h1 id="lsp-mix-and-match---cherry-pick-capabilities-from-different-lsp-servers">LSP mix and match - cherry-pick capabilities from different LSP servers</h1>

<blockquote>
  <p>Note: Recently, <code class="language-plaintext highlighter-rouge">pyrefly</code> has gained the capabilities that were missing for
me to be able to fully switch to it as sole lsp. My current setup doesn’t
make use of cherry-picking LSP capabilities anymore.</p>
</blockquote>

<p>As I have written about <a href="/blog/2024/09/19/nvim-lsp">here</a>, for
most of my file edititing / programming / note-taking / … I use <a href="https://neovim.io">vim
(neovim)</a>.</p>

<p>Not a bare-bones neovim, though. I use many plugins, and I have even more of
them set up. Too many? Maybe. Probably. Especially considering the ever growing
number of software supply chain attacks..</p>

<p>Anyways, that is a different topic for a different day.</p>

<p>Besides convenient plugins, the main building block for developer experience
enhancement and efficiency are <a href="https://microsoft.github.io/language-server-protocol/implementors/servers/"><em>language
servers</em></a>,
which provide stuff like <em>diagnostics</em>, <em>code completion</em>, <em>navigation
enhancements</em> and more.</p>

<p>Besides playing around with its config, I try to actually use vim for
productive work, including programming in <code class="language-plaintext highlighter-rouge">python</code>.</p>

<p>My go-to language server for python has been
<a href="https://github.com/DetachHead/basedpyright"><code class="language-plaintext highlighter-rouge">basedpyright</code></a>, a fork of
microsoft’s <a href="https://github.com/microsoft/pyright"><code class="language-plaintext highlighter-rouge">pyright</code></a>, which basically
adds the functionality of the proprietary <code class="language-plaintext highlighter-rouge">pylance</code> extension to <code class="language-plaintext highlighter-rouge">pyright</code> and
adds some more bells and whistles.</p>

<p>While <code class="language-plaintext highlighter-rouge">basedpyright</code> is stable, helpful, convenient and generally excellent,
there are a few new kids on the block:</p>

<p>There is a new generation of <code class="language-plaintext highlighter-rouge">python</code> language servers, written in <code class="language-plaintext highlighter-rouge">Rust</code>,
targeting high performance and productivity in large and complex code bases:</p>

<p>There is Astral’s <a href="https://docs.astral.sh/ty/"><code class="language-plaintext highlighter-rouge">ty</code></a>, Meta’s
<a href="https://pyrefly.org/"><code class="language-plaintext highlighter-rouge">pyrefly</code></a> and David Halter’s (Jedi’s original author)
<a href="https://zubanls.com"><code class="language-plaintext highlighter-rouge">zuban</code></a>, and probably some more.</p>

<p>I have been intrigued by them, mainly hoping for a smoother and more performant
workflow in large and growing codebases. So, I have played around with them for
a while now, eventually ending up using <code class="language-plaintext highlighter-rouge">ty</code> only for type checking while still
having to rely on <code class="language-plaintext highlighter-rouge">basedpyright</code> for completion, auto-import, symbol renaming,
navigation etc.</p>

<p><em>Why?</em></p>

<p>In single capabilities, none of the new lsp servers match <code class="language-plaintext highlighter-rouge">basedpyright</code> in my
workflow. Yet.</p>

<p>E.g.:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">pyrefly</code> has great completion, navigation and even auto-import, but it’s
really bad in renaming symbols. It can rename variables, but no functions.</li>
  <li><code class="language-plaintext highlighter-rouge">ty</code> has limited support of <code class="language-plaintext highlighter-rouge">pydantic</code>, which I use a lot, completion is not
as powerful as <code class="language-plaintext highlighter-rouge">pyrefly</code>’s, but renaming works great.</li>
</ul>

<p><em>Why not use both?</em></p>

<p>If you enable several language servers with the (nominally) same capabilities
for the same file type, you get duplicate results/actions for every capability.
Go to definition? Select where you want to jump to from these two identical
alternatives!</p>

<p>My goal was to mix-and-match, e.g. use <code class="language-plaintext highlighter-rouge">ty</code> for renaming and <code class="language-plaintext highlighter-rouge">pyrefly</code> for
everything else. However, while especially <code class="language-plaintext highlighter-rouge">pyrefly</code>’s configuration is very
flexible, the lsps don’t support tailored enabling and disabling of select
capabilities.</p>

<p>I tried for an unsuccessful while to configure the lsp server by restricting
the handed-over client capabilities, but that didn’t go anywhere.</p>

<p>The only almost compromise that worked for me with the configuration through
the lsp servers themselves was <em>disabling</em> <code class="language-plaintext highlighter-rouge">basedpyright</code>’s type checking and
<em>disabling</em> <code class="language-plaintext highlighter-rouge">ty</code>’s language server capabilities, besides type checking. Like
this, at least a little bit of the heavy lifting could be offloaded from
<code class="language-plaintext highlighter-rouge">basedpyright</code> to <code class="language-plaintext highlighter-rouge">ty</code>, but I was not happy.</p>

<p>Now, I sort of am happy! I found out how to disable specific lsp capabilities
for specific lsp servers through <a href="https://neovim.io/doc/user/lsp.html"><code class="language-plaintext highlighter-rouge">nvim</code>’s lsp
configuration</a>. Instead of trying to get
the lsp server to not provide the functionality, the <em>client</em> ignores it!</p>

<h2 id="the-problem">The Problem</h2>

<p>I like working with Python, also with larger and growing code bases. There are
LSP servers that offer excellent performance. However, none match the full
functionality of <code class="language-plaintext highlighter-rouge">basedpyright</code> when used independently. Unfortunately,
<code class="language-plaintext highlighter-rouge">basedpyright</code> lacks in performance. Combining multiple LSP servers introduces
duplicate functionality and a bit of a mess. Such combination could be feasible
if we could selectively disable or enable conflicting capabilities. However, it
is not possible to instruct LSP servers to withhold certain functionalities
unless they provide an interface for that, e.g. with dedicated configuration
options.</p>

<h2 id="the-solution">The solution</h2>

<p>Enable conflicting lsp servers, but tell the lsp client (<em>nvim</em>) that it should
not use specific lsp server’s capabilities through an <code class="language-plaintext highlighter-rouge">on_init</code> hook 🥳!</p>

<p>The configuration is probably not fully final and will change, but this is the
general thing.</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="kd">local</span> <span class="n">lsp_capabilities</span> <span class="o">=</span> <span class="nb">require</span><span class="p">(</span><span class="s2">"blink.cmp"</span><span class="p">).</span><span class="n">get_lsp_capabilities</span><span class="p">(</span><span class="kc">nil</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span>

  <span class="c1">---@param client vim.lsp.Client</span>
  <span class="kd">local</span> <span class="n">ty_on_init</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">client</span><span class="p">)</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">callHierarchyProvider</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">completionProvider</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">inlineCompletionProvider</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">inlineValueProvider</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">declarationProvider</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">definitionProvider</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">implementationProvider</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">inlayHintProvider</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">signatureHelpProvider</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">hoverProvider</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">referencesProvider</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">codeActionProvider</span> <span class="o">=</span> <span class="kc">false</span>
  <span class="k">end</span>
  <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">config</span><span class="p">(</span><span class="s2">"ty"</span><span class="p">,</span> <span class="p">{</span>
    <span class="n">capabilities</span> <span class="o">=</span> <span class="n">lsp_capabilities</span><span class="p">,</span>
    <span class="n">on_init</span> <span class="o">=</span> <span class="p">{</span> <span class="n">ty_on_init</span> <span class="p">},</span>
    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
      <span class="n">ty</span> <span class="o">=</span> <span class="p">{</span>
        <span class="n">disableLanguageServices</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
        <span class="n">diagnosticMode</span> <span class="o">=</span> <span class="s2">"workspace"</span><span class="p">,</span>
        <span class="n">experimental</span> <span class="o">=</span> <span class="p">{</span>
          <span class="n">rename</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
        <span class="p">},</span>
      <span class="p">},</span>
    <span class="p">},</span>
  <span class="p">})</span>

  <span class="c1">---@param client vim.lsp.Client</span>
  <span class="kd">local</span> <span class="n">pyrefly_on_init</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">client</span><span class="p">)</span>
    <span class="n">client</span><span class="p">.</span><span class="n">server_capabilities</span><span class="p">.</span><span class="n">renameProvider</span> <span class="o">=</span> <span class="kc">false</span>
  <span class="k">end</span>
  <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">config</span><span class="p">(</span><span class="s2">"pyrefly"</span><span class="p">,</span> <span class="p">{</span>
    <span class="n">capabilities</span> <span class="o">=</span> <span class="n">lsp_capabilities</span><span class="p">,</span>
    <span class="n">on_init</span> <span class="o">=</span> <span class="p">{</span> <span class="n">pyrefly_on_init</span> <span class="p">},</span>
    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
      <span class="n">python</span> <span class="o">=</span> <span class="p">{</span>
        <span class="n">pyrefly</span> <span class="o">=</span> <span class="p">{</span>
          <span class="n">disableLanguageServices</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
          <span class="n">displayTypeErrors</span> <span class="o">=</span> <span class="s2">"force-on"</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="n">analysis</span> <span class="o">=</span> <span class="p">{</span>
          <span class="n">diagnosticMode</span> <span class="o">=</span> <span class="s2">"workspace"</span><span class="p">,</span>
          <span class="n">inlayHints</span> <span class="o">=</span> <span class="p">{</span>
            <span class="n">callArgumentNames</span> <span class="o">=</span> <span class="s2">"all"</span><span class="p">,</span>
            <span class="n">variableTypes</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
            <span class="n">functionReturnTypes</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
            <span class="n">pytestParameters</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
          <span class="p">},</span>
        <span class="p">},</span>
      <span class="p">},</span>
    <span class="p">},</span>
  <span class="p">})</span>

</code></pre></div></div>

<h2 id="tldr">tl;dr</h2>

<ul>
  <li>Some lsp servers are not powerful enough to fully replace alternatives but
would be great enhancements or replacements for single capabilities</li>
  <li><em>nvim</em>’s lsp config API allows for specifying different hooks, <code class="language-plaintext highlighter-rouge">on_init</code> and
<code class="language-plaintext highlighter-rouge">on_attach</code> etc.</li>
  <li>It’s possible to disable specific capabilities in those hooks</li>
</ul>]]></content><author><name>Dominikus Gierlach</name></author><category term="linux" /><category term="programming" /><category term="vim" /><category term="lsp" /><category term="configuration" /><category term="capabilities" /><category term="ty" /><category term="python" /><category term="pyrefly" /><summary type="html"><![CDATA[LSP mix and match - cherry-pick capabilities from different LSP servers]]></summary></entry><entry><title type="html">update: ‘secure’ automatic system decryption - tpm2 + LUKS + systemd-cryptenroll</title><link href="https://gierdo.astounding.technology/blog/2025/07/05/tpm2-luks-systemd" rel="alternate" type="text/html" title="update: ‘secure’ automatic system decryption - tpm2 + LUKS + systemd-cryptenroll" /><published>2025-07-05T00:00:00+00:00</published><updated>2025-07-05T00:00:00+00:00</updated><id>https://gierdo.astounding.technology/blog/2025/07/05/tpm2-luks-systemd</id><content type="html" xml:base="https://gierdo.astounding.technology/blog/2025/07/05/tpm2-luks-systemd"><![CDATA[<h1 id="secure-automatic-system-decrpytion-with-tpm-and-luks---update">‘Secure’ automatic system decrpytion with tpm and LUKS - update</h1>

<p>As I have written about <a href="/blog/2023/12/10/tpm2-luks">here</a>, I
sort of have accepted a compromise in security for added convenience,
auto-unlocking my <a href="https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup">LUKS</a>
encrypted root partition making use of my system’s built in TPM in order to
only auto-unlock the system under specific (secure) conditions.</p>

<p>I’ve used <code class="language-plaintext highlighter-rouge">clevis</code> so far to manage the setup, using the trusty
<code class="language-plaintext highlighter-rouge">initramfs-tools</code> to configure and bake the <em>initramfs</em> with the required
modules and configuration, relying on <code class="language-plaintext highlighter-rouge">cryptsetup</code> to manage and reset the
specifics of the configuration, e.g. whenever a bios update invalidated a <em>PCR
measurement</em>.</p>

<p>Not that elegant. And there are new, more elegant and capable mechanisms
available!</p>

<ul>
  <li>There is <code class="language-plaintext highlighter-rouge">systemd-cryptenroll</code> to enroll physical tokens to LUKS2 encrypted
volumes, simplifying the setup and management of this part of the process</li>
  <li>There is <code class="language-plaintext highlighter-rouge">dracut</code> for the configuration and management of <em>initramfs</em> images,
the designated successor of <code class="language-plaintext highlighter-rouge">initramfs-tools</code></li>
</ul>

<p>While my previous setup worked, I wanted to try setting it up with the
(designated) successor systems.</p>

<p>Well. To be honest, first I just wanted to try out <code class="language-plaintext highlighter-rouge">systemd-cryptenroll</code>
instead of <code class="language-plaintext highlighter-rouge">clevis</code>, realized that the intended way of configuring
auto-unlocking didn’t work with <code class="language-plaintext highlighter-rouge">initramfs-tools</code>, realized that (due to that,
amongst other things) there have been discussions about replacing
<code class="language-plaintext highlighter-rouge">initramfs-tools</code> with <code class="language-plaintext highlighter-rouge">dracut</code> as default and then decided to redo the whole
process.</p>

<h2 id="the-problem">The Problem</h2>

<p>Well, it’s not really a new problem, it’s an itch and a mental problem, I guess
😅. The original challenge, shifting the compromise of convenience vs. security
a little by auto-unlocking the encrypted system with the help of the TPM has
been working for a while.</p>

<p>But:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">systemd-cryptenroll</code> exists, potential successor to <code class="language-plaintext highlighter-rouge">clevis</code></li>
  <li><code class="language-plaintext highlighter-rouge">dracut</code> exists, potential successor to <code class="language-plaintext highlighter-rouge">initramfs-tools</code></li>
  <li>Both of those systems are very capable</li>
  <li>My setup is using their (potential) predecessors</li>
</ul>

<h2 id="the-solution">The solution</h2>

<h3 id="systemd-cryptenroll">systemd-cryptenroll</h3>

<p>Installing <code class="language-plaintext highlighter-rouge">systemd-cryptenroll</code> on Debian is easy enough. It comes with
<code class="language-plaintext highlighter-rouge">systemd-cryptsetup</code>, which might be on your system already?
If not:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>systemd-cryptsetup
</code></pre></div></div>

<p>Then, enrolling the TPM with the encrypted partition is pretty straightforward:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemd-cryptenroll <span class="nt">--tpm2-device</span><span class="o">=</span>auto /dev/nvme0n1p3
</code></pre></div></div>

<p>Depending on your needs/level-of-paranoia, you might want to add other PCRs, as
<code class="language-plaintext highlighter-rouge">systemd-cryptenroll</code> per default only uses PCR 7, maybe you even want to use
TPM + PIN:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemd-cryptenroll <span class="nt">--tpm2-device</span><span class="o">=</span>auto <span class="nt">--tpm2-pcrs</span><span class="o">=</span>7+11 <span class="nt">--tpm2-with-pin</span><span class="o">=</span><span class="nb">yes</span> /dev/nvme0n1p3
</code></pre></div></div>

<p>You follow the guided process, and then can verify success. Hopefully.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemd-cryptenroll /dev/nvme0n1p3
SLOT TYPE
   0 password
   1 tpm2
</code></pre></div></div>

<p>Configuration should (™️) work by configuring the tpm in <code class="language-plaintext highlighter-rouge">/etc/crypttab</code> and
rebuilding the <em>initramfs</em> with it:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> /etc/crypttab
nvme0n1p3_crypt <span class="nv">UUID</span><span class="o">=</span>e010d6c1-6016-4f2d-bd05-1be8f383ada9 none tpm2-device<span class="o">=</span>auto,luks,discard
</code></pre></div></div>

<h3 id="building-the-initramfs-with-initramfs-tools-fail">building the initramfs with initramfs-tools (fail)</h3>

<p>But, alas, rebuilding the initial ram disk fails on Debian and should also fail
on Ubuntu:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>update-initramfs <span class="nt">-u</span> <span class="nt">-k</span> all
...
cryptsetup: WARNING: nvme0n1p3_crypt: ignoring unknown option <span class="s1">'tpm2-device'</span>
...
</code></pre></div></div>

<p>After a bit of research: <code class="language-plaintext highlighter-rouge">initramfs-tools</code> doesn’t support tpm2 devices. There
is a <a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1031254">bug report</a>
and even an open <a href="https://salsa.debian.org/cryptsetup-team/cryptsetup/-/merge_requests/39">merge request with a
fix</a>,
but the situation appears to be stale.</p>

<p>However, there is the designated successor of <code class="language-plaintext highlighter-rouge">initramfs-tools</code>:
<a href="https://en.wikipedia.org/wiki/Dracut_\(software\)"><code class="language-plaintext highlighter-rouge">dracut</code></a>, which has
replaced <code class="language-plaintext highlighter-rouge">initramfs-tools</code> in a bunch of linux distributions already.</p>

<h3 id="building-the-initramfs-with-dracut">building the initramfs with dracut</h3>

<p>On debian and Ubuntu, it can be installed from the package sources like this,
replacing <code class="language-plaintext highlighter-rouge">initramfs-tools</code> in the process:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>dracut
</code></pre></div></div>

<p>Configuration of dracut happens in <code class="language-plaintext highlighter-rouge">/etc/dracut.conf.d/*.conf</code>.</p>

<p>I configured dracut to run the host-only initramfs creation with tpm2 support
and compress with <code class="language-plaintext highlighter-rouge">lz4</code>, all in separate files:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> /etc/dracut.conf.d/compression.conf
<span class="nv">compress</span><span class="o">=</span><span class="s2">"lz4"</span>

<span class="nb">cat</span> /etc/dracut.conf.d/hostonly.conf
<span class="nv">hostonly</span><span class="o">=</span><span class="s2">"yes"</span>
<span class="nv">hostonly_cmdline</span><span class="o">=</span><span class="s2">"no"</span>

<span class="nb">cat</span> /etc/dracut.conf.d/tpm.conf
add_dracutmodules+<span class="o">=</span><span class="s2">" tpm2-tss crypt "</span>
add_drivers+<span class="o">=</span><span class="s2">" tpm tpm_tis_core tpm_tis tpm_crb "</span>
</code></pre></div></div>

<p>With that configuration in place, the initramfs can be (re)built with</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dracut <span class="nt">-f</span>
</code></pre></div></div>

<p>After that, a reboot should end up with an  automatically unlocked encrypted
partition.</p>

<h2 id="tldr">tl;dr</h2>

<p>If you trust your system firmware and tpm enough, you can set your system up to
decrypt automatically without providing a passphrase using
<code class="language-plaintext highlighter-rouge">systemd-cryptenroll</code> and <code class="language-plaintext highlighter-rouge">dracut</code>.</p>

<p>There are also <a href="/blog/2023/12/10/tpm2-luks">other methods</a>, but
this one appears to be the most future proof and easy to set-up and maintain.</p>]]></content><author><name>Dominikus Gierlach</name></author><category term="linux" /><category term="security" /><category term="tpm 2.0" /><category term="systemd-cryptenroll" /><summary type="html"><![CDATA[‘Secure’ automatic system decrpytion with tpm and LUKS - update]]></summary></entry><entry><title type="html">Amazon Q in vim</title><link href="https://gierdo.astounding.technology/blog/2025/05/25/amazon-q-vim" rel="alternate" type="text/html" title="Amazon Q in vim" /><published>2025-05-25T00:00:00+00:00</published><updated>2025-05-25T00:00:00+00:00</updated><id>https://gierdo.astounding.technology/blog/2025/05/25/amazon-q-vim</id><content type="html" xml:base="https://gierdo.astounding.technology/blog/2025/05/25/amazon-q-vim"><![CDATA[<h1 id="amazon-q---assistant-in-vim">Amazon Q - Assistant in vim</h1>

<h2 id="the-problem">The Problem</h2>

<p>My employer is a typical German large scale corporation. One of the attributes
that come with that is that we are, of course, interested in generative AI, but
also very sceptical and cautious.</p>

<p>Slowly, however, more and more AI services become available to us, but
basically only web-interface chat solutions without APIs and IDE/development
related integrations.</p>

<p>I was moderately happy when recently it was announced that we can use <a href="https://aws.amazon.com/q/developer/"><em>Amazon
Q Developer</em></a>! Awesome! Finally a
development focused solution!</p>

<p>There are extensions for JetBrains IDEs, VS Code, Visual Studio and a preview
for Eclipse, and there is a <em>cli</em>.</p>

<p>That should be enough, right?</p>

<p>…</p>

<p>Well. Not for all of us neovim users.</p>

<p>I want to have integration to the full potential of generative AI, adhering to
applicable compliance restrictions, and still remain in the tool environment
that makes me as productive and makes my work as enjoyable as possible.</p>

<h2 id="possible-solution">Possible solution</h2>

<p>There are many pretty excellent neovim AI plugins out there, e.g.
<a href="https://github.com/yetone/avante.nvim">Avante</a> or <a href="https://github.com/CopilotC-Nvim/CopilotChat.nvim">Copilot
Chat</a>.</p>

<p>While those are pretty nice and powerful, most of them are taylored around a
number of the big AI API providers, and are pretty big to work with.</p>

<p><em>Amazon Q</em> does not provide a nice API, especially not an API that is
compatible with existing clients and integrations. I spent a few minutes
looking through Avante and did not see a quick path to extend the existing (web
API centric) model adaption with Q.</p>

<p>I have been tinkering with <a href="https://github.com/gierdo/neoai.nvim">a fork</a> of
<a href="https://github.com/Bryley/neoai.nvim">neoai.nvim</a>, extending it a little for
my usecases, focussing on low-overhead integration of local language models
with llama.cpp. I’ve been very happy with it, especially because the plugin is
still so small that I don’t have a hard time understanding what it does and how
to extend it without having to spend too much time. I’m lazy..</p>

<p>So, I decided to simply extend the plugin I’m most familiar with, <a href="https://github.com/gierdo/neoai.nvim">my
fork</a>.</p>

<h2 id="solution">Solution</h2>

<p>As written above, <em>Amazon Q</em> does not provide a nice API, but has a cli, called
<code class="language-plaintext highlighter-rouge">q</code>. I decided to keep it simple and simply dump my prompt into <code class="language-plaintext highlighter-rouge">q</code> in
<em>non-interactive</em> mode, parsing its <code class="language-plaintext highlighter-rouge">stdout</code> output. I hoped that it would
allow me to use the existing way of providing persistent chat sessions and
context, as well as having a simple way to apply existing functionality, e.g.
code recognition for easy pasting with <code class="language-plaintext highlighter-rouge">:put c</code> etc.</p>

<p>It was surprisingly easy, the core of it all is fifty-ish lines of <code class="language-plaintext highlighter-rouge">lua</code>:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">utils</span> <span class="o">=</span> <span class="nb">require</span><span class="p">(</span><span class="s2">"neoai.utils"</span><span class="p">)</span>

<span class="c1">--- This model definition supports Amazon Q CLI integration</span>
<span class="c1">---@type ModelModule</span>
<span class="kd">local</span> <span class="n">M</span> <span class="o">=</span> <span class="p">{}</span>

<span class="n">M</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="s2">"Amazon Q"</span>

<span class="n">M</span><span class="p">.</span><span class="n">_chunks</span> <span class="o">=</span> <span class="p">{}</span>

<span class="n">M</span><span class="p">.</span><span class="n">get_current_output</span> <span class="o">=</span> <span class="k">function</span><span class="p">()</span>
 <span class="k">return</span> <span class="nb">table.concat</span><span class="p">(</span><span class="n">M</span><span class="p">.</span><span class="n">_chunks</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
<span class="k">end</span>

<span class="c1">---@param chunk string</span>
<span class="c1">---@param on_stdout_chunk fun(chunk: string) Function to call whenever a stdout chunk occurs</span>
<span class="n">M</span><span class="p">.</span><span class="n">_recieve_chunk</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">chunk</span><span class="p">,</span> <span class="n">on_stdout_chunk</span><span class="p">)</span>
 <span class="c1">-- Split the input chunk by newlines</span>
 <span class="n">on_stdout_chunk</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
 <span class="nb">table.insert</span><span class="p">(</span><span class="n">M</span><span class="p">.</span><span class="n">_chunks</span><span class="p">,</span> <span class="n">chunk</span><span class="p">)</span>
<span class="k">end</span>

<span class="c1">---@param chat_history ChatHistory</span>
<span class="c1">---@param on_stdout_chunk fun(chunk: string) Function to call whenever a stdout chunk occurs</span>
<span class="c1">---@param on_complete fun(err?: string, output?: string) Function to call when model has finished</span>
<span class="n">M</span><span class="p">.</span><span class="n">send_to_model</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">chat_history</span><span class="p">,</span> <span class="n">on_stdout_chunk</span><span class="p">,</span> <span class="n">on_complete</span><span class="p">)</span>
 <span class="c1">-- Format messages for Amazon Q CLI</span>
 <span class="kd">local</span> <span class="n">messages_json</span> <span class="o">=</span> <span class="n">vim</span><span class="p">.</span><span class="n">json</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="n">chat_history</span><span class="p">.</span><span class="n">messages</span><span class="p">)</span>

 <span class="n">chunks</span> <span class="o">=</span> <span class="p">{}</span>

 <span class="kd">local</span> <span class="n">command</span> <span class="o">=</span> <span class="p">{}</span>
 <span class="n">command</span> <span class="o">=</span> <span class="p">{</span>
  <span class="s2">"chat"</span><span class="p">,</span>
  <span class="s2">"--no-interactive"</span><span class="p">,</span>
 <span class="p">}</span>
 <span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">v</span> <span class="k">in</span> <span class="nb">ipairs</span><span class="p">(</span><span class="n">chat_history</span><span class="p">.</span><span class="n">params</span><span class="p">)</span> <span class="k">do</span>
  <span class="nb">table.insert</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span>
 <span class="k">end</span>
 <span class="nb">table.insert</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="n">messages_json</span><span class="p">)</span>

 <span class="c1">-- Execute the Amazon Q CLI command</span>
 <span class="n">utils</span><span class="p">.</span><span class="n">exec</span><span class="p">(</span><span class="s2">"q"</span><span class="p">,</span> <span class="n">command</span><span class="p">,</span> <span class="k">function</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
  <span class="n">M</span><span class="p">.</span><span class="n">_recieve_chunk</span><span class="p">(</span><span class="n">chunk</span><span class="p">,</span> <span class="n">on_stdout_chunk</span><span class="p">)</span>
 <span class="k">end</span><span class="p">,</span> <span class="k">function</span><span class="p">(</span><span class="n">err</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span>
  <span class="c1">-- Clean up the temporary file</span>

  <span class="n">on_complete</span><span class="p">(</span><span class="n">err</span><span class="p">,</span> <span class="n">M</span><span class="p">.</span><span class="n">get_current_output</span><span class="p">())</span>
 <span class="k">end</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">return</span> <span class="n">M</span>
</code></pre></div></div>

<p><em>Q</em> behaves a bit weird, it only marks source-code it produces with colors,
using <em>ansi</em> codes, instead of using the de-facto standard backtics. Luckily it
appears to be consistent, so code snippet extraction works like this:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">extract_code_snippets</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
  <span class="kd">local</span> <span class="n">matches</span> <span class="o">=</span> <span class="p">{}</span>
  <span class="c1">-- Amazon Q marks code by color-coding it green with ansi codes.</span>
  <span class="c1">-- Everything between "start green" and "reset color" is assumed to be code.</span>
  <span class="k">for</span> <span class="n">match</span> <span class="k">in</span> <span class="nb">string.gmatch</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="s2">"</span><span class="se">\27</span><span class="s2">%[38;5;%d+m(.-)\27%[0m"</span><span class="p">)</span> <span class="k">do</span>
    <span class="nb">table.insert</span><span class="p">(</span><span class="n">matches</span><span class="p">,</span> <span class="n">match</span><span class="p">)</span>
  <span class="k">end</span>
  <span class="k">return</span> <span class="nb">table.concat</span><span class="p">(</span><span class="n">matches</span><span class="p">,</span> <span class="s2">"</span><span class="se">\n\n</span><span class="s2">"</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>

<p>I run environments that do not have <code class="language-plaintext highlighter-rouge">q</code> available, so I made my config
sensitive to its availability.</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span> <span class="o">=</span> <span class="k">function</span><span class="p">()</span>
      <span class="kd">local</span> <span class="n">q_available</span> <span class="o">=</span> <span class="n">vim</span><span class="p">.</span><span class="n">fn</span><span class="p">.</span><span class="n">executable</span><span class="p">(</span><span class="s2">"q"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>

      <span class="kd">local</span> <span class="n">models</span> <span class="o">=</span> <span class="p">{}</span>
      <span class="kd">local</span> <span class="n">extract_code_snippets</span>

      <span class="k">if</span> <span class="n">q_available</span> <span class="k">then</span>
        <span class="n">extract_code_snippets</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
          <span class="kd">local</span> <span class="n">matches</span> <span class="o">=</span> <span class="p">{}</span>
          <span class="c1">-- Amazon Q marks code by color-coding it green with ansi codes.</span>
          <span class="c1">-- Everything between "start green" and "reset color" is assumed to be code.</span>
          <span class="k">for</span> <span class="n">match</span> <span class="k">in</span> <span class="nb">string.gmatch</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="s2">"</span><span class="se">\27</span><span class="s2">%[38;5;%d+m(.-)\27%[0m"</span><span class="p">)</span> <span class="k">do</span>
            <span class="nb">table.insert</span><span class="p">(</span><span class="n">matches</span><span class="p">,</span> <span class="n">match</span><span class="p">)</span>
          <span class="k">end</span>
          <span class="k">return</span> <span class="nb">table.concat</span><span class="p">(</span><span class="n">matches</span><span class="p">,</span> <span class="s2">"</span><span class="se">\n\n</span><span class="s2">"</span><span class="p">)</span>
        <span class="k">end</span>

        <span class="nb">table.insert</span><span class="p">(</span><span class="n">models</span><span class="p">,</span> <span class="p">{</span>
          <span class="n">name</span> <span class="o">=</span> <span class="s2">"q"</span><span class="p">,</span>
          <span class="n">model</span> <span class="o">=</span> <span class="s2">"q"</span><span class="p">,</span>
          <span class="n">params</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"--trust-all-tools"</span> <span class="p">},</span>
        <span class="p">})</span>
      <span class="k">else</span>
        <span class="n">startLlama</span><span class="p">()</span>

        <span class="n">extract_code_snippets</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
          <span class="kd">local</span> <span class="n">matches</span> <span class="o">=</span> <span class="p">{}</span>
          <span class="k">for</span> <span class="n">match</span> <span class="k">in</span> <span class="nb">string.gmatch</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="s2">"```%w*\n(.-)```"</span><span class="p">)</span> <span class="k">do</span>
            <span class="nb">table.insert</span><span class="p">(</span><span class="n">matches</span><span class="p">,</span> <span class="n">match</span><span class="p">)</span>
          <span class="k">end</span>

          <span class="c1">-- Next part matches any code snippets that are incomplete</span>
          <span class="kd">local</span> <span class="n">count</span> <span class="o">=</span> <span class="nb">select</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="nb">string.gsub</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="s2">"```"</span><span class="p">,</span> <span class="s2">"```"</span><span class="p">))</span>
          <span class="k">if</span> <span class="n">count</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span> <span class="k">then</span>
            <span class="kd">local</span> <span class="n">pattern</span> <span class="o">=</span> <span class="s2">"```%w*\n([^`]-)$"</span>
            <span class="kd">local</span> <span class="n">match</span> <span class="o">=</span> <span class="nb">string.match</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">pattern</span><span class="p">)</span>
            <span class="nb">table.insert</span><span class="p">(</span><span class="n">matches</span><span class="p">,</span> <span class="n">match</span><span class="p">)</span>
          <span class="k">end</span>
          <span class="k">return</span> <span class="nb">table.concat</span><span class="p">(</span><span class="n">matches</span><span class="p">,</span> <span class="s2">"</span><span class="se">\n\n</span><span class="s2">"</span><span class="p">)</span>
        <span class="k">end</span>

        <span class="nb">table.insert</span><span class="p">(</span><span class="n">models</span><span class="p">,</span> <span class="p">{</span>
          <span class="n">name</span> <span class="o">=</span> <span class="s2">"openai"</span><span class="p">,</span>
          <span class="n">model</span> <span class="o">=</span> <span class="s2">"llama 3"</span><span class="p">,</span>
          <span class="n">params</span> <span class="o">=</span> <span class="kc">nil</span><span class="p">,</span>
        <span class="p">})</span>
      <span class="k">end</span>

      <span class="nb">require</span><span class="p">(</span><span class="s2">"neoai"</span><span class="p">).</span><span class="n">setup</span><span class="p">({</span> <span class="c1">---@diagnostic disable-line: missing-fields</span>
        <span class="n">ui</span> <span class="o">=</span> <span class="p">{</span>
          <span class="n">output_popup_text</span> <span class="o">=</span> <span class="s2">"AI"</span><span class="p">,</span>
          <span class="n">input_popup_text</span> <span class="o">=</span> <span class="s2">"Prompt"</span><span class="p">,</span>
          <span class="n">width</span> <span class="o">=</span> <span class="mi">45</span><span class="p">,</span> <span class="c1">-- As percentage eg. 45%</span>
          <span class="n">output_popup_height</span> <span class="o">=</span> <span class="mi">80</span><span class="p">,</span> <span class="c1">-- As percentage eg. 80%</span>
          <span class="n">submit</span> <span class="o">=</span> <span class="s2">"&lt;Enter&gt;"</span><span class="p">,</span> <span class="c1">-- Key binding to submit the prompt</span>
        <span class="p">},</span>
        <span class="n">models</span> <span class="o">=</span> <span class="n">models</span><span class="p">,</span>
        <span class="n">register_output</span> <span class="o">=</span> <span class="p">{</span>
          <span class="p">[</span><span class="s2">"a"</span><span class="p">]</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">output</span>
          <span class="k">end</span><span class="p">,</span>
          <span class="p">[</span><span class="s2">"c"</span><span class="p">]</span> <span class="o">=</span> <span class="n">extract_code_snippets</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="n">inject</span> <span class="o">=</span> <span class="p">{</span>
          <span class="n">cutoff_width</span> <span class="o">=</span> <span class="mi">75</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="n">prompts</span> <span class="o">=</span> <span class="p">{</span>
          <span class="n">context_prompt</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
            <span class="k">return</span> <span class="s2">"Please only follow instructions or answer to questions. Be concise. "</span>
              <span class="o">..</span> <span class="p">(</span><span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_buf_get_name</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">~=</span> <span class="s2">""</span> <span class="ow">and</span> <span class="s2">"This is my currently opened file: "</span> <span class="o">..</span> <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_buf_get_name</span><span class="p">(</span>
                <span class="mi">0</span>
              <span class="p">)</span> <span class="ow">or</span> <span class="s2">""</span><span class="p">)</span>
              <span class="o">..</span> <span class="s2">"I'd like to provide some context for future "</span>
              <span class="o">..</span> <span class="s2">"messages. Here is the code/text that I want to refer "</span>
              <span class="o">..</span> <span class="s2">"to in our upcoming conversations:\n\n"</span>
              <span class="o">..</span> <span class="n">context</span>
          <span class="k">end</span><span class="p">,</span>
          <span class="n">default_prompt</span> <span class="o">=</span> <span class="k">function</span><span class="p">()</span>
            <span class="k">return</span> <span class="s2">"Please only follow instructions or answer to questions. Be concise. "</span>
              <span class="o">..</span> <span class="p">(</span>
                <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_buf_get_name</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">~=</span> <span class="s2">""</span>
                  <span class="ow">and</span> <span class="s2">"This is my currently opened file: "</span> <span class="o">..</span> <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_buf_get_name</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
                <span class="ow">or</span> <span class="s2">""</span>
              <span class="p">)</span>
          <span class="k">end</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="n">mappings</span> <span class="o">=</span> <span class="p">{</span>
          <span class="p">[</span><span class="s2">"select_up"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"&lt;C-k&gt;"</span><span class="p">,</span>
          <span class="p">[</span><span class="s2">"select_down"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"&lt;C-j&gt;"</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="n">open_ai</span> <span class="o">=</span> <span class="p">{</span>
          <span class="n">url</span> <span class="o">=</span> <span class="s2">"http://127.0.0.1:9741/v1/chat/completions"</span><span class="p">,</span>
          <span class="n">display_name</span> <span class="o">=</span> <span class="s2">"llama.cpp"</span><span class="p">,</span>
          <span class="n">api_key</span> <span class="o">=</span> <span class="p">{</span> <span class="c1">---@diagnostic disable-line: missing-fields</span>
            <span class="n">value</span> <span class="o">=</span> <span class="kc">nil</span><span class="p">,</span>
            <span class="n">get</span> <span class="o">=</span> <span class="k">function</span><span class="p">()</span>
              <span class="k">return</span> <span class="s2">""</span>
            <span class="k">end</span><span class="p">,</span>
          <span class="p">},</span>
        <span class="p">},</span>
      <span class="p">})</span>
    <span class="k">end</span><span class="p">,</span>

</code></pre></div></div>

<h2 id="secret-agents">Secret Agents</h2>

<p>Reading through the config you might see a dangerous option: <code class="language-plaintext highlighter-rouge">params = {
"--trust-all-tools" },</code>.</p>

<p>This allows <code class="language-plaintext highlighter-rouge">q</code> to execute all commands that are available to it in the current
context.</p>

<p>Yes, it’s dangerous, probably also a bit stupid. But really powerful, as well.
And a lot of fun, to be honest.</p>

<p>First I was annoyed by <em>Amazon Q</em> not providing a web API, but using the <code class="language-plaintext highlighter-rouge">cli</code>
as API makes the tool integration agentic, basically for free!</p>

<p>I give the currently open file as context in the system prompt.</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">..</span> <span class="p">(</span>
  <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_buf_get_name</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">~=</span> <span class="s2">""</span>
    <span class="ow">and</span> <span class="s2">"This is my currently opened file: "</span> <span class="o">..</span> <span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_buf_get_name</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
  <span class="ow">or</span> <span class="s2">""</span>
<span class="p">)</span>
</code></pre></div></div>

<p>With this, I can refer to the current file and let <em>q</em> interact with it.</p>

<p>Or even the entire project!</p>

<p>I was pretty amazed by pressing <code class="language-plaintext highlighter-rouge">&lt;Alt-a&gt;</code> in my editor and watching the
integration perform a full <em>Please run the tests in this project and tell me if
they were a success.</em> and things like <em>Create a feature branch hinting that I
created a new UI, commit the latest changes and push them.</em></p>

<h2 id="tldr">tl;dr</h2>

<ul>
  <li>Amazon Q is a developer centric AI by AWS</li>
  <li>Amazon Q does not provide a classic web API</li>
  <li>Amazon Q provides a <code class="language-plaintext highlighter-rouge">cli</code></li>
  <li>The <code class="language-plaintext highlighter-rouge">cli</code> can be integrated in <code class="language-plaintext highlighter-rouge">neovim</code></li>
  <li>The integration provides powerful assistance, through context-handover and
chat response or by interacting with entire projects through shell command
execution and interacting with files</li>
</ul>]]></content><author><name>Dominikus Gierlach</name></author><category term="programming" /><category term="linux" /><category term="vim" /><category term="amazon" /><category term="aws" /><category term="ai" /><category term="Q" /><summary type="html"><![CDATA[Amazon Q - Assistant in vim]]></summary></entry><entry><title type="html">Secure(ish) environment setup - direnv + gnome-keyring</title><link href="https://gierdo.astounding.technology/blog/2025/05/08/secureish-environment-setup" rel="alternate" type="text/html" title="Secure(ish) environment setup - direnv + gnome-keyring" /><published>2025-05-08T00:00:00+00:00</published><updated>2025-05-08T00:00:00+00:00</updated><id>https://gierdo.astounding.technology/blog/2025/05/08/secureish-environment-setup</id><content type="html" xml:base="https://gierdo.astounding.technology/blog/2025/05/08/secureish-environment-setup"><![CDATA[<h1 id="how-and-why-to-set-up-automated-environment-secret-stuff">How and why to set up automated environment secret stuff</h1>

<h2 id="the-problem">The Problem</h2>

<p>When working on several projects in different environments, interacting with
distributed and relatively loosely coupled <em>systems</em> and <em>platforms</em>, such as
different <em>Jira</em>, <em>GitLab</em>, <em>…</em> instances and other <em>API</em> providers, e.g.
<em>OpenAI</em> or an <em>Artifactory</em> or whatever.</p>

<p>I want to be able to <em>consume</em> these systems from my development environment,
which is basically my shell with dedicated <em>command line interfaces</em> and
<em>integrations</em> in <code class="language-plaintext highlighter-rouge">vim</code>.</p>

<p>How can I now configure all the endpoints and other <em>environmental aspects</em>,
including <em>API tokens</em> and other <em>secrets</em>, independently for different project
contexts, without having to copy-paste secrets or hard-code secrets somewhere,
e.g. putting clear-text tokens into <code class="language-plaintext highlighter-rouge">.env</code> files?</p>

<p>The solution should handle</p>

<ul>
  <li>Automation and convenience. Whenever I enter a specific directory that
specifies a context, e.g. a project or group of projects, I want the
environment to automagically be set up correctly. When I leave the
environment or change context, I want the environment to be teared down
again.</li>
  <li>Centralization. <em>Don’t repeat yourself</em> (DRY) is not just a good practice for
programming. I don’t want to have 27 places to touch when I changed my GitHub
API token.</li>
  <li>Security. <em>No</em> clear-text secrets stored somewhere, ever.</li>
</ul>

<h2 id="possible-solution">Possible Solution</h2>

<p>In pretty much every Linux environment that is not KDE, you should have the
<code class="language-plaintext highlighter-rouge">gnome-keyring</code> available. The <code class="language-plaintext highlighter-rouge">gnome-keyring</code> is basically a local database
that is <em>encrypted</em> with the user’s login password (-&gt; automatically decrypted
on login through pam), which provides a convenient centralised interface for
secrets/keys. <code class="language-plaintext highlighter-rouge">gnome-keyring</code> supports gpg and ssh keys, which it can unlock
and act as a key agent for, and generic passwords.</p>

<p>All passwords can then be retrieved by libsecret and the according tooling.
There is a built-in git integration (built into git, but you still have to
configure it!), and many applications use it already, e.g. the network manager,
Chrome, Chromium, Edge, …</p>

<p>This opens a solution path, if it would be possible to automatically <em>retrieve</em>
secrets from the <code class="language-plaintext highlighter-rouge">gnome-keyring</code> and create <em>environment variables</em> etc.</p>

<h2 id="solution">Solution</h2>

<p>There is a <em>cli</em>, <code class="language-plaintext highlighter-rouge">secret-tool</code>, which allows you to store and retrieve generic
secrets from/into the <code class="language-plaintext highlighter-rouge">gnome-keyring</code>. In combination with your shell setup
through <code class="language-plaintext highlighter-rouge">direnv</code>, this allows for pretty nice and still secure automation,
fulfilling all the goals.</p>

<h3 id="setup">Setup</h3>

<h4 id="gnome-keyring">gnome-keyring</h4>

<p>Well. You need the <code class="language-plaintext highlighter-rouge">gnome-keyring</code> to be set up, ideally unlocked automatically
on login. If you’re on gnome, that should come as a given. If you are using a
different environment, it might be a given.</p>

<h4 id="secret-tool">secret-tool</h4>

<p>Install the <code class="language-plaintext highlighter-rouge">secret-tool</code>. On <em>Debian</em> based systems, which includes all the
<em>Ubuntu</em> flavours, that’s done with</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>libsecret-tools
</code></pre></div></div>

<h4 id="direnv">direnv</h4>

<p>Install <code class="language-plaintext highlighter-rouge">direnv</code>, e.g., if you are on <em>Debian</em> based systems, with</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>direnv
</code></pre></div></div>

<p>And make sure to set it up and integrate it into your shell.
I am using zsh, so you will find the configuration in the <a href="https://github.com/gierdo/dotfiles/blob/c8877582a3f8fce10e5c78f4e9dc70b8e6a6e9fc/.zshrc#L148"><code class="language-plaintext highlighter-rouge">.zshrc</code></a></p>

<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
<span class="k">if </span><span class="nb">command</span> <span class="nt">-v</span> direnv 1&gt;/dev/null 2&gt;&amp;1<span class="p">;</span> <span class="k">then
  </span>_evalcache direnv hook zsh
<span class="k">fi</span>
...
</code></pre></div></div>

<h3 id="how-does-it-work">How does it work?</h3>

<p>For example, I want to have a gitlab API token in all my repositories for
<a href="https://private-gitlab.com">https://private-gitlab.com</a>, so that I can quickly create new projects or open
/ interact with merge requests with the <em>gitlab cli</em>, <code class="language-plaintext highlighter-rouge">glab</code>. So, I have a file
<code class="language-plaintext highlighter-rouge">~/workspace/private-gitlab.com/.envrc</code> with the following content:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">GITLAB_TOKEN</span><span class="o">=</span><span class="si">$(</span>secret-tool lookup protocol https server private-gitlab.com user &lt;bob@secret.com&gt;<span class="si">)</span>
<span class="nb">export </span><span class="nv">GITLAB_URI</span><span class="o">=</span><span class="s2">"https://private-gitlab.com"</span>
<span class="nb">export </span><span class="nv">GITHUB_TOKEN</span><span class="o">=</span><span class="si">$(</span>secret-tool lookup protocol https server github.com user &lt;dominik.gierlach@gmail.com&gt;<span class="si">)</span>
</code></pre></div></div>

<p>Together with <code class="language-plaintext highlighter-rouge">direnv</code> set up and integrated in to the <code class="language-plaintext highlighter-rouge">shell</code>, as can be seen
im my <a href="https://github.com/gierdo/dotfiles">dotfiles.</a>, this retrieves a token
from the keyring whenever I enter a repository in
<code class="language-plaintext highlighter-rouge">~/workspace/private-gitlab.com/...</code> and sets it up as local environment
variable in this context.</p>

<h3 id="add--change-secrets">Add / change secrets</h3>

<p>The secrets themselves can be set up like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>secret-tool store <span class="nt">--label</span><span class="o">=</span><span class="s1">'Git: &lt;https://github.com/&gt;'</span> schema org.gnome.keyring.NetworkPassword protocol https user &lt;dominik.gierlach@gmail.com&gt; server github.com
</code></pre></div></div>

<p>Or by using <code class="language-plaintext highlighter-rouge">seahorse</code>, a graphical manager for the <code class="language-plaintext highlighter-rouge">gnome-keyring</code>.</p>

<h3 id="nested-environments">Nested environments</h3>

<p>With <code class="language-plaintext highlighter-rouge">direnv</code>, it’s possible to nest environments in order to extend them.</p>

<p>Assume a directory structure like this</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~/workspace/github.com/..
  - .envrc
+ - project_group_1
  + - project_a
  + - project_b
+ - project_group_2
    - .envrc
  + - project_c
  + - project_d
</code></pre></div></div>

<p>I want to be able to use the environment configuration that is part of
<code class="language-plaintext highlighter-rouge">~/workspace/github.com/.envrc</code> in every project, but I want to change/extend it
for all projects in <code class="language-plaintext highlighter-rouge">~/workspace/github.com/project_group_2</code>.</p>

<p>That is possible by referring to the overarching <code class="language-plaintext highlighter-rouge">.envrc</code> from
<code class="language-plaintext highlighter-rouge">~/workspace/github.com/project_group_1/.envrc</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source</span> ~/workspace/github.com/.envrc
<span class="nb">export </span><span class="nv">AWS_PROFILE</span><span class="o">=</span>PROJECT_GROUP_2_AWS
</code></pre></div></div>

<h2 id="bonus-setup-git-credential-integration-with-gnome-keyring">Bonus: Setup git credential integration with <code class="language-plaintext highlighter-rouge">gnome-keyring</code></h2>

<p>As written above, <code class="language-plaintext highlighter-rouge">gnome-keyring</code> can also store <code class="language-plaintext highlighter-rouge">git</code> credentials, when using
<em>https</em> authentication. While this is somewhat orthogonal to setting up shell
environments with secrets, it’s connected and important.</p>

<p><code class="language-plaintext highlighter-rouge">git</code> can use <a href="https://git-scm.com/docs/gitcredentials"><em>credential helpers</em></a>
to store and automagically use credentials for different repositories. The
trivial and built-in <em>credential helper</em> is <code class="language-plaintext highlighter-rouge">store</code>, which simply stores the
credentials on disk in <em>clear text</em>, so let’s not go down that path.</p>

<p>The <em>credential helper</em> to integrate with <code class="language-plaintext highlighter-rouge">gnome-keyring</code> on linux is
<code class="language-plaintext highlighter-rouge">git-credential-libsecret</code>.</p>

<h3 id="install-git-credential-libsecret">Install <code class="language-plaintext highlighter-rouge">git-credential-libsecret</code></h3>

<p>On Debian based systems, including Ubuntu, the credential helper is not
available as compiled binary, but the sourcecode is part of the <code class="language-plaintext highlighter-rouge">git</code> package.</p>

<p>Install it e.g. like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>libsecret-1-dev
<span class="nb">mkdir</span> <span class="nt">-p</span> ~/workspace/other/libsecret
<span class="nb">cd</span> ~/workspace/other/libsecret
<span class="nb">cp</span> <span class="nt">-r</span> /usr/share/doc/git/contrib/credential/libsecret/<span class="k">*</span> ./
make
<span class="nb">mkdir</span> <span class="nt">-p</span> ~/.local/bin
<span class="nb">cp </span>git-credential-libsecret ~/.local/bin
</code></pre></div></div>

<h3 id="set-up-git-credential-libsecret">Set up <code class="language-plaintext highlighter-rouge">git-credential-libsecret</code></h3>

<p>After it has been installed, let’s make git use it.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config <span class="nt">--global</span> credential.helper libsecret
</code></pre></div></div>

<h3 id="automatic-setup-with-dotfiles-and-tuning">Automatic setup with dotfiles and tuning</h3>

<p>Yes, that was annoyingly complex, especially for a thing that should not be
forgotten whenever a new machine is set up.</p>

<p>That’s why I integrated the setup into my
<a href="https://github.com/gierdo/dotfiles">dotfiles.</a>
I don’t want to have to remember (= look up and re-understand) how to set it up
whenever I set up a new machine, and I don’t want to forget to set it up at
all.</p>

<p>The git configuration is part of the <a href="https://github.com/gierdo/dotfiles/blob/c8877582a3f8fce10e5c78f4e9dc70b8e6a6e9fc/.gitconfig#L31">global
<code class="language-plaintext highlighter-rouge">.gitconfig</code></a>,
installation is automated through
<a href="https://github.com/gierdo/dotfiles/blob/c8877582a3f8fce10e5c78f4e9dc70b8e6a6e9fc/tuning/programs.toml#L175">tuning</a>.</p>

<h2 id="tldr">tl;dr</h2>

<ul>
  <li>Sometimes secrets are needed in (shell based) environments, to be set up as
environment variables</li>
  <li>Tokens can be stored centrally and encrypted in the <code class="language-plaintext highlighter-rouge">gnome-keyring</code></li>
  <li>Tokens can be retrieved and exported as environment variables with
<code class="language-plaintext highlighter-rouge">secret-tool</code></li>
  <li>Environment specific automation thereof can be done with <code class="language-plaintext highlighter-rouge">direnv</code></li>
</ul>]]></content><author><name>Dominikus Gierlach</name></author><category term="programming" /><category term="linux" /><category term="security" /><category term="direnv" /><category term="environment" /><summary type="html"><![CDATA[How and why to set up automated environment secret stuff]]></summary></entry><entry><title type="html">neovim config - native lsp support instead of coc</title><link href="https://gierdo.astounding.technology/blog/2024/09/19/nvim-lsp" rel="alternate" type="text/html" title="neovim config - native lsp support instead of coc" /><published>2024-09-19T00:00:00+00:00</published><updated>2024-09-19T00:00:00+00:00</updated><id>https://gierdo.astounding.technology/blog/2024/09/19/nvim-lsp</id><content type="html" xml:base="https://gierdo.astounding.technology/blog/2024/09/19/nvim-lsp"><![CDATA[<h1 id="migrating-language-server-linting-and-formatting-away-from-coc">Migrating language server, linting and formatting away from <a href="https://github.com/neoclide/coc.nvim">coc</a></h1>

<h2 id="context">Context</h2>

<p>I am a big <a href="https://neovim.io">vim (neovim)</a> fan and do basically all my
programming and writing in it, the exception being work things that need MS
Office. Luckily, most of those things are possible with the browser-based
version of office, the exception being macros and obscure things like
Powerpoint footer changes. Anyways, that is a completely different story.</p>

<p>Naked, vanilla vim is nice and present in basically every environment. So, it’s
good to be able to use it, especially for remote
configuration/documentation/troubleshooting work. The real power is unleashed
with more advanced capabilities and extensions, which extend the barebone
editor with modern creature comforts, such as <em>linting</em>, <em>automatic fixing</em>,
<em>code completion</em>, <em>auto formatting</em>, <em>syntax highlighting</em>, <em>test execution</em>,
<em>debugging</em> and <em>intelligent navigation</em>.</p>

<p>My <em>code completion</em> and <em>intelligent navigation</em> setup started with
<a href="https://github.com/ycm-core/YouCompleteMe">YouCompleteMe</a> in 2017. I needed it
mainly for <code class="language-plaintext highlighter-rouge">C</code>, and it did a good job.</p>

<p>A bit later, Microsoft’s <em>Language Server Protocol</em> gained much more traction,
with more and more really usable <a href="https://microsoft.github.io/language-server-protocol/implementors/servers/"><em>language
servers</em></a>
being really usable and taking off, mainly n the context of <em>visual studio
code</em>. Most of the Language servers where installed and integrated into
<em>vscode</em> through dedicated <em>plugins</em>.</p>

<p>In comes the neovim plugin <a href="https://github.com/neoclide/coc.nvim">coc (conquer of
completion)</a>. This plugin acts as a
<em>language server client</em>, meaning that it can use any <em>language server</em> that
implements the lsp standard, and it can integrate slightly adapted <em>vscode
pluins</em> as <em>coc plugins</em>. This meant that the managed installation of language
servers and the integration thereof became really simple and straightforward,
with a very active ecosystem and community around it. I migrated to <em>coc</em> in
2019, adding support and better integration for many more languages into my
setup.</p>

<p><em>coc</em> with a set of plugins and a handful of “manually” integrated language
servers as well as with a set of linters and formatters, integrated with
<a href="https://github.com/iamcco/diagnostic-languageserver">diagnostic-languageserver</a>,
was my default setup for 5 years. I changed the supported languages all the
time, based on what I needed, but besides that and <a href="/blog/2023/12/01/nvim-config-lua">rewriting the config to
lua</a>, my setup stayed like
this for 5 years.</p>

<p>In the meantime, <em>neovim</em> gained native <em>language server client</em> support,
removing the need for a dedicated plugin for lsp support. However, <em>coc</em> still
provided the easy integration and awesome out of the box functionality.</p>

<p>The awesome and very capable community embraced the feature, though. There is
<a href="https://github.com/williamboman/mason.nvim">mason</a>, an nvim native package/lsp
manager, and many integration layer plugins between neovim and mason, making
the integration of language servers slimmer (no extra layer), extremely
flexible and manageable, with concise and powerful configuration.</p>

<p>Powered by all of that, projects like <a href="https://www.lazyvim.org/">lazyvim</a>
started, providing much more out of the box functionality and ease of use than
coc had. Lazyvim is basically a full-featured one size fits all
preconfiguration of nvim, with very sane defaults and a smart plugin and
configuration system that makes it feature rich without bloat. I know a few
people who recently went down the rabbithole of vim, and they started with
lazyvim right away. I respect it a lot, even though I want to have a bit more
control over my setup, so not for me. The ecosystem grew and grew and is now
more active and powerful than coc’s ecosystem.</p>

<p>I was very interested in the principle of the powerful and manageable
modularization, but still, the normal out of the box experience was a lot worse
than with coc, as it was basically not a box, but a collection of elements you
had to build your box with yourself. The capabilities of coc are split into
separate and independent components for completion (with lsp as source amongst
others), dedicated components for formatters, linters, dedicated configuration
of all components and system integration.</p>

<p>Interesting, but it’s a lot. Especially because my current setup just worked
and did mostly what I wanted it to do, and I had no real functioning reason to
migrate besides the itch. So, I put a migration on the back of my to do list,
to do some other time.</p>

<h2 id="some-other-time-is-now">Some other time is <em>now</em></h2>

<p>That is until for the first time ever I had to do things in <code class="language-plaintext highlighter-rouge">C#</code>. There is a
coc plugin for <code class="language-plaintext highlighter-rouge">C#</code>,
<a href="https://github.com/coc-extensions/coc-omnisharp">coc-omnisharp</a>, which in the
README greets you with the message:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>⛔ This project is lacking proper maintanence. I would recommend csharp-ls at this moment.
</code></pre></div></div>

<p>This leaves manual integration of the
<a href="https://github.com/razzmatazz/csharp-language-server">csharp-language-server</a>.
Awesome, I did it right away.</p>

<p>But there was a problem.</p>

<p>With the coc integration, I was unable to find a way to replace the default
sync handler for system libraries only available as binaries, used e.g. for
navigation, such as <em>go_to_definition</em>, with a buffer showing the decompiled
sourcecode. This is explicitly (and easily) possible with the flexible
configuration of native language servers in nvim, as
<a href="https://github.com/Decodetalkers/csharpls-extended-lsp.nvim">documented</a>.</p>

<p>So, last weekend, I did it.</p>

<p>I migrated my configuration.</p>

<h3 id="structure">Structure</h3>

<p>I use <a href="https://github.com/folke/lazy.nvim">lazy</a> as plugin manager, which makes
it really simple to split up plugin configuration. I decided to split my
language server config into a dedicated file for the abstract definition of the
behaviour of lsps, e.g. with key mappings, sources of the completion system and
so on, a dedicated file for the specific definition of supported languages, and
a dedicated file for the separate configuration of linters and fixers, while
also revamping the test execution and debugging setup.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/home/gierdo/.dotfiles/.config/nvim/lua/plugins

❯ tree
.
├── debugging.lua
├── filetree.lua
├── fuzzyness.lua
├── git.lua
├── init.lua
├── linters.lua
├── lsp-behaviour.lua
├── lsp-languages.lua
├── neoai.lua
├── testing.lua
├── theme.lua
└── treesitter.lua
</code></pre></div></div>

<p>Copy-pasting the few hundred lines of lua here probably makes this post even
less readable, but I’ll point out some things here that are not just obvious
configuration.</p>

<p>If you want to see the full configuration, check out my
<a href="https://github.com/gierdo/dotfiles/tree/master/.config/nvim/lua/plugins">dotfiles.</a></p>

<h4 id="the-original-motivation-c-decompiler-integration">The original motivation: C# decompiler integration</h4>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="kd">local</span> <span class="n">cs_config</span> <span class="o">=</span> <span class="p">{</span>
        <span class="n">capabilities</span> <span class="o">=</span> <span class="n">lsp_capabilities</span><span class="p">,</span>
        <span class="n">handlers</span> <span class="o">=</span> <span class="p">{</span>
          <span class="p">[</span><span class="s2">"textDocument/definition"</span><span class="p">]</span> <span class="o">=</span> <span class="nb">require</span><span class="p">(</span><span class="s2">"csharpls_extended"</span><span class="p">).</span><span class="n">handler</span><span class="p">,</span>
          <span class="p">[</span><span class="s2">"textDocument/typeDefinition"</span><span class="p">]</span> <span class="o">=</span> <span class="nb">require</span><span class="p">(</span><span class="s2">"csharpls_extended"</span><span class="p">).</span><span class="n">handler</span><span class="p">,</span>
        <span class="p">},</span>
      <span class="p">}</span>
      <span class="n">lspconfig</span><span class="p">.</span><span class="n">csharp_ls</span><span class="p">.</span><span class="n">setup</span><span class="p">(</span><span class="n">cs_config</span><span class="p">)</span>
</code></pre></div></div>

<h4 id="fuzzy-and-fancy-selection-for-multiple-navigation-target-hits">Fuzzy and fancy selection for multiple navigation target hits</h4>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code>          <span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span>
            <span class="s2">"n"</span><span class="p">,</span>
            <span class="s2">"gd"</span><span class="p">,</span>
            <span class="nb">require</span><span class="p">(</span><span class="s1">'telescope.builtin'</span><span class="p">).</span><span class="n">lsp_definitions</span><span class="p">,</span>
            <span class="p">{</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">event</span><span class="p">.</span><span class="n">buf</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Go to definition."</span> <span class="p">}</span>
          <span class="p">)</span>
          <span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span>
            <span class="s2">"n"</span><span class="p">,</span>
            <span class="s2">"gi"</span><span class="p">,</span>
            <span class="nb">require</span><span class="p">(</span><span class="s1">'telescope.builtin'</span><span class="p">).</span><span class="n">lsp_implementations</span><span class="p">,</span>
            <span class="p">{</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">event</span><span class="p">.</span><span class="n">buf</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Go to implementation."</span> <span class="p">}</span>
          <span class="p">)</span>
          <span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span>
            <span class="s2">"n"</span><span class="p">,</span>
            <span class="s2">"go"</span><span class="p">,</span>
            <span class="nb">require</span><span class="p">(</span><span class="s1">'telescope.builtin'</span><span class="p">).</span><span class="n">lsp_type_definitions</span><span class="p">,</span>
            <span class="p">{</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">event</span><span class="p">.</span><span class="n">buf</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Go to type definition."</span> <span class="p">}</span>
          <span class="p">)</span>
          <span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span>
            <span class="s2">"n"</span><span class="p">,</span>
            <span class="s2">"gr"</span><span class="p">,</span>
            <span class="nb">require</span><span class="p">(</span><span class="s1">'telescope.builtin'</span><span class="p">).</span><span class="n">lsp_references</span><span class="p">,</span>
            <span class="p">{</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">event</span><span class="p">.</span><span class="n">buf</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Show references."</span> <span class="p">}</span>
          <span class="p">)</span>
</code></pre></div></div>

<h4 id="extended-json--and-yaml-schema-support">Extended json- and yaml schema support</h4>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="n">lspconfig</span><span class="p">.</span><span class="n">jsonls</span><span class="p">.</span><span class="n">setup</span><span class="p">({</span>
        <span class="n">capabilities</span> <span class="o">=</span> <span class="n">lsp_capabilities</span><span class="p">,</span>
        <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
          <span class="n">json</span> <span class="o">=</span> <span class="p">{</span>
            <span class="n">schemas</span> <span class="o">=</span> <span class="nb">require</span><span class="p">(</span><span class="s2">"schemastore"</span><span class="p">).</span><span class="n">json</span><span class="p">.</span><span class="n">schemas</span><span class="p">(),</span>
            <span class="n">validate</span> <span class="o">=</span> <span class="p">{</span> <span class="n">enable</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">},</span>
            <span class="n">schemaDownload</span> <span class="o">=</span> <span class="p">{</span> <span class="n">enable</span> <span class="o">=</span> <span class="kc">false</span> <span class="p">},</span>
          <span class="p">},</span>
        <span class="p">},</span>
      <span class="p">})</span>

      <span class="n">lspconfig</span><span class="p">.</span><span class="n">yamlls</span><span class="p">.</span><span class="n">setup</span><span class="p">({</span>
        <span class="n">capabilities</span> <span class="o">=</span> <span class="n">lsp_capabilities</span><span class="p">,</span>
        <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
          <span class="n">yaml</span> <span class="o">=</span> <span class="p">{</span>
            <span class="n">schemaStore</span> <span class="o">=</span> <span class="p">{</span>
              <span class="n">enable</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
              <span class="n">url</span> <span class="o">=</span> <span class="s2">""</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="n">schemas</span> <span class="o">=</span> <span class="nb">require</span><span class="p">(</span><span class="s2">"schemastore"</span><span class="p">).</span><span class="n">yaml</span><span class="p">.</span><span class="n">schemas</span><span class="p">({</span>
              <span class="n">extra</span> <span class="o">=</span> <span class="p">{</span>
                <span class="p">{</span>
                  <span class="n">name</span> <span class="o">=</span> <span class="s2">"Cloudformation"</span><span class="p">,</span>
                  <span class="n">description</span> <span class="o">=</span> <span class="s2">"Cloudformation Template"</span><span class="p">,</span>
                  <span class="n">fileMatch</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"*.template.y*ml"</span><span class="p">,</span> <span class="s2">"*-template.y*ml"</span> <span class="p">},</span>
                  <span class="n">url</span> <span class="o">=</span> <span class="s2">"https://raw.githubusercontent.com/awslabs/goformation/master/schema/cloudformation.schema.json"</span><span class="p">,</span>
                <span class="p">},</span>
              <span class="p">},</span>
            <span class="p">}),</span>
            <span class="n">customTags</span> <span class="o">=</span> <span class="p">{</span>
              <span class="c1">-- Cloudformation tags</span>
              <span class="s2">"!And scalar"</span><span class="p">,</span>
              <span class="s2">"!If scalar"</span><span class="p">,</span>
              <span class="s2">"!Not"</span><span class="p">,</span>
              <span class="s2">"!Equals scalar"</span><span class="p">,</span>
              <span class="s2">"!Or scalar"</span><span class="p">,</span>
              <span class="s2">"!FindInMap scalar"</span><span class="p">,</span>
              <span class="s2">"!Base64"</span><span class="p">,</span>
              <span class="s2">"!Cidr"</span><span class="p">,</span>
              <span class="s2">"!Ref"</span><span class="p">,</span>
              <span class="s2">"!Sub"</span><span class="p">,</span>
              <span class="s2">"!GetAtt sequence"</span><span class="p">,</span>
              <span class="s2">"!GetAZs"</span><span class="p">,</span>
              <span class="s2">"!ImportValue sequence"</span><span class="p">,</span>
              <span class="s2">"!Select sequence"</span><span class="p">,</span>
              <span class="s2">"!Split sequence"</span><span class="p">,</span>
              <span class="s2">"!Join sequence"</span><span class="p">,</span>
            <span class="p">},</span>
          <span class="p">},</span>
        <span class="p">},</span>
      <span class="p">})</span>
</code></pre></div></div>

<h4 id="imho-smart-test-execution-and-debugging-keymaps">imho smart test execution and debugging keymaps</h4>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="kd">local</span> <span class="n">wk</span> <span class="o">=</span> <span class="nb">require</span><span class="p">(</span><span class="s2">"which-key"</span><span class="p">)</span>

      <span class="n">wk</span><span class="p">.</span><span class="n">add</span><span class="p">({</span>
        <span class="p">{</span> <span class="s2">"&lt;leader&gt;t"</span><span class="p">,</span> <span class="n">group</span> <span class="o">=</span> <span class="s2">"   Test"</span> <span class="p">},</span>
        <span class="p">{</span> <span class="s2">"&lt;leader&gt;tr"</span><span class="p">,</span> <span class="n">neotest</span><span class="p">.</span><span class="n">run</span><span class="p">.</span><span class="n">run</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Run nearest test."</span> <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;tf"</span><span class="p">,</span>
          <span class="k">function</span><span class="p">()</span>
            <span class="n">neotest</span><span class="p">.</span><span class="n">run</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">vim</span><span class="p">.</span><span class="n">fn</span><span class="p">.</span><span class="n">expand</span><span class="p">(</span><span class="s2">"%"</span><span class="p">))</span>
          <span class="k">end</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Run current file."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;td"</span><span class="p">,</span>
          <span class="k">function</span><span class="p">()</span>
            <span class="n">neotest</span><span class="p">.</span><span class="n">run</span><span class="p">.</span><span class="n">run</span><span class="p">({</span> <span class="n">strategy</span> <span class="o">=</span> <span class="s2">"dap"</span> <span class="p">})</span>
          <span class="k">end</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Debug nearest test."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;ts"</span><span class="p">,</span>
          <span class="n">neotest</span><span class="p">.</span><span class="n">summary</span><span class="p">.</span><span class="n">toggle</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Show/hide summary."</span><span class="p">,</span>
        <span class="p">},</span>
      <span class="p">})</span>

      <span class="n">wk</span><span class="p">.</span><span class="n">add</span><span class="p">({</span>
        <span class="p">{</span> <span class="s2">"&lt;leader&gt;d"</span><span class="p">,</span> <span class="n">group</span> <span class="o">=</span> <span class="s2">"   Debug"</span> <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dd"</span><span class="p">,</span>
          <span class="n">telescope</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">dap</span><span class="p">.</span><span class="n">commands</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Show debug commands."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dt"</span><span class="p">,</span>
          <span class="n">dap</span><span class="p">.</span><span class="n">toggle_breakpoint</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Toggle line breakpoint on the current line."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dbs"</span><span class="p">,</span>
          <span class="n">dap</span><span class="p">.</span><span class="n">set_breakpoint</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Set breakpoint."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dbl"</span><span class="p">,</span>
          <span class="n">telescope</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">dap</span><span class="p">.</span><span class="n">list_breakpoints</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"List breakpoints."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dv"</span><span class="p">,</span>
          <span class="n">telescope</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">dap</span><span class="p">.</span><span class="n">variables</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Show variables."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dbc"</span><span class="p">,</span>
          <span class="n">dap</span><span class="p">.</span><span class="n">clear_breakpoints</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Clear breakpoints."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dbe"</span><span class="p">,</span>
          <span class="n">dap</span><span class="p">.</span><span class="n">set_exception_breakpoints</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Set Exception breakpoints."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dc"</span><span class="p">,</span>
          <span class="n">dap</span><span class="p">.</span><span class="n">continue</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"When debugging, continue. Otherwise start debugging."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dfd"</span><span class="p">,</span>
          <span class="n">dap</span><span class="p">.</span><span class="n">down</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Move down a frame in the current call stack."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dfu"</span><span class="p">,</span>
          <span class="n">dap</span><span class="p">.</span><span class="n">up</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Move up a frame in the current call stack."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span> <span class="s2">"&lt;leader&gt;dp"</span><span class="p">,</span> <span class="n">dap</span><span class="p">.</span><span class="n">pause</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Pause debuggee."</span> <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dr"</span><span class="p">,</span>
          <span class="n">dap</span><span class="p">.</span><span class="n">run_to_cursor</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Continues execution to the current cursor."</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="p">{</span>
          <span class="s2">"&lt;leader&gt;dre"</span><span class="p">,</span>
          <span class="n">dap</span><span class="p">.</span><span class="n">restart</span><span class="p">,</span>
          <span class="n">desc</span> <span class="o">=</span> <span class="s2">"Restart debugging with the same configuration."</span><span class="p">,</span>
        <span class="p">},</span>
      <span class="p">})</span>
</code></pre></div></div>

<h2 id="tldr">tl;dr</h2>

<ul>
  <li>language servers and such + vim are great</li>
  <li>there are many ways to integrate vim with language servers</li>
  <li><a href="https://github.com/neoclide/coc.nvim">coc</a> is not that slim and flexible
with good out of the box experience</li>
  <li>native nvim lsp support is slim and flexible with worse out of the box
experience</li>
  <li>with existing plugins and thanks to the community, benefiting from the
advantages of native nvim lsp support is possible in a sane way</li>
</ul>]]></content><author><name>Dominikus Gierlach</name></author><category term="programming" /><category term="linux" /><category term="vim" /><summary type="html"><![CDATA[Migrating language server, linting and formatting away from coc]]></summary></entry><entry><title type="html">atuin server on a Raspberry PI - Cross compiling Rust</title><link href="https://gierdo.astounding.technology/blog/2024/07/07/atuin-pi" rel="alternate" type="text/html" title="atuin server on a Raspberry PI - Cross compiling Rust" /><published>2024-07-07T00:00:00+00:00</published><updated>2024-07-07T00:00:00+00:00</updated><id>https://gierdo.astounding.technology/blog/2024/07/07/atuin-pi</id><content type="html" xml:base="https://gierdo.astounding.technology/blog/2024/07/07/atuin-pi"><![CDATA[<h1 id="atuin-server-on-a-raspberry-pi---cross-compiling-rust">atuin server on a Raspberry PI - Cross compiling Rust</h1>

<p>I’m a big fan of <a href="https://docs.atuin.sh/">atuin</a>, a shell history manager,
which replaces the flat command history file with a local database, fully
searcheable and with builtin synchronization support.</p>

<p>My setup of atuin,
<a href="https://github.com/gierdo/dotfiles/blob/master/tuning/programs.toml#L162">installation</a>
and
<a href="https://github.com/gierdo/dotfiles/blob/master/.config/atuin/config.toml">configuration</a>,
can be seen in my <a href="https://github.com/gierdo/dotfiles">dotfiles</a>. But the
<a href="https://docs.atuin.sh/guide/installation/">documentation</a> is excellent.</p>

<p>In case you are wondering why the sync configuration is not in my config: I try
to keep all specific configuration of stuff that is not publicly available and
might not be needed in all my environments outside of the dotfiles. I set
<code class="language-plaintext highlighter-rouge">$ATUIN_SYNC_ADDRESS</code> in my <code class="language-plaintext highlighter-rouge">~/.profile.local</code>.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export ATUIN_SYNC_ADDRESS=http://raspberrypi.local:8888
</code></pre></div></div>

<p>Atuin offers a free synchronization server at <a href="https://api.atuin.sh">https://api.atuin.sh</a>. Yes, it’s
not a good idea to show and open up your command history to everybody. Which is
why the synchronization is completely end-to-end encrypted by atuin. The
synchronization server only sees gibberish, and all your clients have to be set
up with a passkey to enable sync.</p>

<p>If you, like me, still don’t want to trust that data to a sync service, you can
also run your own <a href="https://docs.atuin.sh/self-hosting/server-setup/">synchronization
server</a>, which is pretty
straightforward, assuming you know a bit about services and postgresql.</p>

<p>Of course that means that you have to run a server somewhere…</p>

<p>I happen to have a rasperry pi 3 in my home network, which mainly acts as an
always-on member of <a href="https://syncthing.net/">syncthing</a>-synchronized
directories, and as a git repository server for private repositories with
<a href="https://gitolite.com/gitolite/index.html">gitolite</a>. I don’t expose the Pi at
all to the public internet, it is only available internally, resolveable
through <code class="language-plaintext highlighter-rouge">mDNS</code>. I wanted to set it up as atuin synchronization server. It’s
already there, it is available for all devices in my local home network. This,
of course, means that I can only synchronize shell history with my machines at
home. But I don’t have a problem with this limitation at all.</p>

<h2 id="the-problem">The problem</h2>

<p>Atuin is not installable through the raspbian package sources. This is also
true for Debian at the time of writing. On my machine, I install it with cargo,
orchestrated with <code class="language-plaintext highlighter-rouge">tuning</code>.</p>

<p>So, how do you install atuin on my raspberry pi, which uses an ARM CPU?
Especially if you don’t want to compile it on the pi itself, wait for ages and
fill your pi with stuff you don’t really need?</p>

<h2 id="the-solution">The solution</h2>

<p><a href="https://github.com/atuinsh/atuin">Atuin</a> is a program written in Rust. This
means that</p>

<ol>
  <li>Programs are compiled to architecture specific binaries</li>
  <li>Programs can be built pretty easily with Rust’s awesome package manager
<a href="https://doc.rust-lang.org/cargo/"><code class="language-plaintext highlighter-rouge">cargo</code></a></li>
  <li><a href="https://rust-lang.github.io/rustup/cross-compilation.html">Cross
compilation</a> is
pretty straightforward, thanks to Rust.</li>
</ol>

<p>As usual, the devil is in the details, however.</p>

<h2 id="the-details">The details</h2>

<h3 id="setting-up-rust-for-cross-compilation">Setting up Rust for cross-compilation</h3>

<p>With <code class="language-plaintext highlighter-rouge">rustup</code> as rust toolchain manager, it’s easy to install the rust specific
target and toolchain for the raspberry pi.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rustup target add armv7-unknown-linux-gnueabihf
</code></pre></div></div>

<p>This is not enough, however. For successful cross compilation, rust also needs
the target specific gcc toolchain to link against C libraries and sysroot.</p>

<h3 id="setting-up-the-system">Setting up the system</h3>

<p>You need gcc for cross-compilation with the <code class="language-plaintext highlighter-rouge">armhf</code> target, as well as the
<code class="language-plaintext highlighter-rouge">armhf</code> specific libraries for dynamic linking.</p>

<p>On a Debian system, this can be installed like so.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo apt-get install gcc-arm-linux-gnueabihf libgcc-13-dev-armhf-cross
</code></pre></div></div>

<p>The installation of gcc and the sysroot is on a system level, independent from
the local rust setup. This means that cargo has to be set up correctly so that
it uses the correct linker.</p>

<h3 id="configuring-cargo">Configuring cargo</h3>

<p>Target specific attributes can be configured in <code class="language-plaintext highlighter-rouge">~/.cargo/config.toml</code></p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[target.armv7-unknown-linux-gnueabihf]</span>
<span class="py">linker</span> <span class="p">=</span> <span class="s">"arm-linux-gnueabihf-gcc"</span>
</code></pre></div></div>

<h2 id="compilation">Compilation</h2>

<p>After everything has been set up, cargo can be built in the local directory with this command:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cargo install --target=armv7-unknown-linux-gnueabihf --target-dir=$(pwd) --root=$(pwd) atuin
</code></pre></div></div>

<p>This will download the sources of <code class="language-plaintext highlighter-rouge">atuin</code> and all dependencies, cross-compile
it and copy it to a <code class="language-plaintext highlighter-rouge">bin</code> subdirectory.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ readelf -h bin/atuin
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Position-Independent Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0xd363d
  Start of program headers:          52 (bytes into file)
  Start of section headers:          32358908 (bytes into file)
  Flags:                             0x5000400, Version5 EABI, hard-float ABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         10
  Size of section headers:           40 (bytes)
  Number of section headers:         44
  Section header string table index: 43
</code></pre></div></div>

<h2 id="installation">Installation</h2>

<p>Copy the binary onto your raspberry pi, e.g. to <code class="language-plaintext highlighter-rouge">/usr/local/bin/atuin</code>, and
you’re good to go!</p>

<h2 id="tldr">tl;dr</h2>

<ul>
  <li>atuin is great to manage shell history</li>
  <li>atuin sync is great and pretty safe</li>
  <li>atuin sync on a raspberry pi under your control is even safer</li>
  <li>cross-compilation with Rust is relatively easy</li>
  <li>the devil is in the details</li>
</ul>]]></content><author><name>Dominikus Gierlach</name></author><category term="programming" /><category term="linux" /><category term="rust" /><category term="build" /><category term="atuin" /><summary type="html"><![CDATA[atuin server on a Raspberry PI - Cross compiling Rust]]></summary></entry><entry><title type="html">Local AI assistant - llama3</title><link href="https://gierdo.astounding.technology/blog/2024/05/20/llama3" rel="alternate" type="text/html" title="Local AI assistant - llama3" /><published>2024-05-20T00:00:00+00:00</published><updated>2024-05-20T00:00:00+00:00</updated><id>https://gierdo.astounding.technology/blog/2024/05/20/llama3</id><content type="html" xml:base="https://gierdo.astounding.technology/blog/2024/05/20/llama3"><![CDATA[<h1 id="update-local-ai-assistant---llama-3">Update: Local AI assistant - llama 3</h1>

<p>You might have read my previous posts about <a href="/blog/2023/11/24/llama-vim">using a local codellama for a vim
coding assistant</a>, setting it up to
use <a href="/blog/2023/12/16/llama-vim-igpu-amd">my laptop’s GPU for inference</a> and the <a href="/blog/2023/12/17/llama-vim-igpu-amd-performance">performance evaluation</a> and so on.</p>

<p>Also, my previous <a href="/blog/2023/12/28/codellama-vs-mistral">evaluation of different models</a>, which formed the base of my
selection of which model to run locally.</p>

<p>Since quite a bit of time, there has been a new kid on the block: Meta’s <em>llama
3</em> is here! I’ve been running it since April, and it basically replaced my
previous local model.</p>

<p>I will not repeat the evaluation of the previous models, but apply the same
methods to <em>llama 3</em>. For the others, please take a look at the previous
article. Or, even better, try them out yourself.</p>

<h2 id="environment">Environment</h2>

<p>The general setup is described already in previous articles, mainly <a href="/blog/2023/12/16/llama-vim-igpu-amd">the
performance evaluation of GPU assisted inference</a></p>

<p>Here is the short summary:</p>

<ul>
  <li>Lenovo Thinkpad t14s gen 4</li>
  <li>llama.cpp with ROCm on AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics, 8G VRAM</li>
</ul>

<p>This results an boundary conditions for the models to chose from: The models
have to be usable with llama.cpp and cope with 8GB of VRAM.</p>

<p><em>llama 3</em> fits that requirement nicely in the quanticised 8B instruct variant:
<code class="language-plaintext highlighter-rouge">Meta-Llama-3-8B-Instruct.Q4_K_M.gguf</code></p>

<h2 id="method">Method</h2>

<p>I gave each model a specific task:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Please create a c function for an embedded platform (no stl or time.h, no
POSIX), which returns the unix timestamp for a given time.
</code></pre></div></div>

<p>With the created code in the context, I want to test each model’s ability to
work with given code, e.g. for comments and improvement.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>What is happening here?

What are potential problems with the given code?

Please give me a well-documented version of this code. Please follow best
practices and do not comment overly verbosely.
</code></pre></div></div>

<h3 id="llama-3">llama 3</h3>

<p>Llama 3 happy starts right away, ignoring significant parts of the instruction,
however: The given value is not unix timestamp compliant.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--------

Here is a simple C function that calculates the Unix timestamp (seconds since
January 1, 1970, 00:00:00 UTC) from a given year, month, day, hour, minute and
second:

```c
#include &lt;stdint.h&gt;

uint32_t unix_timestamp(int year, int month, int day, int hour, int min, int sec) {
    uint32_t timestamp = 0;

    // Calculate the seconds since January 1, 1970, 00:00:00 UTC
    timestamp += (year - 1970) * 31536000; // 31536000 is the number of seconds in a year

    // Adjust for leap years
    if ((year % 4 == 0 &amp;&amp; year % 100 != 0) || year % 400 == 0)
        timestamp++; // Add one second for February 29th

    int month_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    timestamp += (month - 1) * 2628000 + day * 86400; // Add days and hours
    timestamp += (hour * 3600); // Add hours
    timestamp += (min * 60); // Add minutes
    timestamp += sec;

    return timestamp;
}
```

Note: This function assumes that the input values are valid. It does not
perform any error checking or handling.

--------

</code></pre></div></div>

<p>The good thing is that llama is aware of it when asked:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--------

Are you sure this function returns a unix compliant timestamp?

--------

I made a mistake. The function I provided earlier does not correctly calculate
the Unix timestamp.

A Unix timestamp is the number of seconds that have elapsed since January 1,
1970, at midnight in Coordinated Universal Time (UTC). To correctly calculate
this, we need to consider the following:

* The epoch date (January 1, 1970)
* The leap years
* The daylight saving time

Here's a revised version of the function that calculates the Unix timestamp
more accurately:
```c
#include &lt;stdint.h&gt;

uint32_t unix_timestamp(int y, int m, int d, int h, int mi, int s) {
    uint32_t timestamp = 0;

    // Calculate the seconds since January 1, 1970, 00:00:00 UTC
    timestamp += (y - 1970) * 31536000; // 31536000 is the number of seconds in a year

    // Adjust for leap years
    if ((y % 4 == 0 &amp;&amp; y % 100 != 0) || y % 400 == 0)
        timestamp++; // Add one second for February 29th

    int month_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    timestamp += (m - 1) * 2628000 + d * 86400; // Add days and hours
    timestamp += h * 3600; // Add hours
    timestamp += mi * 60; // Add minutes
    timestamp += s;

    return timestamp;
}
```
This function takes into account the leap years, but it still does not consider
daylight saving time. If you need to account for daylight saving time, you
would need a more complex algorithm.

Keep in mind that this function is still not fully compliant with the Unix
timestamp standard, as it does not take into account the start and end times of
daylight saving time.

--------
</code></pre></div></div>

<p>Now, this is pretty nice. And we are all just humans, after all.</p>

<p>Now, let’s see if it understands what it has created.</p>

<p>With the (revised) generated function in the context, let’s ask it what is
happening, and what potential issues could be:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>What is happening here?

--------

This is a C function named `unix_timestamp` that takes six integer arguments:
year (`y`), month (`m`), day (`d`), hour (`h`), minute (`mi`), and second
(`s`). It calculates the Unix timestamp (number of seconds since January 1,
1970, 00:00:00 UTC) based on these input values.

--------

What are potential problems with the given code?

--------

1. The function does not handle invalid inputs such as dates that do not exist
   (e.g., February 30, 29 or April 31), negative months, or times outside of
   the range 0-23 for hours and 0-59 for minutes.

2. It does not consider daylight saving time (DST) changes, which can affect
   the accuracy of the timestamp calculation.

3. The function assumes that the system's clock is set to UTC, but if the
   system is in a different timezone, the calculated timestamp will be off by
   the difference between the local timezone and UTC.

4. It does not handle overflow when calculating the timestamp. If the sum of
   seconds exceeds 2^32-1 (the maximum value for a uint32_t), the calculation
   will wrap around, resulting in an incorrect timestamp.

5. The function can be improved to use more descriptive variable names instead
   of single-letter variable names like 'y', 'm', 'd', 'h', 'mi', 's'.

6. It does not consider the fact that month days are different for February and
   it's not correct to subtract 1 from month, because January is also a month.

7. The function should return a time_t type instead of uint32_t.

8. The code can be simplified by removing the conditional statements in the
   calculation of leap year.

9. The variable `month_days` is not necessary and can be removed.

10. The comment for the line `timestamp  += (y - 1970) * 31536000;` should be
    updated to accurately describe what it does.

---------

Can you give me an improved version?

--------

Here is a revised version of your code:

```c
#include &lt;stdint.h&gt;

time_t unix_timestamp(int year, int month, int day, int hour, int minute, int second) {
    if (month &lt; 1 || month &gt; 12)
        return -1;

    time_t timestamp = 0;

    // Calculate the seconds since January 1, 1970
    int total_seconds_in_year = 31536000; // 31536000 is the number of seconds in a year
    if ((year % 4 == 0 &amp;&amp; year % 100 != 0) || year % 400 == 0)
        ++total_seconds_in_year;

    int[] month_days = {31,28,31,30,31,30,31,31,30,31,30,31};
    int day_of_year = 0;
    for(int i = 1; i &lt; month; i++) {
        day_of_year += month_days[i-1];
    }
    day_of_year += day - 1;

    timestamp = (year - 1970) * total_seconds_in_year + day_of_year * 86400 + hour * 3600 + minute * 60 + second;

    return timestamp;
}
```

This version includes error checking for the month, adds a comment to describe what each section of code does, and simplifies some calculations.

--------

Please give me a well-documented version of this code. Please follow best
practices and do not comment overly verbosely.


--------

Here is the code with clear comments:

```c
#include &lt;stdint.h&gt;

// Convert a date in the format (year, month, day, hour, minute, second) to a Unix timestamp.
time_t unix_timestamp(int year, int month, int day, int hour, int minute, int second) {
    // Check if the month is within valid range (1-12)
    if (month &lt; 1 || month &gt; 12) {
        return -1; // Return an error
    }

    time_t timestamp = 0;

    // Calculate the number of seconds in a year, taking into account leap years
    int total_seconds_in_year = 31536000;
    if ((year % 4 == 0 &amp;&amp; year % 100 != 0) || year % 400 == 0) {
        ++total_seconds_in_year; // Leap year adjustment
    }

    // Array of days in each month (excluding February)
    int month_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int day_of_year = 0;

    // Calculate the total number of days before the given month
    for (int i = 1; i &lt; month; i++) {
        day_of_year += month_days[i - 1];
    }
    day_of_year += day - 1; // Adjust for 0-based indexing

    // Calculate the Unix timestamp
    timestamp = (year - 1970) * total_seconds_in_year + day_of_year * 86400 + hour * 3600 + minute * 60 + second;

    return timestamp;
}
```

--------
</code></pre></div></div>

<p>Yes! nice. It also tries to improve it’s code.</p>

<h4 id="verdict">Verdict</h4>

<p><em>Llama 3</em> understands the given task but is lacking a bit of attention to
detail. However, it can improve upon itsolf on reflection.</p>

<h2 id="general-non-coding-tasks">General non-coding tasks</h2>

<p>Ignore the content of the given task, it is merely a repetition of the task
from the previous evaluation.</p>

<h3 id="method-1">Method</h3>

<p>I put the following bullet points in the context of each model, and asked the
modell to write the given points in full sentences for a short tech article.</p>

<ul>
  <li>mistral is okay with coding tasks</li>
  <li>mistral appears to be good at general language tasks</li>
  <li>codellama appears to be good at coding tasks, with good knowledge of libraries</li>
  <li>codellama appears to be lacking at general language tasks</li>
</ul>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Please write the given points in full sentences for a short tech article.


--------

Here is the rewritten text:

Mistral is capable and competent when it comes to tackling coding tasks.
Additionally, Mistral demonstrates proficiency in general language-related
tasks. Codellama, on the other hand, excels at coding tasks, showcasing its
strong understanding of various libraries. However, Codellama's abilities
dwindle when it comes to general language tasks, indicating a specific area
where it requires improvement.

--------

Please elaborate a bit more.

--------

Here is an elaboration on the text:

Mistral's strengths lie in its ability to tackle coding tasks with ease and
proficiency. Whether it's working on a complex algorithm or debugging a piece
of code, Mistral is well-equipped to handle a wide range of programming
challenges.

In addition to its coding prowess, Mistral also demonstrates a strong
foundation in general language skills. It can understand and communicate
effectively through written text, making it an asset in any team that values
clear communication.

Codellama, on the other hand, stands out for its exceptional abilities in
coding tasks. Its deep understanding of various programming libraries and
frameworks allows it to create efficient, effective, and scalable solutions.

However, Codellama's strengths are largely limited to coding-related areas.
When it comes to general language tasks, such as writing coherent sentences or
conversing naturally, Codellama struggles. This highlights an area where it
requires improvement, potentially through exposure to more diverse linguistic
contexts and training exercises.

Overall, Mistral's versatility in both coding and language skills makes it a
valuable asset, while Codellama's specialized expertise in coding is noteworthy
but may benefit from further development in other areas.

--------

</code></pre></div></div>

<h3 id="summary">Summary</h3>

<p>The expanded text, especially the elaborative version, is pretty much spot-on
with the given information.</p>

<h2 id="verdict-1">Verdict</h2>

<p>I switched to llama. It yielded very nice results, while (this is not covered
in this particular article, maybe I’ll write another one?) being very
performant and being able to handle a relatively large context window locally
in my environment, which is a large contributing factor to the decision.</p>]]></content><author><name>Dominikus Gierlach</name></author><category term="programming" /><category term="linux" /><category term="openai" /><category term="ai" /><category term="llama" /><category term="AMD" /><summary type="html"><![CDATA[Update: Local AI assistant - llama 3]]></summary></entry><entry><title type="html">Cook your local environment with `mise-en-place`. And others.</title><link href="https://gierdo.astounding.technology/blog/2024/03/15/shim-cooking" rel="alternate" type="text/html" title="Cook your local environment with `mise-en-place`. And others." /><published>2024-03-15T21:29:00+00:00</published><updated>2024-03-15T21:29:00+00:00</updated><id>https://gierdo.astounding.technology/blog/2024/03/15/shim-cooking</id><content type="html" xml:base="https://gierdo.astounding.technology/blog/2024/03/15/shim-cooking"><![CDATA[<p>I try to keep as much of my relevant toolchain as up to date as possible,
without loosing control over it and breaking compatibility for different
projects.</p>

<p>This boils down to</p>

<ul>
  <li>Enable independent versions of the same program to be installable
    <ul>
      <li>e.g. I want java-21 but also java-17 in a different project</li>
    </ul>
  </li>
  <li>Define the installed/active programs and configuration as code</li>
  <li>Enable per-context activation of different versions</li>
</ul>

<p>There are many different approaches to achieve this, e.g.</p>

<ul>
  <li>Virtualization
    <ul>
      <li>Manually managed Project-context virtual machines</li>
      <li>Built-in e.g. with <a href="https://www.qubes-os.org/">qubes-OS</a> or
<a href="https://spectrum-os.org/">spectrum-os</a></li>
      <li>Powerful due to the level of isolation (security, flexibility)</li>
      <li>Highest overhead</li>
    </ul>
  </li>
  <li>Alternative distributions/package managers
    <ul>
      <li><a href="https://nixos.org/">NixOS</a></li>
      <li><a href="https://guix.gnu.org/">Guix</a></li>
      <li>Fully declarative environment</li>
    </ul>
  </li>
  <li>Containerization
    <ul>
      <li><a href="https://containers.dev/">devcontainers</a></li>
      <li>Custom build containers</li>
    </ul>
  </li>
  <li>shim managers
    <ul>
      <li><a href="https://github.com/pyenv/pyenv">pyenv</a></li>
      <li><a href="https://github.com/ekalinin/nodeenv">nodeenv</a></li>
      <li><a href="https://fig.io/manual/asdf/shim">asdf</a></li>
      <li><a href="https://mise.jdx.dev/">mise-en-place</a></li>
    </ul>
  </li>
</ul>

<p>I have played around/worked with a few of the given options and changed my
setup a few times already. Maybe the observations might be useful to you, or my
later self.</p>

<h2 id="boundary-conditions">Boundary conditions</h2>

<p>The boundary conditions of my setup are as follows</p>

<ul>
  <li>The target system is Linux only. My windows experience is not too deep, but I
don’t think that something I want is easily achievable on windows.</li>
  <li>I have most of my system setup experience with Debian, as I have been
running, breaking, fixing and working with Debian unstable for 17 years.</li>
  <li>I have quite a bit of experience with Ubuntu</li>
  <li>In general, I am very happy with Debian and am not interested in
distro-hopping. Switching away from Debian would have to be motivated by
significant advantages and no downsides</li>
  <li>I want my setup to be portable between my personal (Debian based) machines
and machines I don’t have full control over</li>
</ul>

<h2 id="path-to-the-current-setup">Path to the current setup</h2>

<h3 id="virtualization">Virtualization</h3>

<p>On my personal setup, I don’t have any usecases requiring windows or other
non-Linux operating systems, and my security requirements are not <em>that</em> high.
Consequently, I have not created and worked with a virtualization setup enough
to form an opinion strong enough to write about.</p>

<h3 id="nix">Nix</h3>

<h4 id="basics">Basics</h4>

<p>Nix, and with it NixOS, revolve around a functional domain specific language
that can fully declare environments, including installed packages/tools, making
use of the <code class="language-plaintext highlighter-rouge">Nixpkgs</code> packages collection, as well as their configuration.</p>

<p>These environments are fully self-contained, reproducible and very flexible.
With Nix, it is possible to declare the desired <em>system</em> state, but also
independent environments in e.g. the context of a specific development project,
or a temporary test environment that can be created and discarded afterwards.</p>

<p>This example defines a <code class="language-plaintext highlighter-rouge">nix-shell</code> environment with a specific ruby version:</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> <span class="nv">pkgs</span> <span class="o">?</span> <span class="kr">import</span> <span class="o">&lt;</span><span class="nv">nixpkgs</span><span class="o">&gt;</span> <span class="p">{}</span> <span class="p">}:</span>
  <span class="nv">pkgs</span><span class="o">.</span><span class="nv">mkShell</span> <span class="p">{</span>
    <span class="c"># nativeBuildInputs is usually what you want -- tools you need to run</span>
    <span class="nv">nativeBuildInputs</span> <span class="o">=</span> <span class="kn">with</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">buildPackages</span><span class="p">;</span> <span class="p">[</span> <span class="nv">ruby_3_2</span> <span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<p>What’s especially powerful with Nix is the fully self-contained and
reproducible build definition, which includes the full definition of the build
environment, as shown in this example, defining the build of <code class="language-plaintext highlighter-rouge">chord</code>:</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="nv">pkgs</span> <span class="o">?</span> <span class="kr">import</span> <span class="p">(</span><span class="kr">fetchTarball</span> <span class="p">{</span>
    <span class="nv">url</span> <span class="o">=</span> <span class="s2">"https://github.com/NixOS/nixpkgs/archive/4fe8d07066f6ea82cda2b0c9ae7aee59b2d241b3.tar.gz"</span><span class="p">;</span>
    <span class="nv">sha256</span> <span class="o">=</span> <span class="s2">"sha256:06jzngg5jm1f81sc4xfskvvgjy5bblz51xpl788mnps1wrkykfhp"</span><span class="p">;</span>
  <span class="p">})</span> <span class="p">{}</span>
<span class="p">}:</span>
<span class="nv">pkgs</span><span class="o">.</span><span class="nv">stdenv</span><span class="o">.</span><span class="nv">mkDerivation</span> <span class="kr">rec</span> <span class="p">{</span>
  <span class="nv">pname</span> <span class="o">=</span> <span class="s2">"chord"</span><span class="p">;</span>
  <span class="nv">version</span> <span class="o">=</span> <span class="s2">"0.1.0"</span><span class="p">;</span>

  <span class="nv">src</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">fetchgit</span> <span class="p">{</span>
    <span class="nv">url</span> <span class="o">=</span> <span class="s2">"https://gitlab.inria.fr/nix-tutorial/chord-tuto-nix-2022"</span><span class="p">;</span>
    <span class="nv">rev</span> <span class="o">=</span> <span class="s2">"069d2a5bfa4c4024063c25551d5201aeaf921cb3"</span><span class="p">;</span>
    <span class="nv">sha256</span> <span class="o">=</span> <span class="s2">"sha256-MlqJOoMSRuYeG+jl8DFgcNnpEyeRgDCK2JlN9pOqBWA="</span><span class="p">;</span>
  <span class="p">};</span>

  <span class="nv">buildInputs</span> <span class="o">=</span> <span class="p">[</span>
    <span class="nv">pkgs</span><span class="o">.</span><span class="nv">simgrid</span>
    <span class="nv">pkgs</span><span class="o">.</span><span class="nv">boost</span>
    <span class="nv">pkgs</span><span class="o">.</span><span class="nv">cmake</span>
  <span class="p">];</span>

  <span class="nv">configurePhase</span> <span class="o">=</span> <span class="s2">''</span><span class="err">
</span><span class="s2">    cmake .</span><span class="err">
</span><span class="s2">  ''</span><span class="p">;</span>

  <span class="nv">buildPhase</span> <span class="o">=</span> <span class="s2">''</span><span class="err">
</span><span class="s2">    make</span><span class="err">
</span><span class="s2">  ''</span><span class="p">;</span>

  <span class="nv">installPhase</span> <span class="o">=</span> <span class="s2">''</span><span class="err">
</span><span class="s2">    mkdir -p $out/bin</span><span class="err">
</span><span class="s2">    mv chord $out/bin</span><span class="err">
</span><span class="s2">  ''</span><span class="p">;</span>
</code></pre></div></div>

<p>Nix’ development is driven by a very active expert community and happens at an
impressively quick pace. Most activity, understandably, targets NixOS, not Nix
as third-party package manager.</p>

<p>Nix works by having a <em>package store</em>, which contains every package in every
installed version in a unique directory, distinguished with a unique hash. In
any requested environment, specific versions of everything are “activated”
specifically.</p>

<p>The result resembles <em>Containers</em> a lot, but with full system integration as
upside and less flexibility as downside. It’s even possible to build an <a href="https://nix.dev/tutorials/nixos/building-and-running-docker-images.html">OCI
compatible container
image</a>
out of a Nix environment.</p>

<h4 id="my-caveat">My Caveat</h4>

<p>I only tested Nix as package manager on Debian, which, I have to admit, is
somewhat limited. The main real issue I’ve had was with regards to <code class="language-plaintext highlighter-rouge">OpenGL</code>,
which is not supported in non-NixOS environments. This prevents any programs to
make proper use of graphics cards, and when I tested it, I tried to define my
<code class="language-plaintext highlighter-rouge">sway</code> setup with Nix. Which did not work without a lot of hassle.</p>

<p>There <a href="https://github.com/nix-community/nixGL">are solutions</a>, but it’s all
pretty meh. Besides that, the package management with Nix was pretty slow,
taking a lot of additional space and (for my usecases and experience) less
powerful than using “normal” OCI containers with Docker/podman.</p>

<h3 id="guix">Guix</h3>

<h4 id="basics-1">Basics</h4>

<p>Guix resembles Nix quite a bit. The principle is the same, you use a functional
programming language to fully declare the desired state of your
system/package/…, different versions of stuff are put into a <em>package store</em>
thing and are activated specifically, it’s a dedicated Distribution (<em>Guix
System</em>) and a package manager (<em>Guix</em>), etc.</p>

<p>The main difference is that the theoretical foundation is very nice, clean and
impressive. There is an extremely strong focus on <em>software freedom</em>, which is
nice, and it doesn’t use a domain specific (and hence a bit limited)
description language, but a full fledged functional programming language, <em>GNU
Guile</em>, which is a <em>Scheme</em> implementation. It’s pretty nice. And also, pretty
weird.</p>

<p>This is an example of a package definition for <code class="language-plaintext highlighter-rouge">postgresql-autodoc</code>, which I
created and put into my <code class="language-plaintext highlighter-rouge">guix-channel</code>, which is pretty easy to do. Which is,
again, pretty nice.</p>

<div class="language-scheme highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nf">define-module</span> <span class="p">(</span><span class="nf">postgresql-autodoc</span><span class="p">)</span>
               <span class="o">#</span><span class="nv">:use-module</span> <span class="p">(</span><span class="nf">guix</span> <span class="nv">utils</span><span class="p">)</span>
               <span class="o">#</span><span class="nv">:use-module</span> <span class="p">((</span><span class="nf">guix</span> <span class="nv">licenses</span><span class="p">)</span> <span class="o">#</span><span class="nv">:prefix</span> <span class="nv">license:</span><span class="p">)</span>
               <span class="o">#</span><span class="nv">:use-module</span> <span class="p">(</span><span class="nf">guix</span> <span class="nv">packages</span><span class="p">)</span>
               <span class="o">#</span><span class="nv">:use-module</span> <span class="p">(</span><span class="nf">guix</span> <span class="nv">git-download</span><span class="p">)</span>
               <span class="o">#</span><span class="nv">:use-module</span> <span class="p">(</span><span class="nf">guix</span> <span class="nv">build-system</span> <span class="nv">gnu</span><span class="p">)</span>
               <span class="o">#</span><span class="nv">:use-module</span> <span class="p">(</span><span class="nf">gnu</span> <span class="nv">packages</span> <span class="nv">base</span><span class="p">)</span>
               <span class="o">#</span><span class="nv">:use-module</span> <span class="p">(</span><span class="nf">gnu</span> <span class="nv">packages</span> <span class="nv">perl</span><span class="p">)</span>
               <span class="o">#</span><span class="nv">:use-module</span> <span class="p">(</span><span class="nf">gnu</span> <span class="nv">packages</span> <span class="nv">web</span><span class="p">)</span>
               <span class="o">#</span><span class="nv">:use-module</span> <span class="p">(</span><span class="nf">gnu</span> <span class="nv">packages</span> <span class="nv">databases</span><span class="p">))</span>

<span class="p">(</span><span class="nf">define-public</span> <span class="nv">postgresql-autodoc</span>
               <span class="p">(</span><span class="nf">package</span>
                 <span class="p">(</span><span class="nf">name</span> <span class="s">"postgresql-autodoc"</span><span class="p">)</span>
                 <span class="p">(</span><span class="nf">version</span> <span class="s">"80a6b150febb5c0c91f2daa433cc089ff1278841"</span><span class="p">)</span>
                 <span class="p">(</span><span class="nf">source</span>
                   <span class="p">(</span><span class="nf">origin</span>
                     <span class="p">(</span><span class="nf">method</span> <span class="nv">git-fetch</span><span class="p">)</span>
                     <span class="p">(</span><span class="nf">uri</span> <span class="p">(</span><span class="nf">git-reference</span>
                            <span class="p">(</span><span class="nf">url</span> <span class="s">"https://github.com/cbbrowne/autodoc"</span><span class="p">)</span>
                            <span class="p">(</span><span class="nf">commit</span> <span class="nv">version</span><span class="p">)))</span>
                     <span class="p">(</span><span class="nf">file-name</span> <span class="p">(</span><span class="nf">git-file-name</span> <span class="nv">name</span> <span class="nv">version</span><span class="p">))</span>
                     <span class="p">(</span><span class="nf">sha256</span>
                       <span class="p">(</span><span class="nf">base32</span>
                         <span class="s">"0ip1xy5qmm5hj392vxfs5fkgxgdv8d9kj7pp6p4x79npxczkxyzd"</span><span class="p">))))</span>
                 <span class="p">(</span><span class="nf">build-system</span> <span class="nv">gnu-build-system</span><span class="p">)</span>
                 <span class="p">(</span><span class="nf">arguments</span>
                   <span class="o">'</span><span class="p">(</span><span class="o">#</span><span class="nv">:phases</span>
                     <span class="p">(</span><span class="nf">modify-phases</span> <span class="nv">%standard-phases</span>
                                    <span class="p">(</span><span class="nf">delete</span> <span class="ss">'configure</span><span class="p">)</span>
                                    <span class="p">(</span><span class="nf">delete</span> <span class="ss">'check</span><span class="p">)</span>
                                    <span class="p">(</span><span class="nf">add-before</span> <span class="ss">'install</span> <span class="ss">'patch-prefix</span>
                                                <span class="p">(</span><span class="k">lambda</span> <span class="nv">_</span>
                                                  <span class="p">(</span><span class="nf">substitute*</span> <span class="p">(</span><span class="nf">cons*</span> <span class="s">"Makefile"</span><span class="p">)</span>
                                                               <span class="p">((</span><span class="s">"/usr/local"</span><span class="p">)</span> <span class="p">(</span><span class="nf">assoc-ref</span> <span class="nv">%outputs</span> <span class="s">"out"</span><span class="p">)))</span>
                                                  <span class="no">#t</span><span class="p">))</span>
                                    <span class="p">(</span><span class="nf">add-after</span> <span class="ss">'install</span> <span class="ss">'wrap-program</span>
                                               <span class="p">(</span><span class="nf">lambda*</span> <span class="p">(</span><span class="o">#</span><span class="nv">:key</span> <span class="nv">outputs</span> <span class="o">#</span><span class="nv">:allow-other-keys</span><span class="p">)</span>
                                                        <span class="p">(</span><span class="k">let*</span> <span class="p">((</span><span class="nf">out</span> <span class="p">(</span><span class="nf">assoc-ref</span> <span class="nv">outputs</span> <span class="s">"out"</span><span class="p">))</span>
                                                               <span class="p">(</span><span class="nf">path</span> <span class="p">(</span><span class="nf">getenv</span> <span class="s">"PERL5LIB"</span><span class="p">)))</span>
                                                          <span class="p">(</span><span class="nf">wrap-program</span> <span class="p">(</span><span class="nb">string-append</span> <span class="nv">out</span> <span class="s">"/bin/postgresql_autodoc"</span><span class="p">)</span>
                                                                        <span class="o">`</span><span class="p">(</span><span class="s">"PERL5LIB"</span> <span class="s">":"</span> <span class="nv">prefix</span>
                                                                          <span class="p">(</span><span class="nf">,</span><span class="p">(</span><span class="nb">string-append</span> <span class="nv">out</span> <span class="s">"/lib/perl5/site_perl"</span>
                                                                                           <span class="s">":"</span>
                                                                                           <span class="nv">path</span><span class="p">)))))</span>
                                                        <span class="no">#t</span><span class="p">))</span>
                                    <span class="p">)))</span>
                 <span class="p">(</span><span class="nf">native-inputs</span>
                   <span class="o">`</span><span class="p">((</span><span class="s">"which"</span><span class="o">,</span> <span class="nv">which</span><span class="p">)))</span>
                 <span class="p">(</span><span class="nf">inputs</span>
                   <span class="o">`</span><span class="p">((</span><span class="s">"perl"</span> <span class="o">,</span><span class="nv">perl</span><span class="p">)</span>
                     <span class="p">(</span><span class="s">"perl-html-template"</span><span class="o">,</span> <span class="nv">perl-html-template</span><span class="p">)</span>
                     <span class="p">(</span><span class="s">"perl-term-readkey"</span><span class="o">,</span> <span class="nv">perl-term-readkey</span><span class="p">)</span>
                     <span class="p">(</span><span class="s">"perl-dbd-pg"</span><span class="o">,</span> <span class="nv">perl-dbd-pg</span><span class="p">)))</span>
                 <span class="p">(</span><span class="nf">home-page</span> <span class="s">"https://github.com/cbbrowne/autodoc"</span><span class="p">)</span>
                 <span class="p">(</span><span class="nf">synopsis</span> <span class="s">"PostgreSQL Autodoc"</span><span class="p">)</span>
                 <span class="p">(</span><span class="nf">description</span>
                   <span class="s">"This is a utility which will run through PostgreSQL system tables and returns HTML, DOT, and several styles of XML which describe the database."</span><span class="p">)</span>
                 <span class="p">(</span><span class="nf">license</span> <span class="nv">license:bsd-3</span><span class="p">)))</span>
</code></pre></div></div>

<h4 id="my-caveat-1">My caveat</h4>

<p>Guix doesn’t have Nix’ problems with <em>OpenGL</em>, and it immediately felt much
nicer than Nix to me. I have used it for around a year to manage most of my
environment. The main beef I had with it was details and weird sluggishness in
details. Like with Nix, package management is/can be quite slow. Due to the
nature of things, depending on the current state of the system, it can happen
that you won’t install binary packages but build everything from scratch. Which
happens reliably, reproducibly and nicely, as expected. That’s the point, after
all. But it still takes SO MUCH TIME!! and the fan noise, and … Additionally,
for the graphical environment, I had weird issues I couldn’t quite resolve.
E.g. starting my desktop (<code class="language-plaintext highlighter-rouge">sway</code>) would take several seconds longer than with
pure Debian. Similarly, the initial start of my switcher application, <code class="language-plaintext highlighter-rouge">rofi</code>,
would take something between 3 and 10 seconds, compared to practically instant
on Debian. I spent a few evenings trying to solve the issue, eventually giving
up.</p>

<p>After a while, I accepted, again, that “normal” containers fit my usecase
better.</p>

<h3 id="containers">Containers</h3>

<h4 id="basics-2">Basics</h4>

<p>OCI containers work with <em>namespaces</em> to isolate system resources (processes,
networking, users, mountpoints, ipc, time) and virtual filesystems. The desired
state of a container is freezed in a <em>container image</em>, which basically defines
the virtual base filesystem in <em>layers</em> and certain system interfaces, from
which an actual <em>container</em> can be started. These <em>images</em> are usually defined
in a <code class="language-plaintext highlighter-rouge">Dockerfile</code>, which is the build definition of a container image, and can
be stored, distributed and shared. This way, containers can solve problems in
system dependency management, software distribution and orchestration.</p>

<p>This is an example of a <code class="language-plaintext highlighter-rouge">Dockerfile</code>, defining an environment based on
<code class="language-plaintext highlighter-rouge">ubuntu:jammy</code>:</p>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> ubuntu:jammy</span>

<span class="k">RUN </span>apt-get update <span class="o">&amp;&amp;</span> apt-get <span class="nb">install</span> <span class="nt">-y</span> <span class="nt">--no-install-recommends</span> <span class="se">\
</span>  ca-certificates <span class="se">\
</span>  wget <span class="se">\
</span>  gpg <span class="se">\
</span>  pkg-config <span class="se">\
</span>  <span class="o">&amp;&amp;</span> <span class="nb">rm</span> <span class="nt">-rf</span> /var/lib/<span class="o">{</span>apt,dpkg,cache,log<span class="o">}</span>/
</code></pre></div></div>

<p>Containers can be built <code class="language-plaintext highlighter-rouge">FROM scratch</code> or based on existing environments, e.g.
benefiting on a strong community and ecosystem. There are base images from
basically every major Linux distribution and <em>distroless</em> images, limiting the
attack surface of application environments. Base images and final images are
cryptographically identified and can be referenced reproducibly and explicitly.</p>

<h4 id="my-caveat-2">My caveat</h4>

<p>I have been using <a href="https://podman.io/">podman</a> as highlevel container
management solution and alternative to <code class="language-plaintext highlighter-rouge">Docker</code>, mainly due to it’s
(root-)daemonless nature.
I use it to run and expose permanent network based local services (see also
<a href="/blog/2023/11/24/llama-vim">this</a> or development environments.</p>

<p>One advantage with <code class="language-plaintext highlighter-rouge">podman</code> is that even without additional magic, due to the
user ID mapping, the root UID within a container maps to the UID of the user
who started the container outside of the container. Rootless containers for the
win! This allows you to simply mount a workspace or other resources into a
container and work with it inside of the container without having to worry
about file permissions and ownership.</p>

<p>E.g. like this.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>podman run <span class="nt">--annotation</span> run.oci.keep_original_groups<span class="o">=</span>1 <span class="nt">-v</span> <span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span>/.local/lib/llama/models:/models <span class="nt">--device</span> /dev/kfd <span class="nt">--device</span> /dev/dri <span class="nt">--rm</span> <span class="nt">-it</span> localhost/llama-cpp-python-server-rocm:0.2.20 bash
</code></pre></div></div>

<p>I like the flexibility, manageability and reusability given by containers a
lot and use them heavily.</p>

<p>For the creation of <code class="language-plaintext highlighter-rouge">Dockerfiles</code>, I make use of
<a href="https://github.com/rcjsuen/dockerfile-language-server"><code class="language-plaintext highlighter-rouge">dockerfile-language-server</code></a>
through <a href="https://github.com/josa42/coc-docker"><code class="language-plaintext highlighter-rouge">coc-docker</code></a> and a fancy shell
setup with <a href="https://github.com/ohmyzsh/ohmyzsh/blob/master/plugins/podman/README.md"><code class="language-plaintext highlighter-rouge">zsh
podman</code></a>,
and <a href="https://github.com/jesseduffield/lazydocker"><code class="language-plaintext highlighter-rouge">lazydocker</code></a>, which makes
writing Dockerfiles and working with containers pretty convenient.</p>

<p>However, system integration is not ideal. Some (most?) of that is by design, a
container is supposed to be separated from the host system, after all. What if
I just want to isolate a specific tool and specify it in the context of a
project? Especially, if the tool does not have any specific system
dependencies, but is rather self-contained?</p>

<h3 id="shim-managers">Shim managers</h3>

<p>As the <a href="https://github.com/asdf-vm/asdf">README</a> of <code class="language-plaintext highlighter-rouge">asdf</code> states:</p>

<blockquote>
  <p>Once upon a time there was a programming language
There were many versions of it
So people wrote a version manager for it
To switch between versions for projects
Different, old, new.</p>

  <p>Then there came more programming languages
So there came more version managers
And many commands for them</p>

  <p>I installed a lot of them
I learnt a lot of commands</p>

  <p>Then I said, just one more version manager
Which I will write instead</p>

  <p>So, there came another version manager
asdf version manager - https://github.com/asdf-vm/asdf</p>

  <p>A version manager so extendable
for which anyone can create a plugin
To support their favourite language
No more installing more version managers
Or learning more commands</p>
</blockquote>

<h4 id="basics-3">Basics</h4>

<p>In come <em>shim managers</em>! These programs manage the installation of <em>other</em>
programs, capable of updating them, installing and activating different
versions of the desired program and (ideally) integrating seamlessly with the
rest of the environment and ecosystem. This only works, of course, if the
managed program does not have any specific system dependencies, e.g. specific
shared libraries etc.</p>

<p>There are <em>language specific</em> managers, e.g. for <em>python</em> there is
<a href="https://github.com/pyenv/pyenv"><code class="language-plaintext highlighter-rouge">pyenv</code></a>, which integrates very nicely with
e.g. <code class="language-plaintext highlighter-rouge">poetry</code> and general python virtual environments. <em>Rust</em> has it’s own
installer and toolchain manager, <a href="https://rustup.rs/"><code class="language-plaintext highlighter-rouge">rustup</code></a>, which is also
capable of managing different versions of <em>rust</em>.</p>

<p>Even more powerful are general purpose shim managers like
<a href="https://fig.io/manual/asdf/shim"><code class="language-plaintext highlighter-rouge">asdf</code></a> and, written in rust, more modern and
compatible with <code class="language-plaintext highlighter-rouge">asdf</code>, <a href="https://mise.jdx.dev/"><code class="language-plaintext highlighter-rouge">mise-en-place</code></a>.</p>

<p><code class="language-plaintext highlighter-rouge">mise</code> has a rich ecosystem of plugins, which allows it to manage versions of a
plethora of programs. These can be activated on a system level, temporary shell
level or in the context of (project) directories.</p>

<p>This could be a system level configuration at <code class="language-plaintext highlighter-rouge">~/.config/mise/config.toml</code>, or
a specific project configuration in <code class="language-plaintext highlighter-rouge">~/workspace/foo/.mise.toml</code></p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[tools]</span>
<span class="py">go</span> <span class="p">=</span> <span class="s">"1.22.0"</span>
<span class="py">node</span> <span class="p">=</span> <span class="s">"21.6.1"</span>
<span class="py">java</span> <span class="p">=</span> <span class="s">"adoptopenjdk-21.0.2+13.0.LTS"</span>
<span class="py">ruby</span> <span class="p">=</span> <span class="s">"3.3.0"</span>
<span class="py">awscli</span> <span class="p">=</span> <span class="s">"2.15.19"</span>
<span class="py">direnv</span> <span class="p">=</span> <span class="s">"2.34.0"</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">mise</code> makes sure that, in the respective context, the specified version of the
specified tools are configured and take precedence in the <code class="language-plaintext highlighter-rouge">$PATH</code>.</p>

<h4 id="my-caveat-3">My caveat</h4>

<p>I have been using <code class="language-plaintext highlighter-rouge">pyenv</code>, <code class="language-plaintext highlighter-rouge">asdf</code> and <code class="language-plaintext highlighter-rouge">rustup</code> for a long time, recently
replacing <code class="language-plaintext highlighter-rouge">asdf</code> with <code class="language-plaintext highlighter-rouge">mise</code>. Obviously, <code class="language-plaintext highlighter-rouge">pyenv</code> and <code class="language-plaintext highlighter-rouge">rustup</code> are only used to
manage <code class="language-plaintext highlighter-rouge">python</code> and <code class="language-plaintext highlighter-rouge">rust</code>.</p>

<p>I have replaced <code class="language-plaintext highlighter-rouge">asdf</code> with <code class="language-plaintext highlighter-rouge">mise</code>, as it’s much faster, more actively
maintained and maintainable at all. While <code class="language-plaintext highlighter-rouge">asdf</code> is very impressive, it’s
written in <code class="language-plaintext highlighter-rouge">bash</code>(!), and I’m almost sure that the authors regularly curse
their choice. <code class="language-plaintext highlighter-rouge">mise</code> is compatible with <code class="language-plaintext highlighter-rouge">asdf</code>, but extends its functionality.
And it works great!</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>java <span class="nt">--version</span>
openjdk 21.0.2 2024-01-16 LTS
OpenJDK Runtime Environment Temurin-21.0.2+13 <span class="o">(</span>build 21.0.2+13-LTS<span class="o">)</span>
OpenJDK 64-Bit Server VM Temurin-21.0.2+13 <span class="o">(</span>build 21.0.2+13-LTS, mixed mode, sharing<span class="o">)</span>
<span class="nv">$ </span>mise shell java@corretto-21.0.2.14.1
mise java@corretto-21.0.2.14.1 downloading amazon-corretto-21.0.2.1 72.68 MiB/199.70 MiB <span class="o">(</span>14s<span class="o">)</span> ███████░░░░░░░░░░░░░  7s
<span class="nb">.</span>
<span class="nb">.</span>
<span class="nb">.</span>
<span class="nv">$ </span>java <span class="nt">--version</span>
openjdk 21.0.2 2024-01-16 LTS
OpenJDK Runtime Environment Corretto-21.0.2.14.1 <span class="o">(</span>build 21.0.2+14-LTS<span class="o">)</span>
OpenJDK 64-Bit Server VM Corretto-21.0.2.14.1 <span class="o">(</span>build 21.0.2+14-LTS, mixed mode, sharing<span class="o">)</span>
<span class="nv">$ </span>mise use java@corretto-21.0.2.14.1 <span class="nt">-p</span> ./
mise ./.mise.toml tools: java@corretto-21.0.2.14.1
<span class="nv">$ </span><span class="nb">cat</span> .mise.toml
<span class="o">[</span>tools]
java <span class="o">=</span> <span class="s2">"corretto-21.0.2.14.1"</span>
</code></pre></div></div>

<p>Integration is absolutely seamless, once it’s set up correctly.</p>

<p>If you are interested in the setup, check the (linked) documentation, or take a
look at my <a href="https://github.com/gierdo/dotfiles">dotfiles</a> :)</p>

<h2 id="tldr">tl;dr</h2>

<ul>
  <li>It’s good to have different versions of programs</li>
  <li>It’s hard to manage different versions of programs</li>
  <li>Different ways of managing this have different (dis)advantages</li>
  <li>Containers are not too bad sometimes</li>
  <li>Shim managers are also not too bad sometimes</li>
</ul>]]></content><author><name>Dominikus Gierlach</name></author><category term="programming" /><category term="linux" /><summary type="html"><![CDATA[I try to keep as much of my relevant toolchain as up to date as possible, without loosing control over it and breaking compatibility for different projects.]]></summary></entry><entry><title type="html">Local AI assistant in vim - AMD iGPU and ROCm in a slightly optimized container</title><link href="https://gierdo.astounding.technology/blog/2024/03/02/llama-igpu-amd-container" rel="alternate" type="text/html" title="Local AI assistant in vim - AMD iGPU and ROCm in a slightly optimized container" /><published>2024-03-02T00:00:00+00:00</published><updated>2024-03-02T00:00:00+00:00</updated><id>https://gierdo.astounding.technology/blog/2024/03/02/llama-igpu-amd-container</id><content type="html" xml:base="https://gierdo.astounding.technology/blog/2024/03/02/llama-igpu-amd-container"><![CDATA[<h1 id="update-local-ai-assistant-in-vim-with-codellama-with-igpu-accelerated-inference---container-image-details">Update: Local AI assistant in vim with codellama with iGPU accelerated inference - container image details</h1>

<p>My previous containerization refinement of llama for the local AI assistant
stopped after fulfilling the following requirements:</p>

<ul>
  <li>It works</li>
  <li>It uses ROCm for GPU accelerated inference</li>
  <li>It allows me to specify a ROCm version at build time</li>
  <li>It allows me to specify a llama-cpp-python version at build time</li>
</ul>

<p>With these requirements, I created a single stage <code class="language-plaintext highlighter-rouge">Dockerfile</code>, resulting in a
quite large container image, and stopped right there. It worked, after all.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
podman inspect <span class="nt">-f</span> <span class="s2">"{{ .Size }}"</span> ghcr.io/gierdo/dotfiles/llama-cpp-python-server-rocm:0.2.44-6.0.2-11.0.2 | <span class="nb">numfmt</span> <span class="nt">--to</span><span class="o">=</span>si
23G

</code></pre></div></div>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> ubuntu:jammy</span>

<span class="k">RUN </span>apt-get update <span class="o">&amp;&amp;</span> apt-get <span class="nb">install</span> <span class="nt">-y</span> <span class="nt">--no-install-recommends</span> <span class="se">\
</span>  ninja-build <span class="se">\
</span>  libopenblas-dev <span class="se">\
</span>  python3-pip <span class="se">\
</span>  wget <span class="se">\
</span>  gpg <span class="se">\
</span>  git <span class="se">\
</span>  cmake <span class="se">\
</span>  pkg-config <span class="se">\
</span>  build-essential

<span class="k">ARG</span><span class="s"> ROCM_VERSION=6.0.2</span>

<span class="k">RUN </span><span class="nb">mkdir</span> <span class="nt">--parents</span> <span class="nt">--mode</span><span class="o">=</span>0755 /etc/apt/keyrings <span class="se">\
</span>  <span class="o">&amp;&amp;</span> wget https://repo.radeon.com/rocm/rocm.gpg.key <span class="nt">-O</span> - | <span class="se">\
</span>  gpg <span class="nt">--dearmor</span> | <span class="nb">tee</span> /etc/apt/keyrings/rocm.gpg <span class="o">&gt;</span> /dev/null <span class="se">\
</span>  <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/</span><span class="k">${</span><span class="nv">ROCM_VERSION</span><span class="k">}</span><span class="s2"> jammy main"</span> <span class="o">&gt;</span> /etc/apt/sources.list.d/rocm.list <span class="se">\
</span>  <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s1">'Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600'</span> <span class="o">&gt;</span> /etc/apt/preferences.d/rocm-pin-600 <span class="se">\
</span>  <span class="o">&amp;&amp;</span> apt-get update <span class="se">\
</span>  <span class="o">&amp;&amp;</span> apt-get <span class="nb">install</span> <span class="nt">-y</span> <span class="nt">--no-install-recommends</span> rocm-hip-sdk

<span class="k">ARG</span><span class="s"> OVERRIDE_GFX_VERSION=11.0.2</span>
<span class="k">ENV</span><span class="s"> HSA_OVERRIDE_GFX_VERSION=${OVERRIDE_GFX_VERSION}</span>

<span class="k">ARG</span><span class="s"> LLAMA_CPP_VERSION=0.2.44</span>
<span class="k">RUN </span><span class="nv">CC</span><span class="o">=</span>hipcc <span class="nv">CXX</span><span class="o">=</span>hipcc <span class="nv">CMAKE_ARGS</span><span class="o">=</span><span class="s2">"-DLLAMA_HIPBLAS=on"</span> pip <span class="nb">install </span>llama-cpp-python[server]<span class="o">==</span><span class="k">${</span><span class="nv">LLAMA_CPP_VERSION</span><span class="k">}</span>

<span class="k">ENV</span><span class="s"> HOST=0.0.0.0</span>
<span class="k">ENV</span><span class="s"> PORT=8000</span>
<span class="k">EXPOSE</span><span class="s"> 8000</span>

<span class="k">CMD</span><span class="s"> ["bash", "-c", "python3 -m llama_cpp.server --n_gpu_layers 15 --host ${HOST} --port ${PORT}"]</span>
</code></pre></div></div>

<h2 id="the-problem">The problem</h2>

<p>As you can see, I simply dumped the entire <code class="language-plaintext highlighter-rouge">rocm-hip-sdk</code> into the image and
that’s that. That means, of course, that the <em>build time</em> dependencies, so
everything needed to <em>compile</em> software to use ROCm is part of the image, next
to everything that is required to <em>execute</em> compiled software. The sole purpose
of the final container is to <em>execute</em> the compiled llama-cpp-python server, so
all the <em>build time</em> dependencies are just wasted space.</p>

<h2 id="the-solution">The solution</h2>

<p>Container builds can be split into <em>stages</em>, with only the last stage being
baked into the final image. Those <em>stages</em> can depend upon previous stages or
share content, <em>copied</em> from one stage into the other.</p>

<p>With that approach, a separate stage can be set up with all the <em>build time</em>
dependencies, used to compile the desired artifact, which is then <em>copied</em> into
the final image which only contains the <em>run time</em> dependencies.</p>

<p>As docker/podman caches and reuses identical layers, you can build your
<code class="language-plaintext highlighter-rouge">Dockerfile</code> in a way that allows the build process to reuse as much as
possible, while still keeping the final image small, in order to speed up your
local build process. You don’t want to wait for apt to download <em>14Gb</em> of
libraries just because you changed the version of a python package to be
installed, after all.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
podman inspect <span class="nt">-f</span> <span class="s2">"{{ .Size }}"</span> ghcr.io/gierdo/dotfiles/llama-cpp-python-server-rocm:0.2.54-6.0.2-11.0.2 | <span class="nb">numfmt</span> <span class="nt">--to</span><span class="o">=</span>si
15G

</code></pre></div></div>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="w"> </span><span class="s">ubuntu:jammy</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">rocm_base</span>

<span class="k">RUN </span>apt-get update <span class="o">&amp;&amp;</span> apt-get <span class="nb">install</span> <span class="nt">-y</span> <span class="nt">--no-install-recommends</span> <span class="se">\
</span>  ca-certificates <span class="se">\
</span>  wget <span class="se">\
</span>  gpg <span class="se">\
</span>  pkg-config <span class="se">\
</span>  <span class="o">&amp;&amp;</span> <span class="nb">rm</span> <span class="nt">-rf</span> /var/lib/<span class="o">{</span>apt,dpkg,cache,log<span class="o">}</span>/

<span class="k">ARG</span><span class="s"> ROCM_VERSION=6.0.2</span>

<span class="k">RUN </span><span class="nb">mkdir</span> <span class="nt">--parents</span> <span class="nt">--mode</span><span class="o">=</span>0755 /etc/apt/keyrings <span class="se">\
</span>  <span class="o">&amp;&amp;</span> wget https://repo.radeon.com/rocm/rocm.gpg.key <span class="nt">-O</span> - | <span class="se">\
</span>  gpg <span class="nt">--dearmor</span> | <span class="nb">tee</span> /etc/apt/keyrings/rocm.gpg <span class="o">&gt;</span> /dev/null <span class="se">\
</span>  <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/</span><span class="k">${</span><span class="nv">ROCM_VERSION</span><span class="k">}</span><span class="s2"> jammy main"</span> <span class="o">&gt;</span> /etc/apt/sources.list.d/rocm.list <span class="se">\
</span>  <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s1">'Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600'</span> <span class="o">&gt;</span> /etc/apt/preferences.d/rocm-pin-600

<span class="c"># Allow caching the runtime base even if llama_cpp is rebuilt</span>
<span class="k">FROM</span><span class="w"> </span><span class="s">rocm_base</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">runtime_base</span>

<span class="k">RUN </span>apt-get update <span class="o">&amp;&amp;</span> apt-get <span class="nb">install</span> <span class="nt">-y</span> <span class="nt">--no-install-recommends</span> <span class="se">\
</span>  python3-pip <span class="se">\
</span>  rocm-hip-libraries <span class="se">\
</span>  <span class="o">&amp;&amp;</span> <span class="nb">rm</span> <span class="nt">-rf</span> /var/lib/<span class="o">{</span>apt,dpkg,cache,log<span class="o">}</span>/


<span class="k">FROM</span><span class="w"> </span><span class="s">runtime_base</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">builder</span>

<span class="k">RUN </span>apt-get update <span class="o">&amp;&amp;</span> apt-get <span class="nb">install</span> <span class="nt">-y</span> <span class="nt">--no-install-recommends</span> <span class="se">\
</span>  ninja-build <span class="se">\
</span>  libopenblas-dev <span class="se">\
</span>  git <span class="se">\
</span>  cmake <span class="se">\
</span>  pkg-config <span class="se">\
</span>  build-essential <span class="se">\
</span>  rocm-hip-sdk <span class="se">\
</span>  <span class="o">&amp;&amp;</span> <span class="nb">rm</span> <span class="nt">-rf</span> /var/lib/<span class="o">{</span>apt,dpkg,cache,log<span class="o">}</span>/

<span class="k">ARG</span><span class="s"> OVERRIDE_GFX_VERSION=11.0.2</span>
<span class="k">ENV</span><span class="s"> HSA_OVERRIDE_GFX_VERSION=${OVERRIDE_GFX_VERSION}</span>
<span class="k">ARG</span><span class="s"> LLAMA_CPP_VERSION=0.2.54</span>

<span class="k">RUN </span><span class="nv">CC</span><span class="o">=</span>hipcc <span class="nv">CXX</span><span class="o">=</span>hipcc <span class="nv">CMAKE_ARGS</span><span class="o">=</span><span class="s2">"-DLLAMA_HIPBLAS=on"</span> pip <span class="nb">install</span> <span class="nt">--user</span> llama-cpp-python[server]<span class="o">==</span><span class="k">${</span><span class="nv">LLAMA_CPP_VERSION</span><span class="k">}</span>

<span class="k">FROM</span><span class="w"> </span><span class="s">runtime_base</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">final</span>
<span class="k">LABEL</span><span class="s"> maintainer="Dominikus Gierlach &lt;dominik.gierlach@gmail.com&gt;"</span>

<span class="k">ARG</span><span class="s"> OVERRIDE_GFX_VERSION=11.0.2</span>
<span class="k">ENV</span><span class="s"> HSA_OVERRIDE_GFX_VERSION=${OVERRIDE_GFX_VERSION}</span>

<span class="k">COPY</span><span class="s"> --from=builder /root/.local /root/.local</span>

<span class="k">ENV</span><span class="s"> HOST=0.0.0.0</span>
<span class="k">ENV</span><span class="s"> PORT=8000</span>
<span class="k">EXPOSE</span><span class="s"> 8000</span>

<span class="k">CMD</span><span class="s"> ["bash", "-c", "python3 -m llama_cpp.server --n_gpu_layers 15 --host ${HOST} --port ${PORT}"]</span>
</code></pre></div></div>

<p>The resulting image is still large and could be slimmed down. It now contains
the full collection of ROCm libraries and runtime dependencies. I could
probably walk through them and just identify the stuff that is strictly
required. But losing <em>8Gb</em> of image size, basically for free, is a better use
of my time, I think.</p>

<h2 id="tldr">tl;dr</h2>

<ul>
  <li>Container images should be small</li>
  <li>Container images with ROCm are large</li>
  <li>Container images should contain as little unneeded stuff as possible</li>
  <li>Build time dependencies may not be required at runtime</li>
  <li>Multistage Dockerfiles can help with that</li>
  <li>Ordering stages and layers can still allow you to use caching in the build
process</li>
</ul>]]></content><author><name>Dominikus Gierlach</name></author><category term="programming" /><category term="linux" /><category term="vim" /><category term="openai" /><category term="ai" /><category term="containers" /><category term="llama" /><category term="ROCm" /><category term="AMD" /><summary type="html"><![CDATA[Update: Local AI assistant in vim with codellama with iGPU accelerated inference - container image details]]></summary></entry></feed>