<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mike Almeloo]]></title><description><![CDATA[I find complex solutions to increasingly niche problems!]]></description><link>https://blog.mikealmel.ooo</link><generator>RSS for Node</generator><lastBuildDate>Mon, 11 May 2026 18:08:14 GMT</lastBuildDate><atom:link href="https://blog.mikealmel.ooo/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Effortless Remote Zigbee with SMHUB Nano Mg24]]></title><description><![CDATA[I LOVE smart home devices. Pretty much the exact moment I moved out of my parents’ house to attend university, I equipped my place with a bunch of smart devices to hook into my existing Home Assistant instance. My bedroom alone has multiple smart Zig...]]></description><link>https://blog.mikealmel.ooo/effortless-remote-zigbee</link><guid isPermaLink="true">https://blog.mikealmel.ooo/effortless-remote-zigbee</guid><category><![CDATA[smhub]]></category><category><![CDATA[smlight]]></category><category><![CDATA[zigbee]]></category><category><![CDATA[zigbee2mqtt]]></category><category><![CDATA[mqtt]]></category><category><![CDATA[Home Assistant]]></category><category><![CDATA[mosquitto]]></category><dc:creator><![CDATA[Mike Almeloo]]></dc:creator><pubDate>Wed, 17 Dec 2025 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766151558678/43a55100-def9-4f99-8eb0-5989b172dc0b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I LOVE smart home devices. Pretty much the exact moment I moved out of my parents’ house to attend university, I equipped my place with a bunch of smart devices to hook into my existing Home Assistant instance. My bedroom alone has multiple smart Zigbee devices: LED bulbs and remotes, an air quality monitor, a presence/light sensor, two power switches+monitors and some other stuff I’m probably forgetting. Something about having a fully local dashboard and smart home platform around a bunch of sensors really triggers the nerd inside of me.</p>
<p>Now, my networking situation is a bit… interesting. Home Assistant is running on the homelab at my parents’ place, but I live a little too far away for the Zigbee signal to reach all the way over here. I used to run a Raspberry Pi 4 with an SLZB-07 dongle, Zigbee2MQTT and an MQTT broker at my own place and bridged it with the broker at my homelab over a VPN. That worked incredibly well, and in fact served my needs perfectly for several years. However, the Raspberry Pi 3 in my 3D printer finally gave in recently, so I’ve had to salvage my only Pi 4 to fix it. That unfortunately left me with a dysfunctional Zigbee network, meaning I had to manually turn my lights on and off again (yes, by pressing <em>buttons!</em> Ew!) Now imagine my excitement when the kind folks over at SMLIGHT were willing to send over one of their SMHUB devices, and all it cost was for me to write a single review about their device. Deal!</p>
<p>That’s right! I tricked you into reading a product review! Don’t worry though, all the words in this post were formed from my own opinion alone, and we will still be exploring the cool software stuff behind the scenes. It’s just that while working towards our remote Zigbee goal, we will also be talking about the device itself a bit, as well as what I think of it. Sounds good?</p>
<h1 id="heading-the-device">The Device</h1>
<p>Let’s first talk a bit about what this SMHUB device actually <em>is</em>, and how it will help us achieve our goal. In <a target="_blank" href="https://smlight.tech/zz/smhub-nano-mg24">their own words</a>:</p>
<blockquote>
<p>SMHUB Nano is a professional-grade control hub running a Linux-based system that can host apps locally — Zigbee2MQTT, MQTT broker, WireGuard, Tailscale, Matterbridge, and more.</p>
</blockquote>
<p>Fancy! Under the hood, the SMHUB is simply an embedded Linux device with a built-in Zigbee/Matter/Thread adapter. It doesn’t have a whole lot of juice: it appears to be running on a single-core riscv CPU (bonus nerd points for ISA!) and around half a gigabyte of RAM. Despite this, it appears to be running fairly comfortably: my hub is idling at around 3% CPU usage and 300MB ‘used’ memory. And this is with Mosquitto, Zigbee2MQTT, Tailscale and their Python-based backend running, along with some other necessary services. You can definitely feel it drag its heels when installing apps, booting up or restarting services, but once it’s up and running it’s impressively smooth.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765838755460/e38c841d-49c3-4ed8-ad73-3c65ed7e3e13.png" alt="htop running on SMHUB Nano while idle" class="image--center mx-auto" /></p>
<p>Physically, SMLIGHT appears to use the same black casing as is also used for their other Ethernet-enabled adapters. It can be powered using either a USB-C cable or PoE. PoE does indeed work quite well, and it would be great to deploy it that way; however, I only own a single PoE injector, and it’s currently being used for my access point/router. So USB-C it is. Grrr.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765838654436/a630f5de-9f5d-4b6e-bf4b-624a18252bbe.jpeg" alt="SMHUB Nano taken freshly out of the box. No banana included, so the scale remains unknown" class="image--center mx-auto" /></p>
<p>Note that no USB-C cable or power brick were provided in the box, however I think most people will likely have some of those lying around anyway. The device takes regular 5V power, so any regular old phone charger seems to work just fine. They do provide double-sided sticky pads to stick the device to a surface, as well as two mini screws + a hex key if you prefer to go that route. Or, if you’re like me, just put it on your bedside table 👍 (the lights really add to the ambience at night)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766003211355/e0108715-9295-4aff-9bd6-637ff90cba84.jpeg" alt="SMHUB on my nightstand next to a network switch and power strip. The device arrived just in time for Christmas, and the lights really add to the holiday vibes" class="image--center mx-auto" /></p>
<h1 id="heading-the-plan">The Plan</h1>
<p>My desired end result is a device that I can simply plug into any ethernet port and which will automatically connect back to my homelab where Home Assistant runs.</p>
<p>I already run an MQTT broker and Tailscale on my homelab, so we will be installing Tailscale on the SMHUB to add the device directly to my Tailnet, which allows it to directly communicate with my Home Assistant host. Then, we configure the MQTT broker on the SMHUB to bridge to my main ‘upstream’ MQTT broker. This will re-emit any messages posted on one of the two brokers on the other broker, and essentially provides a transparent bridge for MQTT messages between the two devices. Finally, we configure Zigbee2MQTT on SMHUB to connect to our local broker. Home Assistant will then be able to see all devices controlled by Zigbee2MQTT and be able to control them.</p>
<p>We can split this plan up into the following parts:</p>
<ul>
<li><p>Setting up SMHUB for first use</p>
</li>
<li><p>Configuring our Home Assistant host</p>
</li>
<li><p>Installing and configuring Tailscale on SMHUB</p>
</li>
<li><p>Configuring Mosquitto and Zigbee2MQTT</p>
</li>
<li><p>Adding devices to see if it works!</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766005042615/ef04eff6-0042-48ad-b2c5-df186a9519dd.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-the-execution">The Execution</h1>
<h2 id="heading-getting-started">Getting started</h2>
<p>Setting up the SMHUB was quite simple. Simply plug it into power, attach an ethernet cable and you’re done! The device exposes a web UI which you can reach via <code>smhub.local</code>. If that doesn’t work, check your router for the IP address that was assigned to the device, and use that to connect to it instead.</p>
<p>If all went well, you should be greeted with a dashboard that looks something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766003871997/65d57345-81ba-4723-8960-5885050e192c.png" alt class="image--right mx-auto mr-0" /></p>
<p>Before configuring any settings, I would <strong>highly</strong> recommend to first update the system, apps and firmware on your device. I was running into quite a few (minor) hurdles along the way, but SMLIGHT has assured me (and I was able to verify) that the latest software versions have fixed those issues.</p>
<p>We need to upgrade 3 different system components using the following methods:</p>
<ol>
<li><p><strong>Firmware</strong>: to upgrade the device’s firmware, navigate to <strong>Settings → Updates and Restore</strong> and make sure that the latest OTA update is installed. If not, click the update button and restart the device.</p>
</li>
<li><p><strong>Apps</strong>: apps such as Zigbee2MQTT, the web interface and the backend code are updated independently from the OS. These can be updated by navigating to <strong>Apps</strong> and selecting <strong>Refresh</strong> button, before clicking on the <strong>Upgrade All</strong> button. Alternatively, you can open a terminal session from the <strong>Console</strong> menu and enter the following command: <code>sudo opkg update &amp;&amp; sudo opkg upgrade</code>. I prefer the latter method because you get a better sense of the upgrade progress (which can take some time), but either should work.</p>
</li>
<li><p><strong>Adapter Firmware</strong>: my device came with an adapter firmware version that had issues with Zigbee2MQTT under heavy network load. Even if it works fine for you, it’s a good idea to update to the latest version. Navigate to <strong>Settings → Radios</strong>, select a radio mode (you likely want <strong>Zigbee Coordinator</strong>), then select the newest firmware and click <strong>Update</strong>.</p>
</li>
</ol>
<h2 id="heading-configuring-home-assistant">Configuring Home Assistant</h2>
<p>There are many ways to install and use Home Assistant, and configuration will depend on how you have your smart home set up. I don’t personally use Home Assistant OS, but I know many people do. If you are one of them, here’s some pointers to guide you in the right direction:</p>
<ul>
<li><p><strong>Mosquitto</strong>: <a target="_blank" href="https://github.com/home-assistant/addons/blob/master/mosquitto/DOCS.md">https://github.com/home-assistant/addons/blob/master/mosquitto/DOCS.md</a></p>
</li>
<li><p><strong>Tailscale</strong>: <a target="_blank" href="https://github.com/hassio-addons/addon-tailscale/blob/main/tailscale/DOCS.md">https://github.com/hassio-addons/addon-tailscale/blob/main/tailscale/DOCS.md</a></p>
</li>
</ul>
<p>You may also have to install the <strong>MQTT</strong> integration into Home Assistant (if it doesn’t do that automatically).</p>
<p>What matters is that you have an MQTT broker running with Home Assistant connected to it, and that the broker is reachable through Tailscale.</p>
<h2 id="heading-running-tailscale-on-smhub">Running Tailscale on SMHUB</h2>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Tip!</strong> At the time of writing, SMHUB does not officially support Tailscale (yet). We will therefore be installing it manually for now. SMLIGHT has indicated that they intend on providing Tailscale support soon, so this manual installation method might not be necessary for you. You can use the command <code>sudo opkg list</code> to see whether Tailscale was added to the repository. If it is, use <code>sudo opkg install &lt;package name&gt;</code> to install it.</div>
</div>

<p>Currently, Tailscale is not (yet) officially supported on the SMHUB. However, the binaries published by Tailscale appear to work quite well and can be installed relatively easily. Run the following commands form the <strong>Console</strong> tab to download, extract and install Tailscale <code>1.92.1</code> to <code>/opt/bin/</code>:</p>
<pre><code class="lang-bash">wget https://pkgs.tailscale.com/stable/tailscale_1.92.1_riscv64.tgz
tar -xvf tailscale_1.92.1_riscv64.tgz
sudo mv tailscale_1.92.1_riscv64/tailscale tailscale_1.92.1_riscv64/tailscaled /opt/bin/
rm -rf tailscale_1.92.1_riscv64*
</code></pre>
<p>This works, but requires us to manually start the Tailscale daemon every time. Let’s create an <code>init.d</code> script to automatically run it at boot:</p>
<pre><code class="lang-bash">sudo nano /etc/init.d/S98tailscale
</code></pre>
<p>And paste the following contents:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/sh</span>

DAEMON=<span class="hljs-string">"tailscaled"</span>
PIDFILE=<span class="hljs-string">"/var/run/<span class="hljs-variable">$DAEMON</span>.pid"</span>
LOGFILE=<span class="hljs-string">"/var/log/<span class="hljs-variable">$DAEMON</span>.log"</span>

<span class="hljs-built_in">export</span> PATH=/opt/bin:<span class="hljs-variable">$PATH</span>

<span class="hljs-function"><span class="hljs-title">cleanup_and_exit</span></span>() {
    <span class="hljs-built_in">local</span> exit_code=<span class="hljs-variable">$1</span>
    rm -f <span class="hljs-string">"<span class="hljs-variable">$PIDFILE</span>"</span>
    <span class="hljs-built_in">return</span> <span class="hljs-string">"<span class="hljs-variable">$exit_code</span>"</span>
}

<span class="hljs-function"><span class="hljs-title">start</span></span>() {
    <span class="hljs-built_in">printf</span> <span class="hljs-string">"Starting tailscale: "</span>

    mkdir -p /run/tailscale /var/lib/tailscale

    start-stop-daemon --start --background --make-pidfile \
        --pidfile <span class="hljs-string">"<span class="hljs-variable">$PIDFILE</span>"</span> \
        -o -O <span class="hljs-string">"<span class="hljs-variable">$LOGFILE</span>"</span> \
        --<span class="hljs-built_in">exec</span> /opt/bin/tailscaled -- \
            --state=/var/lib/tailscale/tailscaled.state \
            --socket=/run/tailscale/tailscaled.sock

    status=$?
    [ <span class="hljs-string">"<span class="hljs-variable">$status</span>"</span> -eq 0 ] &amp;&amp; <span class="hljs-built_in">echo</span> <span class="hljs-string">"OK"</span> || <span class="hljs-built_in">echo</span> <span class="hljs-string">"FAIL"</span>
    <span class="hljs-built_in">return</span> <span class="hljs-string">"<span class="hljs-variable">$status</span>"</span>
}

<span class="hljs-function"><span class="hljs-title">stop</span></span>() {
    <span class="hljs-built_in">printf</span> <span class="hljs-string">"Stopping tailscale: "</span>

    start-stop-daemon --stop --pidfile <span class="hljs-string">"<span class="hljs-variable">$PIDFILE</span>"</span> --<span class="hljs-built_in">exec</span> /opt/bin/tailscaled
    status=$?

    <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$status</span>"</span> -eq 0 ]; <span class="hljs-keyword">then</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"OK"</span>
    <span class="hljs-keyword">else</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"FAIL"</span>
    <span class="hljs-keyword">fi</span>

    <span class="hljs-comment"># Wait until fully stopped</span>
    <span class="hljs-keyword">while</span> start-stop-daemon --stop --<span class="hljs-built_in">test</span> --quiet \
        --pidfile <span class="hljs-string">"<span class="hljs-variable">$PIDFILE</span>"</span> --<span class="hljs-built_in">exec</span> /opt/bin/tailscaled; <span class="hljs-keyword">do</span>
        sleep 0.1
    <span class="hljs-keyword">done</span>

    <span class="hljs-comment"># Required post-stop cleanup</span>
    /opt/bin/tailscaled --cleanup &gt;/dev/null 2&gt;&amp;1

    cleanup_and_exit <span class="hljs-string">"<span class="hljs-variable">$status</span>"</span>
}

<span class="hljs-function"><span class="hljs-title">restart</span></span>() {
    stop
    start
}

<span class="hljs-keyword">case</span> <span class="hljs-string">"<span class="hljs-variable">$1</span>"</span> <span class="hljs-keyword">in</span>
    start|stop|restart)
        <span class="hljs-string">"<span class="hljs-variable">$1</span>"</span>
        ;;
    reload)
        restart
        ;;
    *)
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"Usage: <span class="hljs-variable">$0</span> {start|stop|restart}"</span>
        <span class="hljs-built_in">exit</span> 1
<span class="hljs-keyword">esac</span>
</code></pre>
<p>Exit nano using <code>Ctrl+X</code>.</p>
<p>Let’s start the daemon and sign in! Run the following command and follow the instructions:</p>
<pre><code class="lang-bash">sudo chmod 755 /etc/init.d/S98tailscale
sudo /etc/init.d/S98tailscale start
sudo /opt/bin/tailscale up
</code></pre>
<p>If all went well, your SMHUB should now be added to your Tailnet! I recommend rebooting once to make sure Tailscale is in fact started automatically.</p>
<h2 id="heading-configuring-mosquitto-amp-zigbee2mqtt">Configuring Mosquitto &amp; Zigbee2MQTT</h2>
<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text"><strong>Warning</strong>: we will not be configuring a username/password for our MQTT broker. If you intend on deploying SMHUB in an untrusted environment, make sure to set one up. In this case, do not forget to set a password for the web UI (in <strong>Settings → Authorization</strong>) and properly secure the built-in SSH server. Configuring all this is out of scope for this tutorial.</div>
</div>

<p>Both Mosquitto and Zigbee2MQTT can be configured from the web UI alone. We will first configure Mosquitto. Navigate to <strong>Apps → MQTT</strong> and modify the settings as follows:</p>
<ul>
<li><p><strong>Allow Anonymous</strong>: only enable if you don’t have a username / password configured</p>
</li>
<li><p><strong>Enable bridge mode</strong>: enabled</p>
</li>
<li><p><strong>Remote connection</strong>: &lt;give it a nice name&gt;</p>
</li>
<li><p><strong>Remote Address</strong>: address to the MQTT broker that’s connected to Home Assistant. Since we’re using Tailscale, we can use device names here. Example: <code>hass:1883</code> to connect to device <code>hass</code> on port <code>1883</code> (default MQTT port). If you don’t know the name of your device, check your Tailscale dashboard.</p>
</li>
<li><p><strong>Remote User / Password</strong>: credentials for your existing MQTT broker (if required). If you’re using the Home Assistant add-on, this will likely be the credentials to your Home Assistant account.</p>
</li>
<li><p><strong>Bridge Topic</strong>: enter <code># both 0</code> to bridge messages in both directions. If your internet is very unreliable you could set the last digit to <code>1</code> or <code>2</code>, but in practice I found this to be unnecessary. The digits correspond to MQTT QoS levels.</p>
</li>
</ul>
<p>By this point, the page should look similar to this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766014088847/6258c909-9477-4b1f-9ddb-8d6dc11c9e36.png" alt class="image--center mx-auto" /></p>
<p>Make sure to save your settings. The MQTT server should be reloaded automatically.</p>
<p>To configure Zigbee2MQTT, navigate to <strong>Apps → zigbee2mqtt</strong> and set the options as follows:</p>
<ul>
<li><p><strong>Start at Boot</strong>: enabled</p>
</li>
<li><p><strong>Serial Port</strong>: <code>/dev/ttyS1</code></p>
</li>
<li><p><strong>Baud Rate</strong>: <code>115200</code></p>
</li>
<li><p><strong>Adapter</strong>: <code>Ember</code></p>
</li>
<li><p><strong>Zigbee Channel</strong>: leave default unless you’re having interference issues. Read <a target="_blank" href="https://www.zigbee2mqtt.io/guide/configuration/zigbee-network.html#network-config">this page</a> for more info.</p>
</li>
<li><p><strong>MQTT Broker URL</strong>: leave default (<code>mqtt://localhost:1883</code>)</p>
</li>
<li><p><strong>MQTT User / Password</strong>: leave empty</p>
</li>
<li><p><strong>Home Assistant</strong>: enabled</p>
</li>
</ul>
<p>Your configuration page should look something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766013830019/e6ac3997-d118-4a58-a175-9753433ba48a.png" alt class="image--center mx-auto" /></p>
<p>Finally, click the <code>Stop</code> and then <code>Run</code> button at the top of the page to restart Zigbee2MQTT. This can take a minute.</p>
<h2 id="heading-adding-devices">Adding Devices</h2>
<p>If all went well, Zigbee2MQTT on your SMHUB should now be attached to your Home Assistant instance! Navigate to <strong>Zigbee2MQTT</strong> in the web UI to see your shiny new instance. If something doesn’t work, click on the <strong>View Logs</strong> button to see what went wrong.</p>
<p>To add devices to your network, click <strong>Permit Join</strong> and follow your device’s specific instructions. The <a target="_blank" href="https://www.zigbee2mqtt.io/supported-devices/#">Z2M database</a> is a great resource to find instructions for your device. After adding a new device, you probably want to modify its name.</p>
<p>And that’s it! By now you should have a fully functional, fully portable Zigbee coordinator that can be deployed anywhere in the world. Simply connect it to the internet and it will automatically connect back to your home server.</p>
<h1 id="heading-results">Results</h1>
<p>I’ve been running the above configuration for several days now with ~7 Zigbee devices, and it’s been rock solid so far. It may not be the biggest network you’ve ever seen, but with how smooth it all works now, I do have a lot of confidence for stability in the future.</p>
<p>One thing that really stood out to me was how low the latency is. Toggling devices on and off while I’m at my place feels practically instant. This despite the fact that the command makes an entire round trip: from here to the Home Assistant server at my parent’s place, then back across two MQTT brokers and Zigbee2MQTT. My previous setup with a Raspberry Pi was practically the same, and even though the Pi is a lot more powerful than the SMHUB, the lack of compute power does not appear to significantly impact network performance in any way.</p>
<p>I’ll continue to monitor the stability of this setup and will update this blog post if the above ever changes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766080100127/dd964ce3-ed8c-436a-b4ce-703c834a6ad6.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>By now I’ve been able to spend quite some time with this device and have been able to pinpoint quite well what I like about it and what could (in my opinion) use some improvement. I’ll explain in more detail below.</p>
<h2 id="heading-the-good">The Good</h2>
<ul>
<li><p>Very nice / responsive web UI</p>
</li>
<li><p>Surprisingly stable</p>
</li>
<li><p>Flexible power and mounting options</p>
</li>
<li><p>Easy to set up</p>
</li>
<li><p>Full device access</p>
</li>
</ul>
<p>Something that happens very often in tech is companies providing a product for only one of two types of typical users. On one hand you have people who want a good out-of-the-box experience, while on the other hand you have power users who don’t mind getting their hands dirty. Sometimes you see people from the latter group ‘hack’ devices meant for the former group, but this often takes a lot of effort due to artificial restrictions put up by the manufacturer.</p>
<p>SMHUB seems to break with this tradition - I could recommend the SMHUB Nano to both types of users. The out-of-the box experience is great: setup is easy, pretty much everything you need is configurable through the web UI and overall it just works really well. But at the same time, the device was also designed with power users in mind. You get full root access to the device if you so wish, and don’t even have to touch the web UI if you don’t want to. And since the web UI populates most of its config values from the actual state of the device, changes that are made through the console are reflected in the web UI and vice versa. They really struck a remarkably good balance between ease of use and flexibility, and I appreciate that a lot.</p>
<h2 id="heading-the-ehhhhhh">The Ehhhhhh</h2>
<ul>
<li><p>Performance is barely enough</p>
</li>
<li><p>Not technically open source</p>
</li>
<li><p>Some features are currently missing</p>
</li>
</ul>
<p>That said, there are also a few minor downsides. While the compute resources of the device are good enough to run built-in apps, SMLIGHT is really pushing it a lot. Updating apps can take a while, and it takes a few minutes for the device to fully boot. Downloading files over HTTPS is capped at ~20 Mbps due to the slow CPU. And you can see the CPU spike at 100% while signing in because… it’s generating a JWT with HS256. Yeah, the algorithm that only needs to hash a string. That one. Despite this though, once your apps are up and running the device is surprisingly smooth. Just… don’t expect too much of it in terms of performance.</p>
<p>Another disappointment-but-also-not-a-huge-deal: the SMHUB’s firmware is not technically open source. SMLIGHT boasts about “Linux inside” while comparing it to “often closed” alternatives, and while this is true, it doesn’t say anything about the firmware itself. That said, it is super easy to get root access to the device, and if you look around on the file system a bit, you will find their <code>smhub_backend</code> Python library which is completely unobfuscated. So although the device is technically not open source, it’s transparent enough to easily modify, audit and verify that it isn’t doing weird things, which is what is most important to me.</p>
<p>Finally, it’s a little disappointing that some features are missing in the current firmware version. Tailscale is one of them, but there are also a bunch of other features that are currently ‘Under Dev’:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766076786340/0ef68d51-058f-40c3-8183-7d46ae536b82.png" alt class="image--center mx-auto" /></p>
<p>In a way, I like that SMLIGHT has already shared an this device with us so that people can provide feedback. And even in its current state, almost all of the important features are supported already. But if you’re intending on buying the device based on currently unsupported features that are already listed on the website, maybe wait a bit until it is clear when these will be supported.</p>
<h2 id="heading-the-bad">The Bad</h2>
<ul>
<li><p>Not emergency-flashable from Linux or MacOS</p>
</li>
<li><p>Difficult to lock down externally</p>
</li>
</ul>
<p>While the points I made before are relatively minor, there are also a few things that I genuinely dislike. One of them concerns the ‘emergency’ flashing process: if your device is bricked for any reason and is unable to boot up, SMLIGHT provides two ways to reset your device. One of them involves inserting an SD card, which I have no clue how to do, since as far as I can tell there is no SD card port on this thing. It’s probably only supported on the full-size SMHUB version. The other method is to connect the device to your computer and run a flashing script, however this script is currently only supported on Windows. Linux and MacOS users are therefore out of luck, which is a real shame.</p>
<p>Secondly, I really would not recommend deploying this device in an untrusted network. By default, the MQTT server is reachable externally. Zigbee2MQTT, the backend API server and probably other apps as well are all listening on a dedicated, external port. The SSH server has root login enabled by default, so you should change the default password for both the <code>smlight</code> and <code>root</code> user (or preferably, disable password auth altogether). No firewall is installed (including <code>iptables</code>) so you couldn’t configure it even if you wanted to.</p>
<p>All of the above issues are easily fixable; it’s just Linux after all. Maybe SMLIGHT will address it in the future. They’re also not that big of a deal if your device is running in a trusted network. But if you’re deploying it professionally, definitely put it on a separate LAN segment away from other devices (which you should be doing anyway).</p>
<h1 id="heading-final-words">Final Words</h1>
<p>So, am I happy with the final result? Yes! Very much so, in fact. It’s a pretty good successor to my previous setup. I generally enjoy running software with different purposes on separate hardware, and this aligns quite well with that methodology. It’s really nice to have a simple device that manages my Zigbee network and simply exposes an MQTT broker, while simultaneously tying in with my Tailscale network.</p>
<p>Would I recommend it to others? Depending on your use case: yes, absolutely. As I mentioned before, this device targets a pretty wide range of people. To me, the advantages of this device are much greater than the downsides, and I suspect this to be the case for most people.</p>
<p>Of course, if plugging a dongle into your already existing Home Assistant host is all you need, this device is probably not for you. But if you need coverage in a different location, or even on the other side of your house, this device will work great. For ~€45 you get everything you need in a single device that’s also very easy to set up. That’s a pretty good deal if you ask me.</p>
]]></content:encoded></item><item><title><![CDATA[Supercharging the 1Password SSH Agent]]></title><description><![CDATA[🔑
Please note: this article was published as a submission to the 1Password + Hashnode Hackathon. The public source code for the project I am about to discuss can be found here.


If you're like me, you probably have a unique SSH key for each remote ...]]></description><link>https://blog.mikealmel.ooo/supercharging-the-1password-ssh-agent</link><guid isPermaLink="true">https://blog.mikealmel.ooo/supercharging-the-1password-ssh-agent</guid><category><![CDATA[BuildWith1Password]]></category><category><![CDATA[1password]]></category><category><![CDATA[ssh]]></category><category><![CDATA[Nim]]></category><category><![CDATA[ssh-agent]]></category><dc:creator><![CDATA[Mike Almeloo]]></dc:creator><pubDate>Sat, 01 Jul 2023 01:46:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1687460654587/d6692a01-754e-4baf-81bd-3dfa34e2141d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">🔑</div>
<div data-node-type="callout-text"><strong>Please note:</strong> this article was published as a submission to the <a target="_blank" href="https://1password.com">1Password</a> + <a target="_blank" href="https://hashnode.com">Hashnode</a> Hackathon. The public source code for the project I am about to discuss can be found <a target="_blank" href="https://github.com/DismissedGuy/1p-ssh">here</a>.</div>
</div>

<p>If you're like me, you probably have a unique SSH key for each remote host you connect to. 1Password makes this super easy by providing an SSH agent which will automatically serve those keys from your vault. However, it has one very annoying issue:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687299520707/d7dab14a-49d6-4d5a-bbc1-c13aee3b0e98.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-whats-the-problem">What's the problem?</h1>
<p>It's not 1Password's fault! To understand what the problem is, you'll first need to understand how the SSH client and agent are separated. Simply said, the agent is responsible for keeping track of your keys, while the client will connect to the agent and retrieve a list of keys, among other tasks. In this context, the client is the actual SSH program you interact with, such as OpenSSH. The 1Password Desktop App includes a custom SSH agent which the client can then connect to. It's important to note that according to <a target="_blank" href="https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent">the protocol</a>, your private keys never leave the agent.</p>
<p>The SSH client is kind of dumb: it simply asks for all keys from the SSH agent and then tries them one by one until authentication succeeds. However, most servers have a low maximum number of authentication attempts - the default is 6. This causes issues when you have more than 6 keys in your vault, and 1Password happens to offer the correct one last.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687300400789/f2716155-95d2-4805-81f2-6ecf70bf5220.png" alt="An over-simplified overview of the problem" class="image--center mx-auto" /></p>
<p>Unfortunately, this problem is not easy to fix. The SSH agent protocol simply does not support providing any context when requesting a list of keys. You can read more about the process in <a target="_blank" href="https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-4.4">this</a> section of the RFC.</p>
<h2 id="heading-existing-solutions-and-why-theyre-bad">Existing solutions (and why they're bad!)</h2>
<p>As you can probably imagine, this problem is fairly common. The solutions however are not very diverse: they all have drawbacks in one way or another.</p>
<p><strong>Solution #1</strong><br />The most common solution is to simply change the config on your remote host to allow more authentication attempts. However, this might not always be possible, and even if it is, it's quite undesirable from a security standpoint. An ideal solution would have no such compromises - the client is the problem here, so why fix the server?</p>
<p><strong>Solution #2</strong><br /><a target="_blank" href="https://developer.1password.com/docs/ssh/agent/advanced/#match-key-with-host">The "official" solution</a> suggested by 1Password is to configure the SSH client to match your host with the correct public key. Sounds good, right? Well, kind of. This method works by downloading a copy of your public key, then specifying which key to use for which host in your <code>.ssh/config</code> file. I dislike this solution because I don't want to store my public keys on disk - I want 1Password to handle everything! It's also rather annoying to do this for every new host you add. There has to be a better fix, right?</p>
<h2 id="heading-the-proper-fix">The Proper Fix™</h2>
<p>I realized that a potential solution needs something more sophisticated - the SSH agent protocol is too dumb for 1Password to fix something on their end, and OpenSSH (or other clients, for that matter) doesn't allow for the fine-grained configuration options that we need. What we <em>actually</em> need is some kind of connector - a glue if you wish - that integrates the SSH client even more tightly with 1Password than it already is. Preferably, it should be completely transparent to maximize compatibility across SSH clients. A difficult task for sure, but not impossible!</p>
<p>I settled on a solution that implements its own SSH agent. "But the agent protocol is dumb, right?" Well, yes, but it <em>does</em> have <a target="_blank" href="https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-4.7">a formal specification for protocol extensions</a>. In theory, we could use this to supply additional information about the remote host to the agent, which can then shuffle the keys to prioritize the (likely) correct one. In other words, if we can tell the agent "Hey, I want to connect to 1.2.3.4," it should be able to figure out which key to serve you first. We just need to make sure that any other message is forwarded to 1Password because the SSH client can only connect to a single agent at once.</p>
<p>That's not all, though. To actually be useful, we must also intercept the <code>SSH_AGENT_IDENTITIES_ANSWER</code> that we receive from the 1Password agent. This message contains the actual public keys that the SSH client will try, and we should reorder the keys to put the most likely match first. How do we know which keys belong to which host? We'll get to that in a second. In the meantime, here's a flow diagram of the agent's tasks which should help the confused among you:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687465489537/67a8c0b3-f492-45d2-87cb-62424de5110a.png" alt class="image--center mx-auto" /></p>
<p>It sits nicely in the middle of our client and agent. Super transparent!</p>
<h2 id="heading-more-than-just-the-agent">More than just the agent</h2>
<p>The above solution sounds great in theory, but you may have noticed one major flaw: how do we make the SSH client understand our protocol extension? Implementing this very specific extension into every major SSH client doesn't sound very fun, and could take ages (if they're even willing to implement it at all!) "Hey major SSH client devs, could you implement this real quick? I have a Hackathon deadline to meet!"</p>
<p>The simple answer is that we don't bother! Instead of modifying the client itself, we can execute our own special program which will then set up the SSH client. This allows us to quickly send the custom extension message <em>before</em> actually starting the client. Even cooler, we can <em>replace</em> our initializer process by the client once we're done using the <a target="_blank" href="https://linux.die.net/man/3/execv"><code>execv()</code></a> syscall. This allows for even better transparency and security.</p>
<p>Together with our agent process, we would have a solution that looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687467394022/5eba26c5-cb0c-479f-8b42-ac8626665320.png" alt class="image--center mx-auto" /></p>
<p>Much better! I almost forgot, but we can use the excellent <a target="_blank" href="https://developer.1password.com/docs/cli/">1Password CLI</a> to help us match our SSH keys with the host. I've promised this before, but we'll get to that in a second :)</p>
<h2 id="heading-piecing-it-all-together">Piecing it all together</h2>
<p>Now that you hopefully understand how the entire project will fit together, I think it's time to talk about the details. There's a lot to cover, so buckle up.</p>
<h3 id="heading-matching-the-keys">Matching the keys</h3>
<p>Let me address the elephant in the room first: <em>how do we match the keys with our hosts?</em> I've mentioned before that I want to have a solution that's managed completely on the 1Password side, without having to download my keys or edit config files. Do you know what would be even cooler? If we could sync the configuration across all of our devices, by securely storing it as fields in our vault. Something like this, perhaps:</p>
<p><img src="https://u.mikealmel.ooo/u/Wc9Rru.png" alt="https://u.mikealmel.ooo/u/Wc9Rru.png" /></p>
<p>Well, it turns out we actually can! Since our agent is running locally, we can interface with the 1Password CLI to retrieve items from our vault. Since we know which host the user wants to connect to, we can match it against the <code>host</code> fields in the key items that are stored in our vault. If we have a match, we know to serve that key first. If you have the CLI installed, you can try the below command:</p>
<pre><code class="lang-bash">op item list --categories SSHKey --format=json | op item get -
</code></pre>
<p>This will show all your SSH keys, including their fields. For our purposes, we only need the fingerprint and the <code>host</code> fields; therefore we simply filter out the rest. This ensures that we don't touch more sensitive data than necessary. Yay, security!</p>
<h3 id="heading-advanced-matching">Advanced matching</h3>
<p>The host matching is cool, but the temperature can drop further! If you want to access a host by both IP address and hostname, or if the IP regularly changes, it can still be quite annoying. Furthermore, if any of those two change only ever so slightly, it could still serve the wrong keys. We need a smarter way to match against our hosts.</p>
<p>To fix the first problem, the agent will try to resolve any hostname it can find in your host fields, and add them to its internal list. This means that if you have a router that's accessible via both <code>router.local</code> and <code>192.168.1.1</code>, you can simply add <code>router.local</code> to your hosts, and it will still function if you tell SSH to connect to <code>192.168.1.1</code> . Furthermore, if the router's IP address ever changes, your keys will still be matched correctly as long as the domain name is resolvable!</p>
<p>To truly get a good matching algorithm though, we don't just want to check whether hostnames or IPs are equal. Ideally, we want to find the similarity between the host we're connecting to and the hosts that are stored in our vault. We can do this using a metric called the <a target="_blank" href="https://en.wikipedia.org/wiki/Levenshtein_distance">Levenshtein distance</a>, which tells us how many characters we need to modify (insert, delete or replace) to go from string <em>a</em> to <em>b</em>. Since the minimum distance is 0 (if they are equal) and the maximum is equal to our longest host value, we can assign a score from 0 to 1 for every host in our vault to determine a relative similarity. Subtract this score from 1, and we can start maximizing our score!</p>
<p>To find the key offer order, we want to match against all hosts for every key that we have. So we define the <em>key-specific score</em> to be the maximum of all scores, calculated on the host stored alongside the key and our target host. Finally, we give a little boost to the key score if we detect that the score gets lower when we only include resolved hosts. This ensures that if we have two keys - let's call them <em>x</em> and <em>y</em> - and key <em>x</em> contains a host that resolves to an IP in one of <em>y</em>'s hosts, key <em>y</em> will always be served before key <em>x</em>. In other words, we prefer direct matches over resolved ones.</p>
<h3 id="heading-going-full-stealth-mode">Going full stealth mode</h3>
<p>To make the experience as smooth as possible for the end user, we need to make the program even more transparent. We can do this by creating a local symlink called <code>ssh</code> in the user's PATH and setting it to our custom executable. We can then "catch" the ssh command by checking <code>argv</code> in our program, and immediately executing the ssh subcommand if we detect we're being run as ssh. Stealthy!</p>
<pre><code class="lang-bash">$ ls -l $(<span class="hljs-built_in">which</span> ssh)
lrwxrwxrwx 32 mike  1 Jul 03:22 /home/mike/.<span class="hljs-built_in">local</span>/bin/ssh -&gt; /home/mike/.<span class="hljs-built_in">local</span>/bin/op_ssh_fix
</code></pre>
<p>Since the program needs quite some hooks into the user's environment, it comes bundled with a handy installer. The installer will take care of the following things:</p>
<ul>
<li><p>Installing the standalone binary to the user's local bin directory</p>
</li>
<li><p>Setting up the SSH alias</p>
</li>
<li><p>Installing and configuring a local systemd service to run the agent proxy</p>
</li>
<li><p>Automatically updating or adjusting the user's SSH config to point to the new agent</p>
</li>
</ul>
<p><img src="https://u.mikealmel.ooo/u/0nhMfw.png" alt="https://u.mikealmel.ooo/u/0nhMfw.png" /></p>
<h2 id="heading-wrapping-up">Wrapping up</h2>
<p>All in all, I am very happy with how this project turned out. I absolutely love turning my ideas into code, but something that has always bothered me is that some of those ideas are too crazy to execute :). Unlike other products however 1Password really does not stand in the way of my imagination: the CLI is honestly the only thing making this project possible at all, and it's super easy to use in your programs (it supports JSON output!!)</p>
<p>In an ideal world, I would like to see this project be integrated into 1Password itself. That would prevent some issues such as the incorrect executable path when authenticating (since the request comes from the proxy) and would make the entire progress much smoother overall. One downside of my current project is the lack of Windows support: the nature of this project unfortunately makes it quite platform-dependent, and since I do not have a Windows machine I am unable to test on that platform. I could totally see it happen though, I'm sure it's possible in theory!</p>
<p>Finally, I would like to thank everyone at 1Password and Hashnode for this great hackathon, I've had nothing but positive experiences communicating with you guys. See you at the next event!</p>
]]></content:encoded></item><item><title><![CDATA[Welcome to my blog!]]></title><description><![CDATA[Hey there, thanks for dropping by! What you're seeing right now is my first blog article, hopefully with many more to come. For a while now I've been wanting to document my technical adventures and share them with other people. However, I'd been putt...]]></description><link>https://blog.mikealmel.ooo/welcome</link><guid isPermaLink="true">https://blog.mikealmel.ooo/welcome</guid><dc:creator><![CDATA[Mike Almeloo]]></dc:creator><pubDate>Mon, 19 Jun 2023 22:11:30 GMT</pubDate><content:encoded><![CDATA[<p>Hey there, thanks for dropping by! What you're seeing right now is my first blog article, hopefully with many more to come. For a while now I've been wanting to document my technical adventures and share them with other people. However, I'd been putting this off for way too long until I came across Hashnode.</p>
<p>I am aiming for this place to mostly be about things I find interesting, projects I have been working on or other share-worthy material. If you want to see a post or series about a certain topic, feel free to let me know. I've already got some interesting topics lined up which I am planning to write about.</p>
<p>See you in my second post!</p>
]]></content:encoded></item></channel></rss>