<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Mosquitto on ln --help</title>
    <link>https://blog.mei-home.net/tags/mosquitto/</link>
    <description>Recent content in Mosquitto on ln --help</description>
    <generator>Hugo -- 0.147.2</generator>
    <language>en</language>
    <lastBuildDate>Sun, 01 Mar 2026 21:27:37 +0100</lastBuildDate>
    <atom:link href="https://blog.mei-home.net/tags/mosquitto/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Mosquitto: Update to v2.1</title>
      <link>https://blog.mei-home.net/posts/mosquitto-2-1-update/</link>
      <pubDate>Sun, 01 Mar 2026 21:27:37 +0100</pubDate>
      <guid>https://blog.mei-home.net/posts/mosquitto-2-1-update/</guid>
      <description>It needs a few changes...</description>
      <content:encoded><![CDATA[<p>As part of this weekend&rsquo;s regular service update, I also came across
Mosquitto&rsquo;s new 2.1.2 release. This is my tale&hellip;</p>
<p>I&rsquo;m using <a href="https://mosquitto.org/">Mosquitto</a> as the MQTT broker for my IoT
thermostats and smart plugs. If you&rsquo;re interested, you can find more details on
my setup in <a href="https://blog.mei-home.net/posts/power-measurement/">this</a> and <a href="https://blog.mei-home.net/posts/k8s-migration-17-iot/">this</a>
post.</p>
<p>The <a href="https://mosquitto.org/ChangeLog.txt">changelog</a> of the new release contained
a few interesting points:</p>
<ul>
<li>The acl_file option is deprecated in favour of the acl-file plugin, which is
the same code but moved into a plugin. The acl_file option will be removed
in 3.0.</li>
<li>The password_file option is deprecated in favour of the password-file plugin,
which is the same code but moved into a plugin. The password_file option will
be removed in 3.0.</li>
</ul>
<p>I&rsquo;m using both of these options, so because I was doing the update on a lazy
Sunday morning instead of Friday evening after work, I decided to be a good
sysadmin and replace the <code>acl_file</code> and <code>password_file</code> options now, instead of
waiting for the update where they&rsquo;re ultimately getting removed.</p>
<p>The first hurdle was that there doesn&rsquo;t seem to be any good docs on how to use
either the <code>password-file</code> or the <code>acl-file</code> plugins. How do I configure them?
How do I use them? How do I even get them?</p>
<p><strong>Update 2026-03-02</strong>: It turns out that I just wasn&rsquo;t looking properly. The docs
for both, the <a href="https://mosquitto.org/documentation/plugins/acl-file/">ACL file plugin</a>
and the <a href="https://mosquitto.org/documentation/plugins/password-file/">Password file plugin</a>
are right there on the <a href="https://mosquitto.org/documentation/">main docs page</a>.
Thanks a lot to <a href="https://fosstodon.org/@ralight">@ralight@fosstodon.org</a> for pointing out
my mistake.</p>
<p>After not having any success in finding any examples, I finally hit upon an idea:
Look at the source code. Yet again, three Hurrah! for open source software. I
found <a href="https://github.com/eclipse-mosquitto/mosquitto/commit/c522361d359713c4648a92eadfe3312dfa0e4a3e">this commit</a>,
and in it this example Mosquitto config for internal testing:</p>
<pre tabindex="0"><code>listener 1883
allow_anonymous true
plugin ./mosquitto_acl_file.so
plugin_opt_acl_file ./acl_file
</code></pre><p>So first step: Figuring out whether those shared objects are actually delivered
as part of the image.
I had a look into the Mosquitto container and found that at lest those two
libs are delivered as part of the image:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>podman run -it eclipse-mosquitto:2.1.2-alpine ash
</span></span><span style="display:flex;"><span>find . -iname <span style="color:#e6db74">&#34;*.so&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>...<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>./usr/lib/mosquitto_password_file.so
</span></span><span style="display:flex;"><span>./usr/lib/mosquitto_acl_file.so
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>...<span style="color:#f92672">]</span>
</span></span></code></pre></div><p>I ended up with this final config:</p>
<pre tabindex="0"><code>plugin /usr/lib/mosquitto_acl_file.so
plugin_opt_acl_file /mosquitto/config/acl.conf
plugin /usr/lib/mosquitto_password_file.so
plugin_opt_password_file /hl/passwd
</code></pre><p>But I kept getting an error:</p>
<pre tabindex="0"><code>1772364168: Error: Unable to open acl_file &#34;/mosquitto/config/acl.conf&#34;.
1772364168: Error: Plugin returned 13 when initialising.
</code></pre><p>The <code>acl.conf</code> file is mapped into the container via a k8s ConfigMap, so I thought
that perhaps there&rsquo;s something going wrong here?
I checked in a running Pod and saw this:</p>
<pre tabindex="0"><code>/ # ls -Al mosquitto/
total 16
drwxrwsrwx    3 root     1000          4096 Mar  1 11:26 config
drwxrwsr-x    3 mosquitto 1000          4096 Mar  1 11:25 data
drwxr-xr-x    2 mosquitto mosquitto      4096 Feb  9 20:01 log

/ # ls -Al mosquitto/config/
total 4
drwxr-sr-x    2 root     1000          4096 Mar  1 11:26 ..2026_03_01_11_26_50.4252382031
lrwxrwxrwx    1 root     1000            32 Mar  1 11:26 ..data -&gt; ..2026_03_01_11_26_50.4252382031
lrwxrwxrwx    1 root     1000            15 Mar  1 11:26 acl.conf -&gt; ..data/acl.conf
lrwxrwxrwx    1 root     1000            21 Mar  1 11:26 mosquitto.conf -&gt; ..data/mosquitto.conf
</code></pre><p>So I didn&rsquo;t see any issue, the files definitely existed.
After digging even more, I finally found <a href="https://github.com/eclipse-mosquitto/mosquitto/issues/3531">this GitHub issue</a>.
Somebody had the same issue as me. It looks like it was created by some sort of
security measure, disabling following of symlinks by default. After setting the
env variable to disable that behavior, <code>MOSQUITTO_UNSAFE_ALLOW_SYMLINKS=1</code>,
Mosquitto finally started up again and has been running nicely since then.</p>
<p>So be a bit cautious when running Mosquitto in a k8s cluster, the update to
v2.1.2 might not work without some small changes.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Nomad to k8s, Part 17: Migrating my IoT Services</title>
      <link>https://blog.mei-home.net/posts/k8s-migration-17-iot/</link>
      <pubDate>Sat, 15 Feb 2025 12:09:12 +0100</pubDate>
      <guid>https://blog.mei-home.net/posts/k8s-migration-17-iot/</guid>
      <description>Migrating Mosquitto, mqtt2prometheus and zigbee2mqtt</description>
      <content:encoded><![CDATA[<p>Wherein I migrate several IoT services over to Kubernetes.</p>
<p>This is part 18 of my <a href="https://blog.mei-home.net/tags/k8s-migration/">k8s migration series</a>.</p>
<p>This is going to be a short one. This weekend, I finished the migration of
several IoT related services to k8s.
<a href="https://mosquitto.org/">Mosquitto</a> is my MQTT broker, handling messages from
several sources. For me, it&rsquo;s only a listener - I do not have any actual home
automations.
Said mosquitto instance is scraped by <a href="https://github.com/hikhvar/mqtt2prometheus">mqtt2prometheus</a>
to get the data my smart plugs and thermometers produce into my Prometheus
instance.
Finally, I also migrated my <a href="https://www.zigbee2mqtt.io/">Zigbee2MQTT</a> instance
over to the k8s cluster. It controls my Zigbee transceiver and sends the data
from my thermometers on to mosquitto.</p>
<p>If you&rsquo;d like some more details on the power plug data gathering setup, have
a look <a href="https://blog.mei-home.net/posts/power-measurement/">here</a>.
The post on my thermometer setup is still on the large pile of blog posts I&rsquo;d
like to write at some point.</p>
<p>This will be a short(er) post, as I want to only talk about a couple of issues
I encountered along the way.</p>
<h2 id="selfmade-helm-chart">Selfmade Helm chart</h2>
<p>I decided to write my own Helm chart for these tools and manage them all in the
same namespace. Just makes the setup a bit simpler, as they don&rsquo;t really need to
talk to many other services, none of the apps needs a database for example.</p>
<p>So what does a Helm chart look like when I write it myself?
The <code>Chart.yaml</code> is kept extremely simple:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v2</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">name</span>: <span style="color:#ae81ff">iot</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">description</span>: <span style="color:#ae81ff">The Homelab IoT services</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">type</span>: <span style="color:#ae81ff">application</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">version</span>: <span style="color:#ae81ff">0.1.0</span>
</span></span></code></pre></div><p>I don&rsquo;t need anything more. I don&rsquo;t even bother to change the Chart&rsquo;s version
when I change things.</p>
<p>The <code>values.yaml</code> file is also pretty sparse. I mostly use it for cases where
I need a value in multiple places:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">commonLabels</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">homelab/part-of</span>: <span style="color:#ae81ff">iot</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">mosquitto</span>: <span style="color:#e6db74">&#34;1883&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">pwr</span>: <span style="color:#e6db74">&#34;9641&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">temp</span>: <span style="color:#e6db74">&#34;9642&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">z2m</span>: <span style="color:#e6db74">&#34;8080&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">mqttHost</span>: <span style="color:#ae81ff">mqtt.example.com</span>
</span></span></code></pre></div><p>And that&rsquo;s it already.</p>
<h2 id="mosquitto">Mosquitto</h2>
<p>As I said, I won&rsquo;t detail every single manifest here. But one interesting part
was that MQTT isn&rsquo;t HTTP, it&rsquo;s a purely TCP based protocol. But I&rsquo;m still using
Ingress mechanisms, because Traefik does support TCP routes. In k8s, these are
configured with the <a href="https://doc.traefik.io/traefik/routing/providers/kubernetes-crd/#kind-ingressroutetcp">IngressRouteTCP</a> CRD.
Using such a router config, some things are not available. E.g. if you don&rsquo;t
configure TLS, you cannot do host-based routing. The connection simply doesn&rsquo;t
tell you what host it connected to. So when you want to use unencrypted TCP (or UDP),
your have to create a separate Traefik entrypoint with its own port just for this
route.
Here&rsquo;s the route manifest:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">traefik.io/v1alpha1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">IngressRouteTCP</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">mosquitto</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">annotations</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">external-dns.alpha.kubernetes.io/hostname</span>: <span style="color:#e6db74">&#34;{{ .Values.mqttHost }}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">external-dns.alpha.kubernetes.io/target</span>: <span style="color:#e6db74">&#34;ingress.example.com&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">entryPoints</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">mqtt</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">routes</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">match</span>: <span style="color:#ae81ff">HostSNI(`*`)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">mosquitto</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Service</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">port</span>: <span style="color:#ae81ff">1883</span>
</span></span></code></pre></div><p>This connects Traefik&rsquo;s 1883 port to mosquitto&rsquo;s Service. All connections
arriving on the mqtt entrypoint will be forwarded to mosquitto.</p>
<p>If you do require TLS, Traefik can make use of the <a href="https://de.wikipedia.org/wiki/Server_Name_Indication">Server Name Indication</a>,
via the <code>HostSNI</code> setting. But SNI is an extension to TLS, so not all software
implementing TLS will support it.
When TLS is enabled, you can even run pure TLS connections over the same port
Traefik is using for HTTPS.
An IngressRouteTCP would look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">traefik.io/v1alpha1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">IngressRouteTCP</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">mosquitto-tls</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">entryPoints</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">websecure</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">routes</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">match</span>: <span style="color:#ae81ff">HostSNI(`{{ .Values.mqttHost }}`)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">mosquitto</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Service</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">port</span>: <span style="color:#ae81ff">1883</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">tls</span>: {}
</span></span></code></pre></div><p>Here, the <code>websecure</code> entrypoint is my standard HTTPS entrypoint. This still works
as expected though, even for pure TLS connections, by using the SNI and
forwarding connections arriving for mqtt.example.com to mosquitto.
The <code>tls</code> key at the end is important, even though it is empty. This tells
Traefik to enable TLS with its default configuration, which uses my wildcard
cert.</p>
<p>The most interesting part of the mosquitto setup was the creation of users.
It uses a passwd-like file format, and I got &ldquo;creative&rdquo; when setting up the
Nomad job.
All of the users (admin user, scrapers, Zigbee2MQTT, my smart plugs) are in a
directory in Vault, looking like this:</p>
<pre tabindex="0"><code>my-secrets/iot/mqtt/users/username1
my-secrets/iot/mqtt/users/username2
[...]
</code></pre><p>Then each of those only has a single key, <code>secret</code>, which contains the user&rsquo;s
password, already encrypted with <a href="https://mosquitto.org/man/mosquitto_passwd-1.html">mosquitto_passwd</a>.
The problem now is: How to get all of those into a single passwd file for
mosquitto to use?
The resulting file should look something like this:</p>
<pre tabindex="0"><code>user1:$7$foo_encrypted==

user2:$7$bar_encrypted==
</code></pre><p>It turns out that <a href="https://external-secrets.io/latest/">external-secrets</a> has a
pretty good <a href="https://external-secrets.io/latest/guides/templating/">templating engine</a>,
so I was actually able to do this. The finished ExternalSecret looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">external-secrets.io/v1beta1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ExternalSecret</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">mosquitto-users</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">refreshInterval</span>: <span style="color:#e6db74">&#34;1m&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">secretStoreRef</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">my-secrets</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ClusterSecretStore</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">target</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">passwd</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">passwd</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {{ `{{ range $name, $pass := . }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {{ $name }}:{{ with $pass | fromJson }}{{ .secret }}{{ end }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          {{ end }}` }}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">dataFrom</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">find</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">path</span>: <span style="color:#ae81ff">my-secrets/iot/mqtt/users</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">name</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">regexp</span>: <span style="color:#e6db74">&#34;.*&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">rewrite</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">regexp</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">source</span>: <span style="color:#e6db74">&#34;my-secrets/iot/mqtt/users/(.*)&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">target</span>: <span style="color:#e6db74">&#34;$1&#34;</span>
</span></span></code></pre></div><p>Let&rsquo;s start with the data fetching in <code>dataFrom</code>. It returns all secrets
below the <code>users/</code> path and returns them in a map, akin to this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">resultMap</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">my-secrets/iot/mqtt/users/username1</span>: {<span style="color:#f92672">&#34;secret&#34;: </span><span style="color:#e6db74">&#34;foo&#34;</span>}
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">my-secrets/iot/mqtt/users/username2</span>: {<span style="color:#f92672">&#34;secret&#34;: </span><span style="color:#e6db74">&#34;bar&#34;</span>}
</span></span></code></pre></div><p>This is a bit unfortunate, because to get the right format, I need the username
as well. That&rsquo;s what the <code>rewrite:</code> object gives me. It does a regex match on
the whole path and gives me back only the last element, which is the username.
Then the template itself just iterates over the map and brings out the
username and password in the right format.</p>
<p>I&rsquo;m repeatedly impressed how many tight situations external-secrets has helped
me out of already. After some fiddling, this is a good enough result.</p>
<p>One thing I found rather unfortunate though: There&rsquo;s no way of defining the
owner of a Secret mapped into a pod as a volume. This means that the passwd file
sits in the container world readable. Not great. The only potential solution I
found was the introduction of an init container, to run chmod on the file.
I skipped that for now, but will have to take care about it at some point,
because mosquitto already complains about the fact that the passwd file is
world readable, noting that a setup like that will be rejected in the future.</p>
<h2 id="scraping-mqtt-data-with-prometheus">Scraping MQTT data with Prometheus</h2>
<p>I like and greatly enjoy my Prometheus data. I like looking at all of the plots
in Grafana. There&rsquo;s a reason it gets to occupy 200 GB of disk space.
So I need to get my MQTT data, meaning power consumption from the smart plugs
and thermal data from the thermometers, into Prometheus.
For this, I&rsquo;m using <a href="https://github.com/hikhvar/mqtt2prometheus">mqtt2prometheus</a>.
I&rsquo;ve currently got two instances running, one for my power plugs&rsquo; energy measurement
and one for my thermometers&rsquo; temperature and humidity. I put both of them into
one Pod, because having separate Pods for each of them seemed unnecessary.</p>
<p>The configuration of the power measurements exporter looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ConfigMap</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pwr-exporter</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>    {{- <span style="color:#ae81ff">range $label, $value := .Values.commonLabels }}</span>
</span></span><span style="display:flex;"><span>    {{ <span style="color:#f92672">$label }}</span>: {{ <span style="color:#ae81ff">$value | quote }}</span>
</span></span><span style="display:flex;"><span>    {{- <span style="color:#ae81ff">end }}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">config.yaml</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    mqtt:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      server: tcp://{{ .Values.mqttHost }}:{{ .Values.ports.mosquitto }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      user: promexport
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      client_id: pwr-exporter
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      topic_path: &#34;plugs/tasmota/tele/#&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      device_id_regex: &#34;plugs/tasmota/tele/(?P&lt;deviceid&gt;.*)/.*&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    metrics:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_total_power_kwh
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: ENERGY.Total
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Total power consumption (kWh)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: counter
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_power
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: ENERGY.Power
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Current consumption (W)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: gauge
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_current
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: ENERGY.ApparentPower
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Current (A)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: gauge
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_yesterday_pwr
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: ENERGY.Yesterday
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Yesterdays Total Power Consumption (kWh)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: counter
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_today_pwr
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: ENERGY.Today
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Todays Total Power Consumption (kWh)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: counter</span>
</span></span></code></pre></div><p>And the one for the thermometers looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ConfigMap</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">temp-exporter</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>    {{- <span style="color:#ae81ff">range $label, $value := .Values.commonLabels }}</span>
</span></span><span style="display:flex;"><span>    {{ <span style="color:#f92672">$label }}</span>: {{ <span style="color:#ae81ff">$value | quote }}</span>
</span></span><span style="display:flex;"><span>    {{- <span style="color:#ae81ff">end }}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">config.yaml</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    mqtt:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      server: tcp://{{ .Values.mqttHost }}:{{ .Values.ports.mosquitto }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      user: promexport
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      client_id: temp-exporter
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      topic_path: &#34;zigbee2mqtt/temp/sonoff/#&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      device_id_regex: &#34;zigbee2mqtt/temp/sonoff/(?P&lt;deviceid&gt;.*)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    cache:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      timeout: 24h
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    metrics:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_temp_battery_percent
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: battery
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Current battery percentage (percent)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: gauge
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        omit_timestamp: true
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_temp_humidity
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: humidity
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Current humidity (percent)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: gauge
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        omit_timestamp: true
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      - prom_name: mqtt_temp_temperature
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        mqtt_name: temperature
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        help: &#34;Current temperature (C)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        type: gauge
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        omit_timestamp: true</span>
</span></span></code></pre></div><p>The configurations mostly consist of the translation of MQTT topics to Prometheus
metrics.</p>
<p>Here&rsquo;s the deployment for the Pod:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">apps/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Deployment</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">exporters</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">replicas</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">exporters</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">strategy</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">type</span>: <span style="color:#e6db74">&#34;Recreate&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">exporters</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">annotations</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">checksum/pwr-config</span>: {{ <span style="color:#ae81ff">include (print $.Template.BasePath &#34;/pwr-exp-conf.yaml&#34;) . | sha256sum }}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">checksum/temp-config</span>: {{ <span style="color:#ae81ff">include (print $.Template.BasePath &#34;/temp-exp-conf.yaml&#34;) . | sha256sum }}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pwr-exporter</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ghcr.io/hikhvar/mqtt2prometheus:{{ .Values.mqtt2promVersion }}</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">args</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-config&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;/etc/mqtt2prom/config.yaml&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-listen-port&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;{{ .Values.ports.pwr }}&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-log-format&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;json&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config-pwr</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/etc/mqtt2prom</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">readOnly</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">MQTT2PROM_MQTT_USER</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;promexport&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">envFrom</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">secretRef</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">exporter-mosquitto-user</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">optional</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pwr-exporter</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">containerPort</span>: {{ <span style="color:#ae81ff">.Values.ports.pwr }}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">protocol</span>: <span style="color:#ae81ff">TCP</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">temp-exporter</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">ghcr.io/hikhvar/mqtt2prometheus:{{ .Values.mqtt2promVersion }}</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">args</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-config&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;/etc/mqtt2prom/config.yaml&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-listen-port&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;{{ .Values.ports.temp }}&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;-log-format&#34;</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#e6db74">&#34;json&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config-temp</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/etc/mqtt2prom</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">readOnly</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">MQTT2PROM_MQTT_USER</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;promexport&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">envFrom</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">secretRef</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">exporter-mosquitto-user</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">optional</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">temp-exporter</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">containerPort</span>: {{ <span style="color:#ae81ff">.Values.ports.temp }}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">protocol</span>: <span style="color:#ae81ff">TCP</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config-pwr</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">configMap</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pwr-exporter</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config-temp</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">configMap</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">name</span>: <span style="color:#ae81ff">temp-exporter</span>
</span></span></code></pre></div><p>I have again cut out some unimportant pieces. Luckily, mqtt2prometheus supports
providing the credentials for MQTT access via environment variables, so I didn&rsquo;t
have to template the entire configuration file to avoid putting the credentials
into git.</p>
<p>Finally, I had to also set up the network policy to allow my Prometheus deployment
access to the Pod and its ports for scraping:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#e6db74">&#34;cilium.io/v2&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">CiliumNetworkPolicy</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;exporters&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">endpointSelector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">exporters</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ingress</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">fromEndpoints</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">io.kubernetes.pod.namespace</span>: <span style="color:#ae81ff">monitoring</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">app.kubernetes.io/name</span>: <span style="color:#ae81ff">prometheus</span>
</span></span></code></pre></div><h2 id="the-zigbee-manager">The Zigbee manager</h2>
<p>My thermometers are connected via Zigbee, so I needed some way to transform the
data to MQTT and send it to my mosquitto instance. I don&rsquo;t use HomeAssistant,
because it looks very much like overkill. I don&rsquo;t actually control anything,
I just want to gather a bit of data.
I&rsquo;m using <a href="https://www.zigbee2mqtt.io/">Zigbee2MQTT</a> for this. I&rsquo;m using a
Zigbee transceiver connected via LAN, so I didn&rsquo;t have to muck about with
mounting a USB device into the Pod.
Again, zigbee2mqtt is a good piece of software because it allows me to set some
config keys, those containing secrets, via environment variables but also allows
me to provide the non-secret config options in the configuration file.
Zigbee2MQTT requires three secrets:</p>
<ol>
<li>The MQTT credentials for access to mosquitto</li>
<li>An auth token for access to the web UI</li>
<li>A network key</li>
</ol>
<p>I&rsquo;m providing all three from my Vault instance in an ExternalSecret again:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">external-secrets.io/v1beta1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ExternalSecret</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">refreshInterval</span>: <span style="color:#e6db74">&#34;1m&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">secretStoreRef</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">my-secrets</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ClusterSecretStore</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">target</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">ZIGBEE2MQTT_CONFIG_FRONTEND_AUTH_TOKEN</span>: <span style="color:#e6db74">&#34;{{ `{{ .auth }}` }}&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">ZIGBEE2MQTT_CONFIG_MQTT_PASSWORD</span>: <span style="color:#e6db74">&#34;{{ `{{ .mqtt }}` }}&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">ZIGBEE2MQTT_CONFIG_ADVANCED_NETWORK_KEY</span>: <span style="color:#e6db74">&#34;[{{ `{{ .network }}` }}]&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">secretKey</span>: <span style="color:#ae81ff">auth</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">remoteRef</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">key</span>: <span style="color:#ae81ff">my-secrets/iot/zigbee2mqtt/auth</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">property</span>: <span style="color:#ae81ff">secret</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">secretKey</span>: <span style="color:#ae81ff">mqtt</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">remoteRef</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">key</span>: <span style="color:#ae81ff">my-secrets/iot/zigbee2mqtt/mqtt</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">property</span>: <span style="color:#ae81ff">secret</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">secretKey</span>: <span style="color:#ae81ff">network</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">remoteRef</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">key</span>: <span style="color:#ae81ff">my-secrets/iot/zigbee2mqtt/network-key</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">property</span>: <span style="color:#ae81ff">secret</span>
</span></span></code></pre></div><p>The complicated part of the Zigbee2MQTT deployment is the configuration file.
Because sadly, Zigbee2MQTT is one of those applications that need write access
to their configuration file. Which makes usage of a ConfigMap complicated,
because those are always mounted read-only. In the case of Zigbee2MQTT, I don&rsquo;t
really care about the content changes it makes, I can just deploy my original
file over it without an issue. But Zigbee2MQTT won&rsquo;t even start if it can&rsquo;t
write to the config file.</p>
<p>First, the config map itself:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">ConfigMap</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">data</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">configuration.yaml</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    version: 4
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    homeassistant:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      enabled: false
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    permit_join: false
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    frontend:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      enabled: true
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    mqtt:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      base_topic: zigbee2mqtt
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      server: &#39;mqtts://{{ .Values.mqttHost }}:443&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      user: foo
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      client_id: &#34;foobar&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    # Serial settings
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    serial:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      port: &#39;tcp://my-zigbee-bridge:1234&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      adapter: zstack
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    advanced:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      channel: 23
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      log_output:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        - console
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    devices:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      &#39;0x123&#39;:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        friendly_name: &#39;temp/sonoff/thermo1&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        icon: device_icons/bdc2692122548ad0f2b0fb6c9f10a93d.png
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      &#39;0x456&#39;:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        friendly_name: &#39;temp/sonoff/thermo2&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        icon: device_icons/bdc2692122548ad0f2b0fb6c9f10a93d.png</span>
</span></span></code></pre></div><p>When new devices are connected, Zigbee2MQTT adds them to the <code>devices:</code> map, and
I then just add them to the ConfigMap manually.</p>
<p>But how to handle the fact that this config file needs to be writable?
Init containers. Because up to now, I&rsquo;ve been living in blissful ignorance of
such hacks, but that streak of good fortune had to end at some point. I just
find it so incredibly ugly. Look at it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">apps/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Deployment</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">replicas</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">strategy</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">type</span>: <span style="color:#e6db74">&#34;Recreate&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">homelab/app</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">annotations</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">checksum/config</span>: {{ <span style="color:#ae81ff">include (print $.Template.BasePath &#34;/z2m-config.yaml&#34;) . | sha256sum }}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">securityContext</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">fsGroup</span>: <span style="color:#ae81ff">1000</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">initContainers</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt-init</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">alpine:3.21.2</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">data</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/data</span>
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/config</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">command</span>: [<span style="color:#e6db74">&#34;cp&#34;</span>, <span style="color:#e6db74">&#34;/config/configuration.yaml&#34;</span>, <span style="color:#e6db74">&#34;/data/configuration.yaml&#34;</span>]
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">image</span>: <span style="color:#ae81ff">koenkk/zigbee2mqtt:{{ .Values.zigbee2mqttVersion }}</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">volumeMounts</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">data</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">mountPath</span>: <span style="color:#ae81ff">/app/data</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">resources</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">requests</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">cpu</span>: <span style="color:#ae81ff">200m</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">memory</span>: <span style="color:#ae81ff">200Mi</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">envFrom</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">secretRef</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">optional</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">web</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">containerPort</span>: {{ <span style="color:#ae81ff">.Values.ports.z2m }}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">protocol</span>: <span style="color:#ae81ff">TCP</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">data</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">persistentVolumeClaim</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">claimName</span>: <span style="color:#ae81ff">z2m-volume</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">config</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">configMap</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">name</span>: <span style="color:#ae81ff">zigbee2mqtt</span>
</span></span></code></pre></div><p>I&rsquo;m launching an entire other container just to run a single <code>cp</code> command to
copy the mounted ConfigMap into the data volume. I wish we had some better way
to do something like this. But it seems we don&rsquo;t.</p>
<p>And that&rsquo;s it for this one. I think wherever possible, I will keep the future
migration posts in this format, not explaining every single line of every single
Yaml file anymore, but only pointing out interesting things like the issue
with the mosquitto credentials in this one. It&rsquo;s more interesting to write and
I hope more interesting to read than the umpteenth re-explanation of my CNPG
DB setup.</p>
<p>Next up will be my Jellyfin media server. The copying of my media collection
is already done, and hopefully I will get the actual migration completed today.
That one will contain a lot of Grafana plots and Ceph performance musings. &#x1f913;</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
