<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Posts on GA</title>
    <link>https://galenabell.com/posts/</link>
    <description>Recent content in Posts on GA</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 04 Jan 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://galenabell.com/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Introducing Box</title>
      <link>https://galenabell.com/2026/01/04/introducing-box/</link>
      <pubDate>Sun, 04 Jan 2026 00:00:00 +0000</pubDate>
      <guid>https://galenabell.com/2026/01/04/introducing-box/</guid>
      <description>&lt;p&gt;Package managers are my favorite part of Linux. Having a consistent, curated way to install and upgrade your system makes everything easier and more secure. That&amp;rsquo;s not to say there can&amp;rsquo;t still be &lt;a href=&#34;https://en.wikipedia.org/wiki/XZ_Utils_backdoor&#34;&gt;security issues&lt;/a&gt;, but if you can&amp;rsquo;t &lt;em&gt;generally&lt;/em&gt; trust the system packages you probably have bigger issues, and it beats downloading random binaries off the internet.&lt;/p&gt;&#xA;&lt;p&gt;Unfortunately (and ironically) this system starts to fall apart when it comes to software development, especially at work where you may have less control over which tools and dependencies are used. While most Linux distributions include a wide array of programming tools, there are often weird requirements for specific versions or third-party tooling which may not be packaged. And although we can assume that the language tooling itself is secure if it comes from the package manager, the software we build and run with it may have vulnerable or even malicious dependencies that could wreak havoc on our system.&lt;/p&gt;&#xA;&lt;p&gt;A common way to ameliorate these issues is to use containers for development. Although they aren&amp;rsquo;t true sandboxes, containers can add additional layers of protection by preventing binaries from accessing parts of your system they shouldn&amp;rsquo;t (i.e. most of your home directory), and since they don&amp;rsquo;t share a root filesystem with the host, they can run arbitrary versions of tools without interference. Programs like &lt;code&gt;docker compose&lt;/code&gt; are often used to automate running a set of services for development (e.g. PostgreSQL), but using containers for running CLI tools (e.g. &lt;code&gt;go build&lt;/code&gt;) has significantly worse user experience than installing and running the tools directly.&lt;/p&gt;&#xA;&lt;p&gt;The generally accepted solution to this appears to be &lt;a href=&#34;https://containers.dev/&#34;&gt;devcontainers&lt;/a&gt;, which installs and executes all the required tools for a project inside a semi-persistent development environment. This is a nice idea in theory, as new team members or contributors can just spin up a container with everything preconfigured, but since the development environments need to be codified, it works best when everyone uses the same tooling and workflows (even code editors - devcontainers are rather tightly coupled with Visual Studio Code). What I want is the opposite: rely on my system&amp;rsquo;s package manager as much as possible, and only run specific tools in containers based on security or versioning concerns.&lt;/p&gt;&#xA;&lt;h2 id=&#34;enter-box&#34;&gt;Enter &lt;code&gt;box&lt;/code&gt;&lt;/h2&gt;&#xA;&lt;p&gt;My solution is &lt;code&gt;box&lt;/code&gt;: a CLI for transparently running your development tools in a container, with the goal of combining the security and versioning benefits of containers with the ease of running tools directly on the host. &lt;code&gt;box&lt;/code&gt; (ab)uses your &lt;code&gt;PATH&lt;/code&gt; to replace your development tools with symlinks to itself, then executes the corresponding command in a container. As a result, you can continue using the tool as if it were natively installed, including in scripts or in your editor (e.g. Neovim&amp;rsquo;s language server integration).&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;box&lt;/code&gt; is essentially a wrapper around a container runtime (currently Docker and Podman are supported). It is configured via environment variables, which map loosely to the runtime&amp;rsquo;s directives, e.g. &lt;code&gt;BOX_ENVS=&amp;quot;LOG_LEVEL=debug HTTP_PORT=8080&amp;quot;&lt;/code&gt;. This configuration method was chosen primarily because it allows for nested configs and overrides out-of-the-&lt;code&gt;box&lt;/code&gt;, since environment variables can be composed from other environment variables (e.g. &lt;code&gt;export BOX_ENVS=&amp;quot;$BOX_ENVS HTTP_HOST=0.0.0.0&amp;quot;&lt;/code&gt;). As an added benefit, it also integrates very well with tools that manage environment variables, such as &lt;a href=&#34;https://direnv.net/&#34;&gt;direnv&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-case-box-study&#34;&gt;A &lt;del&gt;Case&lt;/del&gt; &lt;code&gt;box&lt;/code&gt; Study&lt;/h2&gt;&#xA;&lt;p&gt;At work I am writing a Rust-based firmware for an ESP32-S3 microcontroller. This device has an Xtensa microprocessor, which is not an officially supported Rust target, and as such requires a forked toolchain maintained by Espressif (the makers of the ESP platform). In this case, we can use &lt;code&gt;box&lt;/code&gt; to run everything safely in a container.&lt;/p&gt;&#xA;&lt;p&gt;First we&amp;rsquo;ll need to set our runtime; since this will probably be the same for all projects, I like to set this in my &lt;code&gt;.profile&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_RUNTIME&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;podman&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next we&amp;rsquo;ll need an image from which to create a container. Espressif publishes &lt;a href=&#34;https://hub.docker.com/r/espressif/idf-rust&#34;&gt;an image&lt;/a&gt; with the toolchain already included, but we&amp;rsquo;ll need to add some extra tools:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-containerfile&#34; data-lang=&#34;containerfile&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;espressif/idf-rust:esp32s3_1.88.0.0&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;RUN&lt;/span&gt; rm ~/.cargo/bin/rust-analyzer &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;&#x9;curl -sL https://github.com/rust-lang/rust-analyzer/releases/download/2025-08-25/rust-analyzer-x86_64-unknown-linux-gnu.gz &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; gunzip -c - &amp;gt; ~/.cargo/bin/rust-analyzer &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;&#x9;chmod +x ~/.cargo/bin/rust-analyzer&lt;span class=&#34;err&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;RUN&lt;/span&gt; rm ~/.cargo/bin/cargo-espflash ~/.cargo/bin/espflash &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;&#x9;curl -sL https://github.com/esp-rs/espflash/releases/download/v4.3.0/cargo-espflash-x86_64-unknown-linux-gnu.zip &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; gunzip -c - &amp;gt;~/.cargo/bin/cargo-espflash &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;&#x9;curl -sL https://github.com/esp-rs/espflash/releases/download/v4.3.0/espflash-x86_64-unknown-linux-gnu.zip &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; gunzip -c - &amp;gt;~/.cargo/bin/espflash &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;&#x9;chmod +x ~/.cargo/bin/cargo-espflash ~/.cargo/bin/espflash&lt;span class=&#34;err&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;COPY&lt;/span&gt; ./entrypoint.sh /usr/local/bin/entrypoint.sh&lt;span class=&#34;err&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;ENTRYPOINT&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/usr/local/bin/entrypoint.sh&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we install &lt;code&gt;rust-analyzer&lt;/code&gt; (Rust&amp;rsquo;s language server) and a newer version of &lt;code&gt;espflash&lt;/code&gt; (for flashing and monitoring the microcontrollers). We need to replace the &lt;code&gt;rust-analyzer&lt;/code&gt; that is already in the image since it is installed for the default toolchain and won&amp;rsquo;t work with the ESP toolchain.&lt;/p&gt;&#xA;&lt;p&gt;The entrypoint mentioned sources some necessary setup scripts:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash -eu&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;source&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.cargo/env&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;source&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/export-esp.sh&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$@&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By default the container uses &lt;code&gt;/bin/bash&lt;/code&gt; as the &lt;code&gt;CMD&lt;/code&gt;, but we need these scripts to be sourced for the toolchain to work.&lt;/p&gt;&#xA;&lt;p&gt;With our image ready, we can configure &lt;code&gt;box&lt;/code&gt;. I use &lt;code&gt;direnv&lt;/code&gt; to automatically set the necessary environment variables when I enter the directory, but they could also be manually set or sourced from a script. Here&amp;rsquo;s my &lt;code&gt;.envrc&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_IMAGE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;esp&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;dotenv_if_exists&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;envs_from_dotenv&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;envs_from_dotenv&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_ENVS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;CARGO_HOME=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.cargo &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$envs_from_dotenv&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_VOLUMES&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.cargo/git:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.cargo/git &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.cargo/registry:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.cargo/registry &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_WORKDIR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;--userns=keep-id --cap-drop=ALL --read-only&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;serial_extra_opts&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;--group-add=keep-groups --device=/dev/ttyACM0&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_cargo_run_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$BOX_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$serial_extra_opts&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_espflash_monitor_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$BOX_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$serial_extra_opts&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_esptool_MATCH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;esptool.py&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_esptool_IMAGE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;espressif/idf:release-v5.3&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_esptool_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$BOX_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$serial_extra_opts&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s a lot going on here so we&amp;rsquo;ll break it down by section:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_IMAGE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;esp&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First we set the image we&amp;rsquo;ll use to run our commands (in this case, the image I described above, built with the tag &lt;code&gt;esp&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;dotenv_if_exists&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;envs_from_dotenv&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;envs_from_dotenv&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_ENVS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;CARGO_HOME=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.cargo &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$envs_from_dotenv&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This section automatically loads a &lt;code&gt;.env&lt;/code&gt; file if it exists, and specifies some environment variables that should be set in the container. The &lt;code&gt;envs_from_dotenv&lt;/code&gt; is a &lt;a href=&#34;https://codeberg.org/galen/box-cli/src/branch/main/contrib/direnv/box.sh&#34;&gt;custom &lt;code&gt;direnv&lt;/code&gt; directive&lt;/a&gt; that will automatically export variables loaded from the &lt;code&gt;.env&lt;/code&gt; into the container; this is a great way to include secrets without having them be part of the &lt;code&gt;.envrc&lt;/code&gt; directly.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_VOLUMES&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.cargo/git:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.cargo/git &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.cargo/registry:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.cargo/registry &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_WORKDIR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PWD&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next we set some bind-mounts so the container has access to certain paths on the host. The first two entries mount our &lt;code&gt;cargo&lt;/code&gt; cache so we don&amp;rsquo;t need to redownload dependencies every time we run a command (this is also where the &lt;code&gt;CARGO_HOME&lt;/code&gt; environment variable comes into play). We also mount the current working directory to the same path in the container so &lt;code&gt;cargo&lt;/code&gt; can access our project, and set the container&amp;rsquo;s working directory correspondingly. Mounting the current directory to the same path in the container also means we can run &lt;code&gt;rust-analyzer&lt;/code&gt; inside the container and have jump-to-definition work correctly.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;--userns=keep-id --cap-drop=ALL --read-only&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;BOX_EXTRA_OPTS&lt;/code&gt; directive is an escape hatch for specifying any arbitrary Docker/Podman args that aren&amp;rsquo;t represented by another directive. In my case I want to make sure that the user in the container has the same uid as my host user, so the file permissions match up. The capability drop and read-only flags are an extra bit of security hardening.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;serial_extra_opts&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;--group-add=keep-groups --device=/dev/ttyACM0&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_cargo_run_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$BOX_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$serial_extra_opts&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_espflash_monitor_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$BOX_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$serial_extra_opts&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s very likely that not all tools will require the same resources; case in point, when we want to flash the firmware to an actual ESP, we&amp;rsquo;ll need access to the serial port from the host. While we could set these flags globally, they&amp;rsquo;re really only necessary for two commands: &lt;code&gt;cargo run&lt;/code&gt; (which builds and flashes the code) and &lt;code&gt;espflash monitor&lt;/code&gt; (which monitors the logs of the device over the serial port). &lt;code&gt;box&lt;/code&gt; allows us to specify overrides for specific commands so we can tailor-make the container with exactly the resources it needs. By using &lt;code&gt;BOX_cargo_run_EXTRA_OPTS&lt;/code&gt;, we will only set these additional parameters for the &lt;code&gt;cargo run&lt;/code&gt; command, but not for other commands (e.g. &lt;code&gt;cargo check&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_esptool_MATCH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;esptool.py&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_esptool_IMAGE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;espressif/idf:release-v5.3&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BOX_esptool_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$BOX_EXTRA_OPTS&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$serial_extra_opts&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The last block adds overrides for &lt;code&gt;esptool&lt;/code&gt;, which is a Python-based tool that has some additional functionality currently missing from &lt;code&gt;espflash&lt;/code&gt;. What&amp;rsquo;s neat here is that we can even override the image on a per-tool basis, so you don&amp;rsquo;t necessarily need to create one giant image with all the tooling. The &lt;code&gt;BOX_esptool_MATCH&lt;/code&gt; directive also shows off how to create overrides that are not space-delimited: it maps the command &lt;code&gt;esptool.py&lt;/code&gt; to the &lt;code&gt;esptool&lt;/code&gt; override; any directives with the same override will be applied.&lt;/p&gt;&#xA;&lt;p&gt;Finally, we need to create symlinks for the tools we want to run with &lt;code&gt;box&lt;/code&gt;. We can use the &lt;code&gt;box alias add &amp;lt;alias&amp;gt;...&lt;/code&gt; subcommand to automatically create them:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ box &lt;span class=&#34;nb&#34;&gt;alias&lt;/span&gt; add cargo espflash esptool rust-analyzer&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will create symlinks to &lt;code&gt;box&lt;/code&gt; in the &lt;code&gt;XDG_DATA_HOME/box&lt;/code&gt; directory (typically &lt;code&gt;~/.local/share/box&lt;/code&gt; on most Linux systems), but this can be customized using the &lt;code&gt;BOX_PATH&lt;/code&gt; directive. This path also needs to be added to your &lt;code&gt;PATH&lt;/code&gt;, e.g. in your &lt;code&gt;.profile&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.local/share/box:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PATH&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With all that in place, we can now run &lt;code&gt;cargo build&lt;/code&gt; normally in our terminal, but have it magically executed in a container, safely separated from our host system!&lt;/p&gt;&#xA;&lt;h2 id=&#34;try-it-out&#34;&gt;Try It Out!&lt;/h2&gt;&#xA;&lt;p&gt;You can find &lt;code&gt;box&lt;/code&gt;&amp;rsquo;s code on &lt;a href=&#34;https://codeberg.org/galen/box-cli&#34;&gt;Codeberg&lt;/a&gt;, along with additional documentation on all the available directives. If you use &lt;code&gt;direnv&lt;/code&gt; you&amp;rsquo;ll also want to check out the included &lt;a href=&#34;https://codeberg.org/galen/box-cli/src/branch/main/contrib/direnv/box.sh&#34;&gt;library script&lt;/a&gt; to make it even easier to integrate. Currently the easiest way to install &lt;code&gt;box&lt;/code&gt; is with &lt;code&gt;cargo install --git https://codeberg.org/galen/box-cli&lt;/code&gt; (or by cloning and &lt;code&gt;cargo build&lt;/code&gt;ing manually).&lt;/p&gt;&#xA;&lt;p&gt;Thanks for reading, and I look forward to hearing about your experience with &lt;code&gt;box&lt;/code&gt;!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Using KubeVirt with Cilium&#39;s kube-proxy Replacement Mode</title>
      <link>https://galenabell.com/2025/03/30/using-kubevirt-with-ciliums-kube-proxy-replacement-mode/</link>
      <pubDate>Sun, 30 Mar 2025 00:00:00 +0000</pubDate>
      <guid>https://galenabell.com/2025/03/30/using-kubevirt-with-ciliums-kube-proxy-replacement-mode/</guid>
      <description>&lt;p&gt;A few months back I set up a new Kubernetes cluster for self-hosting, and decided on &lt;a href=&#34;https://cilium.io/&#34;&gt;Cilium&lt;/a&gt; for the &lt;abbr title=&#34;Container Network Interface&#34;&gt;CNI&lt;/abbr&gt; implementation. Partly to learn more about Cilium (it&amp;rsquo;s a project with a rather large scope), but also because it has some neat features, including cluster-wide firewalling and a UI called Hubble for visualizing network connections within the cluster.&lt;/p&gt;&#xA;&lt;p&gt;So far it&amp;rsquo;s worked well, but I recently ran into an issue while experimenting with &lt;a href=&#34;https://kubevirt.io/&#34;&gt;KubeVirt&lt;/a&gt;. Cilium is installed as a replacement to kube-proxy, which depends on a feature called &amp;ldquo;socket-LB&amp;rdquo; designed to transparently bypass Kubernetes service-level NAT. Unfortunately, this feature breaks the cluster-level networking of KubeVirt virtual machines, so my VMs were able to access external IPs but not services within the cluster (including internal DNS or the public interface of exposed services). After some searching I was able to fix it with the following steps:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Enable the &lt;code&gt;hostNamespaceOnly&lt;/code&gt; option in Cilium; if you&amp;rsquo;re using the Helm chart, this can be enabled by setting the value:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;socketLB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;hostNamespaceOnly&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;re installing by another method (e.g. Kustomize), it seems this flag sets the option &lt;code&gt;bpf-lb-sock-hostns-only: &amp;quot;true&amp;quot;&lt;/code&gt; in the &lt;code&gt;cilium-config&lt;/code&gt; ConfigMap.&lt;/p&gt;&#xA;&lt;p&gt;This is rather hidden in their documentation about the kube-proxy replacement mode: &lt;a href=&#34;https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/#socket-loadbalancer-bypass-in-pod-namespace&#34;&gt;Socket LoadBalancer Bypass in Pod Namespace&lt;/a&gt;. I originally found the option mentioned in a &lt;a href=&#34;https://github.com/kubevirt/kubevirt/issues/10388&#34;&gt;GitHub issue&lt;/a&gt; about networking not working with KubeVirt.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Restart the Cilium pods!&lt;/strong&gt; I spent quite some time wondering why the networking was still broken after applying the fix; it turns out the config isn&amp;rsquo;t automatically reloaded until the Cilium pods are restarted. My assumption was that updating the Helm release would apply the change, but it seems to just update the ConfigMap. After Cilium is restarted you can verify that the config is updated by running &lt;code&gt;kubectl -n kube-system exec ds/cilium -- cilium-dbg status --verbose&lt;/code&gt;; under the &lt;code&gt;KubeProxyReplacement Details&lt;/code&gt; header you&amp;rsquo;ll see &lt;code&gt;Socket LB Coverage&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[...]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;KubeProxyReplacement Details:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Status:                 True&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Socket LB:              Enabled&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Socket LB Tracing:      Enabled&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Socket LB Coverage:     Hostns-only&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[...]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Since this feature is designed to bypass some additional networking steps, it&amp;rsquo;s likely that enabling the &lt;code&gt;hostNamespaceOnly&lt;/code&gt; option comes at some efficiency cost; however my cluster and use-case are small enough that I don&amp;rsquo;t expect it to have a noticeable impact.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>An Open Music Setup</title>
      <link>https://galenabell.com/2019/08/15/an-open-music-setup/</link>
      <pubDate>Thu, 15 Aug 2019 00:00:00 +0000</pubDate>
      <guid>https://galenabell.com/2019/08/15/an-open-music-setup/</guid>
      <description>&lt;h1 id=&#34;a-bit-of-background&#34;&gt;A Bit of Background&lt;/h1&gt;&#xA;&lt;p&gt;I have been belligerently reducing the number of online services I use, opting instead for open-source software that I can self-host or run locally. One service I wanted to replace was Spotify, which meant finding a proper alternative for managing my music.&lt;/p&gt;&#xA;&lt;p&gt;I have had issues with Spotify for a while&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, including unfair artist compensation and locking content behind &lt;a href=&#34;https://en.wikipedia.org/wiki/Digital_rights_management&#34;&gt;DRM&lt;/a&gt;, which forces users to keep paying for a subscription or risk losing their music library. You are also restricted to using Spotify&amp;rsquo;s client or a client backed by their API, rather than the huge number of music players that can play normal audio files. And since you can&amp;rsquo;t download music from Spotify in a non-encrypted format, you can even lose access to music completely if it is removed from the service.&lt;/p&gt;&#xA;&lt;p&gt;Switching to self-hosted / local software was the perfect opportunity to leave Spotify and start building an actual music library. Unfortunately, emulating the benefits of their service, such as cross-device synchronization and automatic library management, ended up being more trouble than I anticipated. In this post, I&amp;rsquo;ll detail some of my solutions to these problems as well as my current music setup, in case you too are looking for an alternative to online streaming services.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 id=&#34;content-management&#34;&gt;Content Management&lt;/h1&gt;&#xA;&lt;p&gt;The first difficulty with managing your own music library is ensuring that the audio files are properly labeled, sorted, and categorized. Fortunately, there are a number of tools available that make it easy to process your music.&lt;/p&gt;&#xA;&lt;h2 id=&#34;beets&#34;&gt;Beets&lt;/h2&gt;&#xA;&lt;p&gt;My library management tool of choice is an excellent command-line app called &lt;a href=&#34;http://beets.io/&#34;&gt;beets&lt;/a&gt;, which lets you easily tag and organize music using the &lt;a href=&#34;https://musicbrainz.org/&#34;&gt;MusicBrainz&lt;/a&gt; database, an &amp;ldquo;open music encyclopedia&amp;rdquo; with metadata for millions of songs. Beets requires some manual input to ensure it correctly identifies your music, but the payoff is a well-structured library that can be searched, sorted, and updated by just about any reasonable media player.&lt;/p&gt;&#xA;&lt;p&gt;Beets also supports extensions, which can add features such as automatically downloading and embedding art and lyrics. Check out the &lt;a href=&#34;https://beets.readthedocs.io/en/stable/&#34;&gt;documentation&lt;/a&gt; for the complete list, as well as instructions on how to get started.&lt;/p&gt;&#xA;&lt;h2 id=&#34;musicbrainz-picard&#34;&gt;MusicBrainz Picard&lt;/h2&gt;&#xA;&lt;p&gt;In case a command-line app isn&amp;rsquo;t your cup of tea, there is also a tool published by MusicBrainz themselves called &lt;a href=&#34;https://picard.musicbrainz.org/&#34;&gt;Picard&lt;/a&gt;. It features a GUI and, like beets, integrates directly with MusicBrainz to tag your music. I personally prefer beets&amp;rsquo; interface and customizability, but Picard also works well.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 id=&#34;cross-device-synchronization&#34;&gt;Cross-device Synchronization&lt;/h1&gt;&#xA;&lt;p&gt;The main reason online services are popular is because they are convenient, so I needed find a setup that could offer similar features. Specifically, I wanted my music to be available on all of my devices, as well as automatically synchronize new music and playlists. Here are a couple of tools that fit the bill.&lt;/p&gt;&#xA;&lt;h2 id=&#34;funkwhale&#34;&gt;Funkwhale&lt;/h2&gt;&#xA;&lt;p&gt;My initial solution to this problem was to set up my own &amp;ldquo;streaming service&amp;rdquo; using &lt;a href=&#34;https://funkwhale.audio/&#34;&gt;Funkwhale&lt;/a&gt;: a federated music sharing software built on top of the decentralized social networking protocol ActivityPub (the same protocol powering &lt;a href=&#34;https://joinmastodon.org/&#34;&gt;Mastodon&lt;/a&gt;). In addition to a web interface, Funkwhale exposes a Subsonic API endpoint&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, which allows integration with a number of existing clients. I switched away from Funkwhale since it offered a lot more functionality and complexity than I needed, but I still think it&amp;rsquo;s a cool project with a welcoming community.&lt;/p&gt;&#xA;&lt;h2 id=&#34;syncthing&#34;&gt;Syncthing&lt;/h2&gt;&#xA;&lt;p&gt;One of my favorite recently-discovered pieces of software is &lt;a href=&#34;https://syncthing.net/&#34;&gt;Syncthing&lt;/a&gt;: a peer-to-peer, decentralized file synchronization system. It allows you to register devices with one another, then share files and directories between them without any intermediary server. It sometimes takes a while to sync, but overall is a useful tool. I was already using it to sync phone backups to my server, and decided to try using it for music as well.&lt;/p&gt;&#xA;&lt;p&gt;The setup was very straightforward; I &amp;ldquo;paired&amp;rdquo; my phone and laptop, then added my music directory as a shared folder between the two. Aside from a small hiccup where my backup software tried to cyclically back up the music directory and gobbled all the space on my phone, the entire process went smoothly and I was left with a synchronized music library on both my devices.&lt;/p&gt;&#xA;&lt;p&gt;One nifty benefit of syncing the files directly between devices is that playlist management becomes very straightforward. Many music players support the &lt;a href=&#34;https://en.wikipedia.org/wiki/M3U&#34;&gt;m3u&lt;/a&gt; playlist format, which is basically just a list of audio file paths. One gotcha with this method is that the paths need to be the same on all devices, so using relative paths typically works better than absolute. I have my playlist files in the root of my music directory, and reference audio files with paths relative to that.&lt;/p&gt;&#xA;&lt;h2 id=&#34;rsync&#34;&gt;rsync&lt;/h2&gt;&#xA;&lt;p&gt;I feel I should at least give &lt;a href=&#34;https://en.wikipedia.org/wiki/Rsync&#34;&gt;rsync&lt;/a&gt; an honorable mention, since I use it on an almost daily basis for transferring files between servers. I considered setting up a recurring script to periodically rsync music between my devices, but I wanted a &amp;ldquo;smarter&amp;rdquo; solution that only synchronized when there were new items or changes.&lt;/p&gt;&#xA;&lt;p&gt;Additionally, it would be difficult to automatically rsync between a phone and laptop, since they would need to be on the same network. This could be solved by having a central music server that all devices sync against, but I prefer the reduced complexity of a decentralized setup.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 id=&#34;media-players&#34;&gt;Media Players&lt;/h1&gt;&#xA;&lt;p&gt;There are a &lt;strong&gt;lot&lt;/strong&gt; of media players out there. In fact, it wouldn&amp;rsquo;t surprise me if there are more media players than any other category of software. The great thing about having such a variety of players is that you can choose the one that best fits your use-cases. Whether you prefer a command-line tool for running in a terminal, a web-based player that&amp;rsquo;s accessible from anywhere, or a native desktop application, there are countless great options.&lt;/p&gt;&#xA;&lt;p&gt;In my opinion, client lock-in is one of the worst problems with using online streaming services, since it greatly restricts how you can interact with your music. You&amp;rsquo;ll typically get a single web interface, desktop application, phone app, and, if you&amp;rsquo;re lucky, a handful of third-party clients hacked together to work with the service&amp;rsquo;s API. The lock-in becomes especially egregious when the company behind the client randomly decides to introduce unasked-for changes (looking at you, Spotify), forcing you to choose to continue using an increasingly worse client, or leave the service and lose your music. Thankfully, we suffer none of these issues when using open file formats and clients.&lt;/p&gt;&#xA;&lt;h2 id=&#34;on-the-computer&#34;&gt;On the Computer&lt;/h2&gt;&#xA;&lt;p&gt;These days, most of my computer use is in a terminal session, so having a good command-line music player is a must. To that end, I am very happy with &lt;a href=&#34;https://rybczak.net/ncmpcpp/&#34;&gt;ncmpcpp&lt;/a&gt; backed by &lt;a href=&#34;https://www.musicpd.org/&#34;&gt;MPD&lt;/a&gt;. The setup is super simple; just point MPD to your media files and it&amp;rsquo;s off to the races. You can even do neat stuff like stream from MPD to remote devices&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. MPD supports a lot of different configurations, so check out their documentation to get a feel for how it can best work for you.&lt;/p&gt;&#xA;&lt;h2 id=&#34;for-my-phone&#34;&gt;For my Phone&lt;/h2&gt;&#xA;&lt;p&gt;Unfortunately, phones aren&amp;rsquo;t as well suited to command-line interfaces, so I needed to find a usable Android app for listening to music on the go. Almost any music player available for Android can play standard-format audio files and read m3u playlists, so I set out testing a bunch of players to see which one suited me best.&lt;/p&gt;&#xA;&lt;p&gt;A problem I quickly ran into is how Android handles media and playlists. In an attempt to prevent lots of repeated filesystem calls from locking up your phone, the Android system will automatically scan and index local audio, video, image and playlist files, and expose them to apps through an API. While the pros for the system being built in this way probably outweigh the cons&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;, it makes rolling your own media management difficult due to two large issues:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The only way for apps to access your media is through this API, so until new media is indexed, they don&amp;rsquo;t know it exists. There is also no reasonable way to force the system to index files, short of clearing the current index, restarting the phone, and waiting for the system to rebuild the index sometime thereafter&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Apps don&amp;rsquo;t handle playlist changes very well. Either as part of the their own internal storage system, or through the interaction with the media API, playlist changes that are updated from an external source (i.e. Syncthing) don&amp;rsquo;t get picked up quickly (or ever) by the app, and changes to the playlists within the app need to be manually exported back to the filesystem, which often breaks the path references for other devices.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I spent a good deal of time researching workarounds for these issues when I had a thought: perhaps I could just mimic my desktop setup, and use MPD? As it turns out, running MPD as a local media server on my phone works great, especially since there are a number of quality open-source apps for streaming music from MPD. My current favorite is &lt;a href=&#34;https://gitlab.com/gateship-one/malp&#34;&gt;M.A.L.P.&lt;/a&gt;, which offers a nice UI and seamless MPD integration (plus the bonus of being able to connect to and control remote instances, like your computer).&lt;/p&gt;&#xA;&lt;p&gt;For the MPD daemon, I originally used &lt;a href=&#34;https://termux.com/&#34;&gt;Termux&lt;/a&gt; (an app that provides a terminal emulator and faux Linux environment) with the MPD package. This setup is usable, although I have since switched to MPD&amp;rsquo;s official Android implementation which I find to be more convenient.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 id=&#34;closing-thoughts&#34;&gt;Closing Thoughts&lt;/h1&gt;&#xA;&lt;p&gt;This post ended up being a lot longer than I anticipated, but hopefully it&amp;rsquo;s given you an idea of how you can set up your own media management system without relying on an online service. I set out to meet fairly specific goals which added some complexity, but parts of it could certainly be pared down or adapted to fulfill a different use-case.&lt;/p&gt;&#xA;&lt;p&gt;There are a few kinks still left to sort out, including:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Having to store my entire library on all devices is inefficient, especially if I am only listening to a certain playlist at a time. For now, my library is small enough that it&amp;rsquo;s not an issue, but in the future I might need to start filtering which songs get synchronized.&lt;/li&gt;&#xA;&lt;li&gt;The local-MPD phone instance is a bit of a hack, and M.A.L.P. has some quirks when connecting to a local server, including one related to volume control. I have &lt;a href=&#34;https://gitlab.com/gateship-one/malp/issues/182&#34;&gt;an issue&lt;/a&gt; open to try and resolve this, so hopefully this won&amp;rsquo;t be a problem in the future!&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Overall, I am super pleased with this setup, and enjoy the amount of control and customizability it has given me over my music. If you are also becoming disenchanted with Spotify or other online streaming services, then I encourage you to take it out for a spin! And if you come up with your own system or find a way to improve mine, I would love to hear about it.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;These criticisms apply to most, if not all, online streaming platforms.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;Subsonic is another music server software that ended up going closed-source, although many successive projects borrow its API design.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;I did consider streaming music from a centralized MPD instance as an enabler for cross-device synchronization. Unfortunately, this isn&amp;rsquo;t an ideal option for listening to music on a phone, since MPD doesn&amp;rsquo;t directly support caching, and having to stream music live every time will quickly eat mobile data.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;It&amp;rsquo;s worth noting that mobile OS&amp;rsquo;s with proper file systems (like Sailfish OS) don&amp;rsquo;t suffer either of these issues, and basically Just Work™ as you&amp;rsquo;d expect them to.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;Technically, using Android&amp;rsquo;s MTP (Media Transfer Protocol) functionality to transfer media should cause an immediate reindex, but it only works over USB.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Random Certificate Serials in FreeIPA</title>
      <link>https://galenabell.com/2018/10/23/random-certificate-serials-in-freeipa/</link>
      <pubDate>Tue, 23 Oct 2018 00:00:00 +0000</pubDate>
      <guid>https://galenabell.com/2018/10/23/random-certificate-serials-in-freeipa/</guid>
      <description>&lt;h1 id=&#34;intro&#34;&gt;Intro&lt;/h1&gt;&#xA;&lt;p&gt;I recently broke my FreeIPA setup, and needed to reinstall from scratch. Since FreeIPA by default creates certificates with serial numbers starting at 1, I ended up with a Certificate Authority with the same serial number and fields as my previous installation, but with a different certificate. Unfortunately, this caused a &lt;code&gt;sec_error_reused_issuer_and_serial&lt;/code&gt; error in Firefox, which I couldn&amp;rsquo;t bypass. I tried a number of different solutions to try and fix this issue, including clearing out the certificate database as well as resetting my Firefox profile, but none of them worked.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 id=&#34;solution&#34;&gt;Solution&lt;/h1&gt;&#xA;&lt;p&gt;In the end, I was able to work around the Firefox error by enabling random certificate serial numbers in FreeIPA and reinstalling. If random serials are enabled, FreeIPA will use a random, nonsequential number for the certificate serial number rather than starting at 1. If this is enabled before FreeIPA is installed, the Certificate Authority will also be generated with a random serial, which prevents Firefox from throwing an error.&lt;/p&gt;&#xA;&lt;p&gt;To enable random certificate serial numbers, we need to modify the Dogtag PKI configuration. The Dogtag system is used by FreeIPA to create the integrated Certificate Authority and provision certificates. Before installing FreeIPA, edit the &lt;code&gt;/etc/pki/default.cfg&lt;/code&gt; file on your IPA server and find the following line:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;pki_random_serial_numbers_enabled&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;False&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Change it to:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;pki_random_serial_numbers_enabled&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;True&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Save and close the file. You can now complete the FreeIPA installation as per usual; after it finishes installing, we can check the list of certificates in the WebUI under &lt;code&gt;Authentication -&amp;gt; Certificates&lt;/code&gt;, and we will see that the certificate serial numbers are all random and nonsequential. Firefox should also now load pages signed by the FreeIPA Certificate Authority without complaining about reused serial numbers.&lt;/p&gt;&#xA;</description>
    </item>
  </channel>
</rss>
