<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet type="text/xsl" href="/index.xsl"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>./techtipsy</title><link>https://ounapuu.ee/tags/networking/</link><description>Recent content on ./techtipsy, a blog written by Herman Õunapuu.</description><generator>Hugo -- gohugo.io</generator><language>en-GB</language><managingEditor>ihavesomethoughtsonyourblog@ounapuu.ee (Herman Õunapuu)</managingEditor><webMaster>ihavesomethoughtsonyourblog@ounapuu.ee (Herman Õunapuu)</webMaster><lastBuildDate>Wed, 06 May 2026 06:00:00 +0300</lastBuildDate><atom:link href="https://ounapuu.ee/tags/networking/index.xml" rel="self" type="application/rss+xml"/><item><title>How I self-host this blog at home with a dynamic IPv4 address, IPv6 prefix, and a dash of Wireguard</title><link>https://ounapuu.ee/posts/2026/05/06/self-host-at-home/</link><pubDate>Wed, 06 May 2026 06:00:00 +0300</pubDate><author>ihavesomethoughtsonyourblog@ounapuu.ee (Herman Õunapuu)</author><guid>https://ounapuu.ee/posts/2026/05/06/self-host-at-home/</guid><description>I think I've finally figured it out. For now.</description><content:encoded><![CDATA[<img src="https://ounapuu.ee/posts/2026/05/06/self-host-at-home/media/cover_hu_a0e9c758122adadf.jpg" width="1200" height="630" alt="How I self-host this blog at home with a dynamic IPv4 address, IPv6 prefix, and a dash of Wireguard" /><p>Networking has long been my Achilles heel. I know the very basics, but the more complex areas of networking have been a
bit puzzling to me. By the time I figured out how IPv4 works, I found IPv6 and that my ISP supports it.</p>
<p>Back to square one.</p>
<p>That didn&rsquo;t stop me from learning some bits, and after 8+ years of self-hosting as a hobby, I&rsquo;ve settled on a setup that
works for me and overcomes common residential internet connection nuances, such as dynamic IPv4 addresses and changing
IPv6 prefixes. I&rsquo;m sharing these tips and tricks with the goal of helping out other hobbyists out there that happen to
share a similar stack.</p>
<h2 id="background">
  <a class="heading-anchor" href="#background">Background<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>My ISP is polite enough to provide a public IPv4 address, and allowing incoming traffic is a toggle in their online
self-service. Not perfect, but at least you can do it. However, they charge about 6 EUR a month for the static IP
address service, which I am not willing to pay out of principle.</p>
<p>They also support IPv6, which is great, and they provide you a whole <code>/56</code> slice of it to play with using IPv6 prefix
delegation. Unfortunately they have configured the lease time for the prefix to be incredibly short: 26 <strong>minutes</strong>!</p>
<p>A router reboot or short power outage usually results in the IPv4 address and IPv6 prefix changing, which is really
annoying as my services become unavailable for a short time.</p>
<h2 id="dynamic-dns">
  <a class="heading-anchor" href="#dynamic-dns">Dynamic DNS<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>A common way to overcome the dynamic IP address limitation is to sign up with a provider to set up a DNS record that
changes whenever your home IP address changes. My domain registrar does not have this as a feature, and I&rsquo;m not
interested in using a different provider, so I went in a different direction and built a home-grown script that does the
same thing.</p>
<p>Initially, this script relied on a public service that tells you what your IP address is, and based on that I could
check if things have changed and I need to update my DNS record. This approach has one glaring catastrophic failure mode
though: that provider could lie to you one day and now you&rsquo;ve pointed your DNS records at the attackers&rsquo; servers. :)</p>
<p>I ignored that failure mode for a while, but once I learned about the effectiveness of LLM-based tooling, I decided to
give it a go and to build a better solution that takes into account my setup and requirements, while at the same time
saving me from the frustration of troubleshooting and debugging this in a late evening. I&rsquo;m still very limited on
available free time, so optimizing for that is a priority for me.</p>
<p>My main networking gear runs OpenWRT, and it supports running shell scripts periodically in a crontab. The router has
two WAN interfaces, one for IPv4 and one for IPv6. It already knows what IP address and prefix have been assigned to it,
so I don&rsquo;t have to rely on an external service provider for finding this out.</p>
<p>Handling IPv4 addresses is simple: check the IPv4 address of the WAN interface. Query your existing DNS records, diff
it, and if it has changed, push an update in a separate API call. Super simple!</p>
<p>With IPv6, the approach is slightly different. Instead of the WAN interface, I have to get the IPv6 address of the
target machine, and make sure that it&rsquo;s routable over the public internet. When you&rsquo;ve checked your network settings in
an IPv6 network, you may have noticed a lot of different IP addresses there, with lots of letters thrown into the mix.</p>
<p>Here&rsquo;s an example from the machine that is serving you this blog (likely out of date though!):</p>
<pre tabindex="0"><code>    inet6 fdb3:6dad:6dce::f41/128 scope global dynamic noprefixroute 
    inet6 fdb3:6dad:6dce:0:2e0:4cff:fe0c:9ddb/64 scope global noprefixroute 
    inet6 2001:7d0:856c:4000::f41/128 scope global dynamic noprefixroute 
    inet6 2001:7d0:856c:4000:2e0:4cff:fe0c:9ddb/64 scope global dynamic noprefixroute 
    inet6 fe80::2e0:4cff:fe0c:9ddb/64 scope link noprefixroute 
</code></pre><p>The two relevant ones are the ones that start with <code>2001:</code>, others are link-local or accessible over the local network
only. The shorter one consists of the IPv6 prefix part, and then the unique bit at the end is a predictable suffix that
the host gets. The other one also works, but is as far as I understand randomly generated and more difficult to predict
when we get around to next sections.</p>
<p>I know that there is probably a better way to do this, but I wanted to keep things simple enough so that I can
troubleshoot them if needed. It may be possible to trigger this updater script on events that WAN and WAN6 interfaces
send, but I have not validated this theory.</p>
<p>There are many different ways to find the IPv6 address of a particular host, so the script I have just tries multiple
approaches to find the one that we&rsquo;re looking for.</p>
<p><a href="/posts/2026/05/06/self-host-at-home/media/ddns.sh">Here&rsquo;s the script</a> in case you&rsquo;re interested in setting up something similar. It reads credentials from
an .env file and is built around <a href="https://api.zone.eu/">the Zone API</a>. On OpenWRT, the only dependency that you need to
install is <code>curl</code>, which to my surprise was not part of the default packages list, probably to save on space.</p>
<p>One lesson I learned from a previous iteration of the script: if you trigger DNS record updates every minute, then Zone
will actually reach out to you via e-mail telling you to cut that shit out, politely. It was just one missing <code>if</code>
statement, and yet it caused some frustration to engineers far away. Sorry!</p>
<h2 id="predictable-ip-addresses">
  <a class="heading-anchor" href="#predictable-ip-addresses">Predictable IP addresses<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>It&rsquo;s a good idea to set up static IP addresses for your hosts, both for IPv4 addresses and IPv6 prefix delegation via
DUID-s.</p>
<p>The OpenWRT GUI LuCI makes it quite simple, just set the addresses as static on the landing page for the hosts
that you are interested in forwarding traffic to, and you&rsquo;re done!</p>
<p>My recommendation here is to also set a predictable IPv6 suffix, otherwise all your IPv6 traffic rules may break once
again due to this nuance.</p>
<p>I like to make that host number the same for both IPv4 and IPv6, quick example:</p>
<ul>
<li><code>192.168.1.2</code></li>
<li><code>2001:7d0:854f:8e00::2</code></li>
</ul>
<p>Here&rsquo;s a configuration snippet example from <code>/etc/config/dhcp</code>, look for the <code>hostid</code> option:</p>
<pre tabindex="0"><code>config host
        option name &#39;mycoolserver&#39;
        option ip &#39;192.168.1.69&#39;
        list mac &#39;12:34:56:78:90:AB&#39;
        option duid &#39;yourduidgoeshere&#39;
        option hostid &#39;69&#39;
</code></pre><p>Apply with <code>service dnsmasq restart</code>.</p>
<p>In LuCI, as of OpenWRT 25.12, look for &ldquo;IPv6 token&rdquo;.</p>









<figure class="center">
  <a href="/posts/2026/05/06/self-host-at-home/media/ipv6token.png">
    <img src="/posts/2026/05/06/self-host-at-home/media/ipv6token_hu_556676a81dfcb972.webp"
     width="1000"
     height="103"
     loading="lazy"
     decoding="async"
     alt="Set an IPv6 token for a predictable IPv6 suffix.">

  </a>
  <figcaption class="center">Set an IPv6 token for a predictable IPv6 suffix.</figcaption>
</figure>

<p>Note that due to a bug, it doesn&rsquo;t seem to be possible to set a numeric IPv6 token via GUI, which is why you will need
to add it manually in CLI using the above approach.</p>
<h2 id="port-forwards-traffic-rules-potato-potahtoh">
  <a class="heading-anchor" href="#port-forwards-traffic-rules-potato-potahtoh">Port forwards, traffic rules, potato, potahtoh<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>Whenever you want to make a local machine accessible on the internet for IPv4, the solution is simple: set up a port
forward to that particular machine, and you&rsquo;re done! It&rsquo;s a common enough flow for people who&rsquo;ve set up game servers and
the like, and well understood by more novice users.</p>
<p>With IPv6, port forwards don&rsquo;t help. You&rsquo;ll have to check one tab over at &ldquo;Traffic rules&rdquo; in OpenWRT GUI.</p>
<p>It&rsquo;s a common misconception that by using IPv6 you are exposing everything to the world as each device gets its own IPv6
address, but turns out that this is not the case in most common setups. By default, OpenWRT forwards only a few types of
traffic to IPv6 hosts, such as ICMP packets that make <code>ping</code> work between devices over IPv6 across the public internet.
If you are interested in allowing IPv6 clients to access services on your local server that has an IPv6 address, you
will have to explicitly allow it by adding a new traffic rule.</p>
<p>There&rsquo;s one issue with this approach that a lot of users seem to run into: if the IPv6 prefix changes, then all my
traffic rules that are pointing to a particular host are automatically broken!</p>
<p>Luckily there is a clever workaround implemented on OpenWRT that bypasses this issue. Assuming that you followed the
previous step and set yourself up with a predictable IPv6 suffix, when setting up a traffic rule, set the target device
up as <code>::69/-64</code>, just replace <code>69</code> with your actual suffix. The IPv6 prefix can now change, but the ports that you&rsquo;ve
made accessible on this specific host will remain working.</p>
<p>At this point, you should be all set with a reasonably well working setup where you&rsquo;ve handled the issues with dynamic
IPv4 and IPv6 prefix, and you can access your services over the public internet even when things happen.</p>
<h2 id="limitations">
  <a class="heading-anchor" href="#limitations">Limitations<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>One issue that this setup has is the fact that DNS change propagation takes time. Usually clients will pick up the new
records within 5 minutes, but in my professional career I&rsquo;ve seen some clients take up to 24 hours or longer to finally
start sending traffic to the new DNS record. Whenever your IP address changes, there will be a mini-outage. Not
catastrophic if you&rsquo;re just hosting hobby projects and personal services at home, but I wouldn&rsquo;t host anything
mission-critical in such a setup.</p>
<p>When your OpenWRT device is as underpowered as mine, then you may notice that the TLS encryption overhead when <code>curl</code>
-ing around can be significant. I have set my dynamic DNS script to run every 5 minutes, and it shows up on the CPU
usage graphs on my router.</p>









<figure class="center">
  <a href="/posts/2026/05/06/self-host-at-home/media/graph.png">
    <img src="/posts/2026/05/06/self-host-at-home/media/graph_hu_e39ff7b6e73083d4.webp"
     width="956"
     height="383"
     loading="lazy"
     decoding="async"
     alt="CPU usage graph on my router showing the scheduled dynamic DNS script doing work.">

  </a>
  <figcaption class="center">CPU usage graph on my router showing the scheduled dynamic DNS script doing work.</figcaption>
</figure>

<h2 id="wireguard-all-the-things">
  <a class="heading-anchor" href="#wireguard-all-the-things">Wireguard all the things!<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>I know that Tailscale is a popular method of connecting up your devices and making your personal services privately
accessible over that, which significantly reduces your attack surface. Being behind a few updates or not being vigilant
enough is less of an issue compared to exposing your services over the public internet.</p>
<p>You don&rsquo;t necessarily need Tailscale for that though! If you just need a way to access your services over a private and
secure network, then setting up a dedicated mini PC or single-board computer is a very good starting point. Let it be
the server, allow traffic to move between the clients over the Wireguard interface, and you&rsquo;re all set!</p>
<p>Alternatively, if you have an OpenWRT router, then you can do it right there, but I found the GUI management setup to be
a bit clunky compared to deploying the plain configuration files to clients. When I <em>did</em> do that test, I discovered
quickly that my router and its single ARM CPU core with no cryptography extensions is too slow for managing my Wireguard
network, with speeds topping out at 20 Mbit/s. <strong>20.</strong> The LattePanda IOTA can easily saturate its gigabit link, as does
the ThinkPad T430, and even devices like the Orange Pi Zero can handle a theoretical maximum of about 240 Mbit/s over
Wireguard measured using <a href="https://github.com/cyyself/wg-bench">wg-bench</a>.</p>
<p>My current Wireguard host is <a href="/posts/2026/04/04/lattepanda/">the LattePanda V1, the most unstable computer in my fleet.</a>
With a USB adapter, it can push almost half a gigabit of traffic over Wireguard.</p>
<p>If you&rsquo;re like me, and you like hosting your services over Docker or Podman, then instead of listening on ports for all
interfaces on your containers (default behaviour when setting up port forwards), I recommend listening only on the
Wireguard interface. This makes the service only accessible over Wireguard, meaning that you only need to set up one
port forward and traffic rule to connect to the Wireguard network, and then you have access to all of your services. The
attack surface is significantly reduced, the whole Wireguard solution is stable and very small, and unless you leak your
private key, you are reasonably secure!</p>
<p>Here&rsquo;s a snippet from a compose file showcasing how to set this up for IPv4 and IPv6:</p>
<pre tabindex="0"><code>ports:
  - 10.69.69.12:2283:2283
  - &#34;[fded:abba:acca::12]:2283:2283&#34;
</code></pre><p>Want to make the service available over Wireguard <em>and</em> over the local network directly? Just add those to the list!
Note that if your local address changes and you don&rsquo;t update it in the compose file, your container will refuse to start
up as it cannot listen to the interface any longer, but you can mitigate that with the static IP addresses step.</p>
<p>When you are going with this route, it is unlikely but still possible that by the time the container starts up, the
Wireguard interface is not yet up. To resolve this, you can use <code>systemd</code> to set Wireguard up as a dependency that you
will have to wait for before the container starts up.</p>
<p>I manage my Wireguard connection with <code>wg-quick@interfacename</code> service. You can set up a <code>systemd</code> override for Docker,
or if you manage your Docker/Podman services via systemd, then you can set it up per-service using this pattern:</p>
<pre tabindex="0"><code># /etc/systemd/system/myimmichserver.service.d/override.conf
[Unit]
Requires=wg-quick@interfacename.service
After=wg-quick@interfacename.service
</code></pre><p>By the way, <code>systemd</code> overrides are also really useful for ensuring that your storage that your containers rely on is
properly mounted. If my service requires the path <code>/immich</code> to be available and mounted, add something like this:</p>
<pre tabindex="0"><code>BindsTo=immich.mount
After=immich.mount
</code></pre><p>If you unmount the mount point, it will also properly bring down the service. The service won&rsquo;t start if the mount point
is missing. I&rsquo;ve had the issue with containers seeing blank mount points more times than I&rsquo;d like to admit, and this has
eliminated this issue for me.</p>
<p><code>systemd</code> has received a lot of hate online, and I don&rsquo;t think it&rsquo;s fair. The ease with which you can set up
dependencies on your system, set up resource limits, make services more restricted to improve the security posture is
great and allows me to and avoid all sorts of failure modes. Production services that I&rsquo;m responsible for make use of
these <code>systemd</code> features, with great results.</p>
<p>For services that need to be public, such as Nextcloud and its public shareable links, this approach won&rsquo;t work,
obviously, but for things that only you and your family members use, this is a viable approach.</p>
<h2 id="conclusion">
  <a class="heading-anchor" href="#conclusion">Conclusion<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>This setup works well enough for me to confidently host my blog and self-hosted services off of it. I&rsquo;ve hit a lot of
paper cuts and frustrations along the way, but after following this guide, you don&rsquo;t have to do the same.</p>
<p>Yes, VLAN-s are intentionally missing from this guide. I&rsquo;ll get to them eventually, maybe by Q4 2037 given my lack of
free time.</p>
<p>And no, IPv6 isn&rsquo;t complicated, it&rsquo;s just different from what everyone is used to. If we started out with IPv6 right
from the get-go, <a href="https://www.ietf.org/archive/id/draft-thain-ipv8-00.html">we wouldn&rsquo;t be having dumb arguments online.</a></p>
]]></content:encoded></item><item><title>The most unstable computer in my fleet is now the most critical one</title><link>https://ounapuu.ee/posts/2026/04/04/lattepanda/</link><pubDate>Sat, 04 Apr 2026 06:00:00 +0300</pubDate><author>ihavesomethoughtsonyourblog@ounapuu.ee (Herman Õunapuu)</author><guid>https://ounapuu.ee/posts/2026/04/04/lattepanda/</guid><description>No way that this can go wrong. Not at all.</description><content:encoded><![CDATA[<img src="https://ounapuu.ee/posts/2026/04/04/lattepanda/media/cover_hu_73aa3329770b1e79.jpg" width="1200" height="630" alt="The most unstable computer in my fleet is now the most critical one" /><p>Remember <a href="/posts/2026/01/16/worst-media-server/">that failed experiment</a> where I ran Jellyfin off of a LattePanda V1?</p>
<p>Do you recall all the parts where I said what this single board computer <em><strong>cannot</strong></em> do?</p>
<p>Yeah, I remember.</p>
<p>Then I took it and put the two of the most critical services running on it: the blog you&rsquo;re reading right now, and my
Wireguard setup.</p>
<p>Trust me, it makes more sense with some context. The board is incapable of doing anything else other than serving
content from the eMMC module, and it has a functioning network port. It doesn&rsquo;t seem to crash in these scenarios.</p>
<p>When I try anything else with this board, especially things that include USB connectivity, things break. This makes the
board ideal for a light workload that needs to be up 24/7.</p>
<p>The biggest threat to my uptime is not internet connectivity or loss of power (although that did happen for the first
time in a year recently), it&rsquo;s me getting new ideas to try out on my setup, which results in downtime. This board is so
unreliable for trying those ideas out that it removes any and all temptation to <em>do</em> that, resulting in a computer that
has the highest chance of actually being up and running for a very long time.</p>
<p>To play things safe, I used an IKEA SJÖSS 20W USB-C power adapter that I got for 3 EUR, with a cheap USB-C to USB-A
adapter thrown into the mix. It looks janky, but the adapter outputs 5V 3A, which makes it the beefiest power adapter
that I have in my fleet for plain USB-A powered devices.</p>
<p>I then hit the board with some <code>stress</code> commands, including hitting the 2 GB of memory. It ran really well for days, no
issues at all.</p>









<figure class="center">
  <a href="/posts/2026/04/04/lattepanda/media/stressing-out.jpg">
    <img src="/posts/2026/04/04/lattepanda/media/stressing-out_hu_b4ea99889312878a.webp"
     width="1000"
     height="751"
     loading="lazy"
     decoding="async"
     alt="Less espresso, more Lattepanda, more stresso.">

  </a>
  <figcaption class="center">Less espresso, more Lattepanda, more stresso.</figcaption>
</figure>

<p>I also improved the cooling situation. I am now a proud owner of an assortment of M2, M2.5 and M3 screws and bits, and
equipped with a Makita cordless drill, I made some mounting holes into an old aluminium server heat sink. The drilling
was a complete hack job, everything was misaligned, but it was good enough. Certainly better than holding the board and
heat sink together with thin velcro strips. The cooling performance is completely adequate, the board hits a maximum of
65°C with the heat sink facing down. This is well below the point at which the board starts to throttle its CPU.</p>









<figure class="center">
  <a href="/posts/2026/04/04/lattepanda/media/side-view.jpg">
    <img src="/posts/2026/04/04/lattepanda/media/side-view_hu_62184c91343891eb.webp"
     width="1000"
     height="482"
     loading="lazy"
     decoding="async"
     alt="Two out of three chips have adequate cooling. Whoops.">

  </a>
  <figcaption class="center">Two out of three chips have adequate cooling. Whoops.</figcaption>
</figure>

<p>The theoretical maximum Wireguard throughput on this board is about 340 Mbps, measured using the
fantastic <a href="https://github.com/cyyself/wg-bench">wg-bench</a> solution.</p>
<p>Remember the part about the USB ports being flaky? Yeah. That didn&rsquo;t stop me from getting a USB Gigabit Ethernet
adapter to remove one of the main limitations of the LattePanda V1. Based off of vibe-recommendations by Claude, I got a
TP-Link UE300 for its alleged low power usage and its availability at a local computer store in Estonia. It seems to
work well enough, you can push gigabit speeds through it measured by <code>iperf3</code>, and the actual Wireguard performance that
I could push through it with an actual workload was at about 420 Mbit/s, higher than indicated by the benchmark, and
plenty fast for most workloads, especially in external networks that are usually slower than that.</p>









<figure class="center">
  <a href="/posts/2026/04/04/lattepanda/media/speed.png">
    <img src="/posts/2026/04/04/lattepanda/media/speed_hu_553c4ae4b61a3ad.webp"
     width="1000"
     height="439"
     loading="lazy"
     decoding="async"
     alt="iperf3 test results on LattePanda V1, with occasional CPU and memory stress tests thrown in.">

  </a>
  <figcaption class="center">iperf3 test results on LattePanda V1, with occasional CPU and memory stress tests thrown in.</figcaption>
</figure>










<figure class="center">
  <a href="/posts/2026/04/04/lattepanda/media/wireguard.png">
    <img src="/posts/2026/04/04/lattepanda/media/wireguard_hu_b67d71a1d5528ffb.webp"
     width="1000"
     height="439"
     loading="lazy"
     decoding="async"
     alt="Copying data from another Wireguard peer through the LattePanda V1.">

  </a>
  <figcaption class="center">Copying data from another Wireguard peer through the LattePanda V1.</figcaption>
</figure>










<figure class="center">
  <a href="/posts/2026/04/04/lattepanda/media/cables.jpg">
    <img src="/posts/2026/04/04/lattepanda/media/cables_hu_d0b5c96d42223007.webp"
     width="1000"
     height="751"
     loading="lazy"
     decoding="async"
     alt="Cable management is not my strength.">

  </a>
  <figcaption class="center">Cable management is not my strength.</figcaption>
</figure>

<p>A few hours after making that
change, <a href="https://news.ycombinator.com/item?id=47597159">a HN post put some mild load on the LattePanda V1,</a> what good
timing.</p>
<p>As of publishing this post, the blog has been running mostly off of the LattePanda V1 for over a month now, with that
gap in it being caused by contemplating getting that USB Ethernet adapter and temporarily running the blog and Wireguard
off of another mini PC during that time.</p>
<p>Did you notice?</p>
<h2 id="2026-july-update">
  <a class="heading-anchor" href="#2026-july-update">2026 July update<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>I was running out of room and network ports on my router, so I decided to stop this experiment. During all this time,
there were no issues with the LattePanda V1, except when I accidentally bumped into its power cable and made it go
offline. Oops.</p>
]]></content:encoded></item><item><title>The day IPv6 went away</title><link>https://ounapuu.ee/posts/2025/11/06/ipv6-is-kill/</link><pubDate>Thu, 06 Nov 2025 06:00:00 +0200</pubDate><author>ihavesomethoughtsonyourblog@ounapuu.ee (Herman Õunapuu)</author><guid>https://ounapuu.ee/posts/2025/11/06/ipv6-is-kill/</guid><description>Well, that's a first.</description><content:encoded><![CDATA[<img src="https://ounapuu.ee/posts/2025/11/06/ipv6-is-kill/media/cover_hu_464d143a945f4e35.jpg" width="1200" height="630" alt="The day IPv6 went away" /><p>I take pride in <a href="/posts/2024/06/24/back-to-roots/">hosting my blog on a 13-year old ThinkPad acting as a home server</a>,
but sometimes it&rsquo;s kind of a pain. It&rsquo;s only fair that I cover the downsides of this setup in contrast to all the
positives.</p>
<p>Yesterday, I happened to notice that a connection to a backup endpoint was gone. Okay, happens sometimes. Then I went
into the router and noticed that hey, that&rsquo;s odd, there&rsquo;s no WAN6 connection showing up. All gone. Just as if I had gone
back to a crappy ISP that only provides IPv4<a href="https://www.elisa.ee/">!</a></p>
<p>Restarting the interface did not work, but a full router restart worked.</p>
<p>Since the IPv4 address and IPv6 prefix are all dynamic, that meant that my DNS entries had just gone stale. I do
have a custom DNS auto-updater script for my DNS provider, but DNS propagation takes time. Luckily not a lot of time, my
uptime checker only reported downtime of 5-15 minutes, depending on the domain.</p>
<p>Here&rsquo;s what it looked like on OpenWRT.</p>









<figure class="center">
  <a href="/posts/2025/11/06/ipv6-is-kill/media/openwrt.png">
    <img src="/posts/2025/11/06/ipv6-is-kill/media/openwrt_hu_cc5a6092998c51ea.webp"
     width="1000"
     height="257"
     loading="lazy"
     decoding="async"
     alt="The IPv4 only experience.">

  </a>
  <figcaption class="center">The IPv4 only experience.</figcaption>
</figure>










<figure class="center">
  <a href="/posts/2025/11/06/ipv6-is-kill/media/interfaces.png">
    <img src="/posts/2025/11/06/ipv6-is-kill/media/interfaces_hu_a95c298721c380e6.webp"
     width="1000"
     height="631"
     loading="lazy"
     decoding="async"
     alt="Interface was still there, just not in a happy state.">

  </a>
  <figcaption class="center">Interface was still there, just not in a happy state.</figcaption>
</figure>










<figure class="center">
  <a href="/posts/2025/11/06/ipv6-is-kill/media/cli.png">
    <img src="/posts/2025/11/06/ipv6-is-kill/media/cli_hu_9754341e943c5a3d.webp"
     width="344"
     height="356"
     loading="lazy"
     decoding="async"
     alt="Interface down, in pending state.">

  </a>
  <figcaption class="center">Interface down, in pending state.</figcaption>
</figure>

<p>Impact to my blog? Not really noticeable, since IPv4 kept trucking along. Perhaps a few IPv6-only readers may have
noticed this.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>I can always move to a cheap VPS or <em><strong>the cloud</strong></em> at a moments&rsquo; notice, but where&rsquo;s the fun in that? I can
produce <a href="https://aws.amazon.com/message/101925/">AWS levels of uptime</a> at home, <em>thankyouverymuch</em>!</p>
<p>I think I&rsquo;ll now need to figure out some safeguards, even if it means scheduling a weekly router reboot if the WAN6
interface is not up for X amount of time.</p>
<p>That, and better monitoring.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>if you are that person, say hi!&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to run Uptime Kuma in Docker in an IPv6-only environment</title><link>https://ounapuu.ee/posts/2025/08/05/uptime-kuma-ipv6/</link><pubDate>Tue, 05 Aug 2025 21:00:00 +0300</pubDate><author>ihavesomethoughtsonyourblog@ounapuu.ee (Herman Õunapuu)</author><guid>https://ounapuu.ee/posts/2025/08/05/uptime-kuma-ipv6/</guid><description>When you're too cheap to pay for an IPv4 address but you'd really like to monitor your services.</description><content:encoded><![CDATA[<img src="https://ounapuu.ee/posts/2025/08/05/uptime-kuma-ipv6/media/cover_hu_25de9680b02fc40f.jpg" width="1200" height="630" alt="How to run Uptime Kuma in Docker in an IPv6-only environment" /><p>I use <a href="https://github.com/louislam/uptime-kuma">Uptime Kuma</a> to check the availability of a few services that I run,
with the most important one being my blog. It&rsquo;s really nice.</p>
<p>Today I wanted to set it up on a different machine to help troubleshoot and confirm some latency issues that I&rsquo;ve
observed, and for that purpose I picked the cheapest ARM-based Hetzner Cloud VM hosted in Helsinki, Finland.</p>
<p>Hetzner provides a public IPv6 address for free, but you have to pay extra for an IPv4 address. I didn&rsquo;t want to do that
out of
principle, so I went ahead and copied my Docker Compose definition over to the new server.</p>
<p>For some reason, Uptime Kuma would start up on the new IPv6-only VM, but it was unsuccessful in making requests to my
services, which support both IPv4 and IPv6. The requests would time out and show up as &ldquo;Pending&rdquo; in the UI, and the
service logs complained about not being able to deliver e-mails about the failures.</p>
<p>I confirmed IPv6 connectivity within the container by running <code>docker exec -it uptime-kuma bash</code> and running a
few <code>curl</code> and <code>ping</code> commands with IPv6 flags, had no issues with those.</p>
<p>When I added a public IPv4 address to the container, everything started working again.</p>
<p>I fixed the issue by explicitly disabling the IPv4 network in the Docker Compose service definition, and that did the
trick, Uptime Kuma made successful requests towards my services. It seems that the service defaults to IPv4 due to the
internal Docker network giving it an IPv4 network to work with, and that causes issues when your machine doesn&rsquo;t have
any IPv4 network or public IPv4 address associated with it.</p>
<p>Here&rsquo;s an example Docker Compose file:</p>
<pre tabindex="0"><code>name: uptime-kuma
services:
  uptime-kuma:
    container_name: uptime-kuma
    networks:
      - uptime-kuma
    ports:
      - 3001:3001
    volumes:
      - /path/to/your/storage:/app/data
    image: docker.io/louislam/uptime-kuma
    restart: always
networks:
  uptime-kuma:
    enable_ipv6: true
    enable_ipv4: false
</code></pre><p>That&rsquo;s it!</p>
<p>If you&rsquo;re interested in different ways to set up IPv6 networking in
Docker, <a href="/posts/2024/12/20/docker-ipv6/">check out this overview that I wrote a while ago.</a></p>
]]></content:encoded></item><item><title>The IPv6 situation on Docker is good now!</title><link>https://ounapuu.ee/posts/2024/12/20/docker-ipv6/</link><pubDate>Fri, 20 Dec 2024 19:30:00 +0200</pubDate><author>ihavesomethoughtsonyourblog@ounapuu.ee (Herman Õunapuu)</author><guid>https://ounapuu.ee/posts/2024/12/20/docker-ipv6/</guid><description>It's not often when a piece of software has genuinely improved, which is why this is worth celebrating!</description><content:encoded><![CDATA[<img src="https://ounapuu.ee/posts/2024/12/20/docker-ipv6/media/cover_hu_2d1118bf85d813df.jpg" width="1200" height="630" alt="The IPv6 situation on Docker is good now!" /><p>Good news, everyone! Doing IPv6 networking stuff on Docker is actually good now!</p>
<p>I&rsquo;ve recently started reworking my home server setup to be more IPv6 compatible, and as part of that I learned that
during
the summer of 2024 <a href="https://docs.docker.com/engine/release-notes/27/#ipv6">Docker shipped an update</a> that eliminated a
lot of the configuration and tweaking previously necessary
to support IPv6.</p>
<p>There is no need to change the daemon configuration any longer, it just works on Docker Engine v27 and later.</p>
<h2 id="examples">
  <a class="heading-anchor" href="#examples">Examples<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>If your host has a working IPv6 setup and you want to listen to port 80 on both IPv4 and IPv6, then you don&rsquo;t
have to do anything special. However, the container will only have an IPv4 address internally.
You can verify it by listing all the Docker networks via <code>sudo docker network ls</code> and running
<code>sudo docker network inspect network-name-here</code> for the one associated with your container.</p>
<p>For services like <code>nginx</code> that log the source IP address, this is problematic, as every incoming IPv6 request will be
logged with the Docker network gateway IP address, such as <code>10.88.0.1</code>.</p>
<pre tabindex="0"><code>name: nginx
services:
  nginx:
    container_name: nginx
    ports:
      - 80:80
    image: docker.io/library/nginx
    restart: always
</code></pre><p>If you want the container to have an IPv4 <em>and</em> an IPv6 address within the Docker network, you can create a new network
and enable IPv6 in it.</p>
<pre tabindex="0"><code>name: nginx
services:
  nginx:
    container_name: nginx
    networks:
      - nginx-network
    ports:
      - 80:80
    image: docker.io/library/nginx
    restart: always
networks:
  nginx-network:
    enable_ipv6: true
</code></pre><p>There are situations where it&rsquo;s handy to have a static IP address for a container within the Docker network.
If you need help coming up with an unique local IPv6 address range, you
can <a href="https://unique-local-ipv6.com/">use this tool.</a></p>
<pre tabindex="0"><code>name: nginx
services:
  nginx:
    container_name: nginx
    networks:
      nginx-network
        ipv4_address: 10.69.42.5
        ipv6_address: fdec:cc68:5178::abba
    ports:
      - 80:80
    image: docker.io/library/nginx
    restart: always
networks:
  nginx-network:
    enable_ipv6: true
    ipam:
      driver: default
      config:
        - subnet: &#34;10.69.42.0/24&#34;
        - subnet: &#34;fdec:cc68:5178::/64&#34;
</code></pre><p>If you choose the <a href="https://docs.docker.com/engine/network/drivers/host/">host network driver,</a> your container will
operate within the same networking space as your container host. If the host handles both IPv4 and IPv6 networking, then
your container will happily operate with both. However, due to reduced network isolation, this has some security
implications that you must take into account.</p>
<pre tabindex="0"><code>name: nginx
services:
  nginx:
    container_name: nginx
    network_mode: host
    # ports are not relevant with host network mode
    image: docker.io/library/nginx
    restart: always
</code></pre><p>If you want your container to only accept connections on select interfaces, such as a Wireguard connection, then you will need
to specify the IP addresses in the <code>ports</code> section. Here&rsquo;s one example with both IPv4 and IPv6.</p>
<pre tabindex="0"><code>name: nginx
services:
  nginx:
    container_name: nginx
    networks:
      - nginx-network
    ports:
      - 10.69.42.5:80:80
      - &#34;[fdec:cc68:5178::beef]:80:80&#34;
    image: docker.io/library/nginx
    restart: always
networks:
  nginx-network:
    enable_ipv6: true
</code></pre><h2 id="what-about-podman">
  <a class="heading-anchor" href="#what-about-podman">What about Podman?<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>I&rsquo;ve given up on Podman. Before doing things the IPv6 way, Podman was functional for the most part, requiring a few
tweaks to get things working.</p>
<p>I have not managed to get Podman to play fair with IPv6. No matter what I did, I could not get it to listen to certain
ports and access my services, the ports would always be filtered out.</p>
<h2 id="conclusion">
  <a class="heading-anchor" href="#conclusion">Conclusion<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>I&rsquo;m genuinely happy to see that the IPv6 support has gotten better with Docker, and I hope that this short introduction
helps those out there looking to do things the IPv6 way with containers.</p>
]]></content:encoded></item><item><title>How to build a fleet of networked offsite backups using Linux, WireGuard and rsync</title><link>https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/</link><pubDate>Wed, 11 Dec 2024 06:00:00 +0200</pubDate><author>ihavesomethoughtsonyourblog@ounapuu.ee (Herman Õunapuu)</author><guid>https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/</guid><description>You have offsite backups of your most important data, right? Right???</description><content:encoded><![CDATA[<img src="https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/media/cover_hu_79c3c1903608e4ea.jpg" width="1200" height="630" alt="How to build a fleet of networked offsite backups using Linux, WireGuard and rsync" /><p>Just like most people out there, I have some files that are irreplaceable, such as cat pictures.</p>
<p>At one point I had a few single-board computers sitting idle, namely
the <a href="http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-Zero.html">Orange Pi Zero</a> and
the <a href="https://www.lattepanda.com/lattepanda-v1">LattePanda V1</a>, and a few
1TB SSD-s.</p>
<p>I hate idle hardware, so I did the most sensible thing and assembled a fleet of networked offsite backups for
backing up the most important data.</p>
<p>My setup is based on various flavors of Linux, but the ideas will likely translate well onto other operating systems
and solutions.</p>
<h2 id="networking">
  <a class="heading-anchor" href="#networking">Networking<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>The most important part is the networking. The offsite backup endpoints connect together to my home server over a
WireGuard network. The home server is, well, <em>the server</em>, and backup endpoints are clients.</p>
<p>I like <a href="https://github.com/linuxserver/docker-WireGuard">this WireGuard Docker image</a> a lot because it generates
the server and client configurations automatically, but you can use plain WireGuard or a completely different networking
solution to connect all the devices together. Some use Tailscale to make the setup process easier, but I like to keep
things as self-hosted as possible.</p>
<p>I&rsquo;m not a networking expert, but here&rsquo;s how I&rsquo;ve set up my network. For this example, the WireGuard network operates in
the <code>10.13.69.0/24</code> range.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>To only allow traffic between the devices and avoid tunneling everything through the home server, set the <code>AllowedIPs</code>
setting to <code>AllowedIPs = 10.13.69.0/24,10.13.69.1</code>. We want to be able to access the backup endpoints, and nothing more.</p>
<p>All the devices have a static IP address in that network, such as <code>10.13.69.1</code> for the home server, <code>10.13.69.2</code> for a
backup endpoint and so on.</p>
<p>The <code>PersistentKeepalive = 25</code> option is present in the client configurations so that I don&rsquo;t lose the ability to access
the backup endpoints. With it, all the backup endpoints call back to the home server from time to time. The
aforementioned Docker image automatically adds it to the generated configuration using
the <code>PERSISTENTKEEPALIVE_PEERS=all</code> option.
This setting is <em><strong>crucial.</strong></em> Without it, I sometimes ran into problems trying to connect from my home server to the
backup endpoint, and that&rsquo;s something you can&rsquo;t easily alleviate without having physical access to the backup endpoints,
which are offsite.</p>
<p>Remove the DNS configuration from generated WireGuard client configurations, as you don&rsquo;t need it for this purpose.</p>
<p>Optionally, edit the <code>/etc/hosts</code> file for the home server and backup endpoints so that you can access your backup
endpoints using simple hostnames, like <code>orangepizero</code>. Example row can look like this: <code>10.13.69.6 orangepizero</code>.</p>
<p>if your WireGuard server operates in a network with a dynamic external IP address, as is common with many home internet
connections, I recommend getting yourself a domain name that you can update whenever your IP address changes and using
that in your WireGuard client configurations. Without this, an IP address change will result in your backup endpoints
being inaccessible.</p>
<p>You&rsquo;ll also likely need to set up port forwarding and/or traffic rules for your backup endpoints to be able to connect
back to your WireGuard server.</p>
<p>Once you have the WireGuard connection set up and SSH running on the backup endpoints, you should be able to drop the
backup endpoints into any network that you have permission for. Ask your friends and family, and sweeten the deal by
offering free technical support or help in some other area in return. The cost of running a single-board computer 24/7
is minuscule with the typical power consumption being 1-3W, so that won&rsquo;t be much of a concern.</p>
<h2 id="making-backups">
  <a class="heading-anchor" href="#making-backups">Making backups<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>For making the actual backups themselves, you have all sorts of options.</p>
<p>I rely on <code>rsync</code> to copy the data over. It&rsquo;s simple and it works, that&rsquo;s all I expect from it.
Example command: <code>rsync -aAXvz /folder/to/back/up/ backupuser@backupendpoint:/backup/ --delete</code>.
The files will be compressed during transit with the <code>-z</code> option, and with <code>--delete</code> you&rsquo;ll ensure that the target
folder has all the files from the source, and nothing else.</p>
<p>The backup storage is specified in <code>/etc/fstab</code> with the <code>nofail</code> option present. This ensures that in case the disk
dies, the backup endpoint will still boot properly, allowing me to access the machine to troubleshoot the issue and/or
force a desperate reboot to try to fix things. A good alternative approach is to mount/unmount the remote disk manually
as part
of the backup script.</p>
<p>The backup storage uses the <code>btrfs</code> filesystem, and I use <code>btrbk</code> to take snapshots of the contents. If I accidentally
delete all the files on the backup endpoint, then I can still recover from that situation because the data is still
present in snapshots. 30 days is a good retention period: enough time to save the data in case of an accidental
deletion, but short
enough to avoid the backup disk getting full.</p>
<p>If you don&rsquo;t want to use filesystem-level snapshots, then tools like <code>restic</code> are a good alternative. It can also
operate
over SSH and you can configure snapshot retention policies in your backup script. Just make sure to not lose the
encryption password, and
verify the backups once in a while.</p>
<h2 id="deployment">
  <a class="heading-anchor" href="#deployment">Deployment<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>I manage my backup endpoints using some cobbled-together Ansible roles. I&rsquo;ve perfected it to the point where the only
manual
steps are flashing the OS and setting up the storage, the rest is handled via Ansible.</p>
<p>I&rsquo;d like to share my work here, but it will make Jeff Geerling cry. Maybe one day I&rsquo;ll take the time to improve
things&hellip;</p>
<h2 id="maintenance">
  <a class="heading-anchor" href="#maintenance">Maintenance<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>All the backup endpoints update and reboot themselves regularly. It&rsquo;s just the sensible thing to do.</p>
<p>Every 6-12 months I also do major OS version updates. It&rsquo;s risky because of the whole offsite aspect of the solution,
but so far I haven&rsquo;t been burned yet.</p>
<h2 id="monitoring">
  <a class="heading-anchor" href="#monitoring">Monitoring<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>Monitoring is an area where I have some room for improvement. So far, I&rsquo;ve set up Prometheus node-exporter to all of
the backup endpoints, and my home server keeps track of how the backup endpoints are doing.</p>
<p>This allows me to check once in a while if any of the backup endpoints has fallen off the network, or if the backup disk
is getting full.</p>
<h2 id="issues-ive-faced">
  <a class="heading-anchor" href="#issues-ive-faced">Issues I&rsquo;ve faced<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>I&rsquo;ve had this system running for a few years now, and it&rsquo;s mostly stable! There have been some issues I&rsquo;ve faced as
well,
though. Some are very specific to certain hardware, but I think there&rsquo;s value in mentioning them.</p>
<p><a href="/posts/2023/06/10/how-i-blew-up-my-backup-server/">I once blew up a backup server because of an Ansible configuration issue.</a>
That meant that I had to physically go pick up the server to re-image it.</p>
<p>The Orange Pi Zero was running quite hot, resulting in stability issues, so I put together a really janky cooling
solution.</p>









<figure class="center">
  <a href="/posts/2024/12/11/wireguard-backup-fleet/media/orangepizero.jpg">
    <img src="/posts/2024/12/11/wireguard-backup-fleet/media/orangepizero_hu_689cb39c9001c4e2.webp"
     width="750"
     height="1000"
     loading="lazy"
     decoding="async"
     alt="Cooling. It works.">

  </a>
  <figcaption class="center">Cooling. It works.</figcaption>
</figure>

<p>Hell, I did the same for the LattePanda as well.</p>









<figure class="center">
  <a href="/posts/2024/12/11/wireguard-backup-fleet/media/lattepanduh.jpg">
    <img src="/posts/2024/12/11/wireguard-backup-fleet/media/lattepanduh_hu_b5634f62aec46ed4.webp"
     width="1000"
     height="750"
     loading="lazy"
     decoding="async"
     alt="First version of the cooling upgrade on the LattePanda.">

  </a>
  <figcaption class="center">First version of the cooling upgrade on the LattePanda.</figcaption>
</figure>

<p>It might look horrific but the extra cooling has fixed all the stability problems on both boards.</p>
<p>The lack of a real-time clock on the LattePanda has required me to make its backup script a bit special. I can&rsquo;t rely on
a systemd timer that automatically reboots the machine once in a while, so instead that part is present in the backup
script. The issue is that the LattePanda boots up with the time being set in the past, and once it gets the actual time
from the
network, it will run all sorts of tasks because enough time has passed!
This included the reboot timer as well.</p>
<p>At one point, the power supply on the LattePanda just died, and it was very visible on my graphs. That required a
replacement.</p>









<figure class="center">
  <a href="/posts/2024/12/11/wireguard-backup-fleet/media/lattepanda-psu-failure.png">
    <img src="/posts/2024/12/11/wireguard-backup-fleet/media/lattepanda-psu-failure_hu_83ab55cb07768116.webp"
     width="1000"
     height="725"
     loading="lazy"
     decoding="async"
     alt="Signs that you might have a failing power supply.">

  </a>
  <figcaption class="center">Signs that you might have a failing power supply.</figcaption>
</figure>

<h2 id="conclusion">
  <a class="heading-anchor" href="#conclusion">Conclusion<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>That&rsquo;s how I back up the most important data. I hope that this has given you inspiration to take your own backup
approach to the next level!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>yes, I do plan to move this setup to IPv6 eventually.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Your Wi-Fi might be terrible because of Dynamic Frequency Selection (DFS)</title><link>https://ounapuu.ee/posts/2024/11/11/openwrt-dfs/</link><pubDate>Mon, 11 Nov 2024 06:00:00 +0200</pubDate><author>ihavesomethoughtsonyourblog@ounapuu.ee (Herman Õunapuu)</author><guid>https://ounapuu.ee/posts/2024/11/11/openwrt-dfs/</guid><description>My Wi-Fi kept dropping out until I learned about this fun little feature. Here's how to fix it for good.</description><content:encoded><![CDATA[<img src="https://ounapuu.ee/posts/2024/11/11/openwrt-dfs/media/cover_hu_922176fce2414a28.jpg" width="1200" height="630" alt="Your Wi-Fi might be terrible because of Dynamic Frequency Selection (DFS)" /><p>For a few months, I had issues with my Wi-Fi network. The 2.4 GHz network would be fine, but the 5 GHz one would
suddenly stop working and completely disappear from the available Wi-Fi networks. OpenWRT upgrades also didn&rsquo;t improve
the situation. This was very annoying.</p>
<p>After some discussions with a friend, I learned
about <a href="https://en.wikipedia.org/wiki/Dynamic_frequency_selection">Dynamic Frequency Selection (DFS).</a> Apparently some
channels on the 5 GHz Wi-Fi spectrum are also used by weather and military radars, and those take priority. If such
interference is detected, your Wi-Fi access point should switch to a different channel.</p>
<p>It turns out that some implementations are buggy and mine is one of them.</p>
<p>One quick but permanent fix is to manually select a Wi-Fi channel to operate in.</p>
<p>With OpenWRT, you can get a list of all the available Wi-Fi channels using this command:</p>
<pre tabindex="0"><code>iw list | grep dBm
</code></pre><p>Example output:</p>
<pre tabindex="0"><code>* 2412 MHz [1] (20.0 dBm)
* 2417 MHz [2] (20.0 dBm)
* 2422 MHz [3] (20.0 dBm)
* 2427 MHz [4] (20.0 dBm)
* 2432 MHz [5] (20.0 dBm)
* 2437 MHz [6] (20.0 dBm)
* 2442 MHz [7] (20.0 dBm)
* 2447 MHz [8] (20.0 dBm)
* 2452 MHz [9] (20.0 dBm)
* 2457 MHz [10] (20.0 dBm)
* 2462 MHz [11] (20.0 dBm)
* 2467 MHz [12] (20.0 dBm)
* 2472 MHz [13] (20.0 dBm)
* 5180 MHz [36] (23.0 dBm)
* 5200 MHz [40] (23.0 dBm)
* 5220 MHz [44] (23.0 dBm)
* 5240 MHz [48] (23.0 dBm)
* 5260 MHz [52] (20.0 dBm) (radar detection)
* 5280 MHz [56] (20.0 dBm) (radar detection)
* 5300 MHz [60] (20.0 dBm) (radar detection)
* 5320 MHz [64] (20.0 dBm) (radar detection)
* 5500 MHz [100] (26.0 dBm) (radar detection)
* 5520 MHz [104] (26.0 dBm) (radar detection)
* 5540 MHz [108] (26.0 dBm) (radar detection)
* 5560 MHz [112] (26.0 dBm) (radar detection)
* 5580 MHz [116] (26.0 dBm) (radar detection)
* 5600 MHz [120] (26.0 dBm) (radar detection)
* 5620 MHz [124] (26.0 dBm) (radar detection)
* 5640 MHz [128] (26.0 dBm) (radar detection)
* 5660 MHz [132] (26.0 dBm) (radar detection)
* 5680 MHz [136] (26.0 dBm) (radar detection)
* 5700 MHz [140] (26.0 dBm) (radar detection)
* 5720 MHz [144] (13.0 dBm) (radar detection)
* 5745 MHz [149] (13.0 dBm)
* 5765 MHz [153] (13.0 dBm)
* 5785 MHz [157] (13.0 dBm)
* 5805 MHz [161] (13.0 dBm)
* 5825 MHz [165] (13.0 dBm)
* 5845 MHz [169] (13.0 dBm)
* 5865 MHz [173] (13.0 dBm)
</code></pre><p>Notice the ones with <code>(radar detection)</code> at the end? Those are the potentially problematic channels. We&rsquo;re going to
avoid them from now on by picking a specific channel to use.</p>
<p>When it comes to the choice of the channels themselves, you&rsquo;ll also have to consider the channel width. If you pick a
channel next to one of the radar detection ones and with a large channel width, you might still run into issues.</p>
<p>Choosing a specific channel also comes with bandwidth and range trade-offs. If you don&rsquo;t care much for those, go for the
lowest one and with 40 MHz width. Picking the optimal Wi-Fi channel and channel width configuration for your specific
needs is better explained by other resources.</p>









<figure class="center">
  <a href="/posts/2024/11/11/openwrt-dfs/media/openwrt-luci.png">
    <img src="/posts/2024/11/11/openwrt-dfs/media/openwrt-luci_hu_762e2ab7b34fdf81.webp"
     width="902"
     height="462"
     loading="lazy"
     decoding="async"
     alt="Choosing a specific channel in OpenWRT using the GUI (LuCI).">

  </a>
  <figcaption class="center">Choosing a specific channel in OpenWRT using the GUI (LuCI).</figcaption>
</figure>

<p>I recommend getting a Wi-Fi spectrum analysis app that shows you the channels that are least populated by neighboring
Wi-Fi access points.</p>
<p>It is also possible to define the list of channels that the Wi-Fi AP can automatically choose from using the <code>channels</code>
option for the wireless interface. We can use this setting to avoid the radar detection channels completely. This
setting doesn&rsquo;t seem to be configurable via the graphical interface (LuCI), but you can change it
in <code>/etc/config/wireless</code> using the command line and <code>vi</code>, over SSH.</p>
<p>More information about this option and others can be found
in <a href="https://openwrt.org/docs/guide-user/network/wifi/basic#common_options">OpenWRT documentation.</a></p>
<p>Based on our example, a configuration that avoids radar detection frequencies can look something like this:</p>
<pre tabindex="0"><code>config wifi-device &#39;radio0&#39;
        option type &#39;mac80211&#39;
        option path &#39;pci0000:00/0000:00:00.0&#39;
        option channel &#39;auto&#39;
        option channels &#39;36 40 44 48 149 153 157 161 165 169 173&#39;
        option band &#39;5g&#39;     
        option htmode &#39;VHT40&#39;
        option country &#39;EE&#39;    
        option cell_density &#39;0&#39;
</code></pre><p>Using a manually specified channel has resulted in no Wi-Fi related issues for over half a year.</p>
<p>I consider this a permanent fix.</p>
<p>If you&rsquo;re using a PC and don&rsquo;t want to mess with Wi-Fi issues ever again, then just run some Ethernet cables. It&rsquo;s worth
it.</p>
]]></content:encoded></item><item><title>OpenWRT, ISP modem and dynamic IP addresses: how to fix connectivity issues without rebooting your router every time</title><link>https://ounapuu.ee/posts/2024/05/20/openwrt-connectivity-fix/</link><pubDate>Mon, 20 May 2024 06:00:00 +0300</pubDate><author>ihavesomethoughtsonyourblog@ounapuu.ee (Herman Õunapuu)</author><guid>https://ounapuu.ee/posts/2024/05/20/openwrt-connectivity-fix/</guid><description>The solution proposed might be a bit specific for my particular setup, but hopefully useful to someone out there.</description><content:encoded><![CDATA[<img src="https://ounapuu.ee/posts/2024/05/20/openwrt-connectivity-fix/media/cover_hu_e82e7b9beb9d68f.jpg" width="1200" height="630" alt="OpenWRT, ISP modem and dynamic IP addresses: how to fix connectivity issues without rebooting your router every time" /><p><a href="https://elisa.ee/">My current ISP</a> provides an internet connection over a
copper wire. To use it,
I have a crappy modem (Technicolor CGA2121, DOCSIS 3.0). It&rsquo;s running in bridge
mode,
meaning that all it does is convert the signal running over the coax cable
into plain old Ethernet.</p>
<p>My main networking device is a TP-Link Archer C7 v5. It runs OpenWRT. This
router/Wi-Fi AP box connects to the modem and handles everything, including
getting a public
IPv4 address from the ISP.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>After a power outage or my ISP doing maintenance, the public IP address has
usually changed. This wouldn&rsquo;t be a problem if I just stuck to the ISP-approved
modem.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>With my setup, there was a problem. The OpenWRT box would try to operate with
the IPv4 address that it was
given since the DHCP lease had not yet expired. However, this meant that there
was no internet
connectivity. A reboot of the OpenWRT box would resolve the issue.</p>
<p>This manual workaround wasn&rsquo;t good enough for me. It would be quite problematic
if this issue happened while I was away from home because I&rsquo;d still like to
access
my home server.</p>
<p>After traversing OpenWRT forums and consulting the Slack workspace of my local
hackerspace, I found that bringing up the WAN interface again would result in
the OpenWRT box getting a new public IPv4 address. Problem solved!</p>
<p>To automate this workaround, I created a single crontab entry in the OpenWRT
box. This is
also configurable in a graphical user interface as long as you
have <a href="https://openwrt.org/docs/guide-user/luci/start">LuCI installed.</a></p>
<p>The crontab entry looks like this:</p>
<p><code>*/5 * * * * /bin/ash -c '/bin/ping -c 3 8.8.8.8 &gt; /dev/null || /sbin/ifup wan'</code></p>
<p>Every 5 minutes, the router pings Google&rsquo;s DNS server. If that command succeeds,
then the internet connection works and that&rsquo;s it. If the ping fails, then the
other half of the shell command is executed, which brings up the <code>wan</code> interface
on my router.</p>
<p>Feel free to use a different IP address to test with. Your WAN network interface
might also have a different name.</p>
<p>The downside of this solution is that if the server you&rsquo;re using to verify your
internet connection is down or refuses pings, then you&rsquo;ll be causing
interruptions in your home network every 5 minutes.</p>
<p>Talking to the ISP about this issue was something I considered as well. Then
I remembered that it took me 1.5 months of fighting chatbots and repeating the
same information to different customer care agents to use my own modem that&rsquo;s
identical to the one the ISP uses. That&rsquo;s a <em>hell no</em> from me.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I really should get around to drilling those holes in the apartment
building to get access to a fiber connection.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>using the ISP-approved box would introduce a whole other set of problems
because they are surprisingly low quality.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>The little Wi-Fi AP that could</title><link>https://ounapuu.ee/posts/2020/07/23/the-little-wifi-ap-that-could/</link><pubDate>Thu, 23 Jul 2020 01:17:30 +0300</pubDate><author>ihavesomethoughtsonyourblog@ounapuu.ee (Herman Õunapuu)</author><guid>https://ounapuu.ee/posts/2020/07/23/the-little-wifi-ap-that-could/</guid><description>Depends on your definition of _could_.</description><content:encoded><![CDATA[<img src="https://ounapuu.ee/media/cover_hu_4fe4cf2661554252.jpg" width="1200" height="630" alt="The little Wi-Fi AP that could" /><p>I have a bad habit of testing things whenever a &ldquo;good&rdquo; idea pops into my head. This is a short overview of one of them.</p>
<p>The <a href="http://linux-sunxi.org/Xunlong_Orange_Pi_Zero">Orange Pi Zero</a> is a SBC (single board computer) that has a slow 32-bit ARM 4 core CPU, 512MB of RAM and no display output.
It&rsquo;s actually quite OK for many tasks, such as reverse proxy (assuming 100Mbit/s is enough for you), low-performance
NAS (assuming you are fine with 10-12MB/s file transfer speeds) or a Syncthing relay. Oh, and you can also turn it into
a Wi-Fi access point.</p>









<figure class="center">
  <a href="/posts/2020/07/23/the-little-wifi-ap-that-could/media/wifi-ap.jpg">
    <img src="/posts/2020/07/23/the-little-wifi-ap-that-could/media/wifi-ap_hu_b66fbeeb649becd7.webp"
     width="1067"
     height="800"
     loading="lazy"
     decoding="async"
     alt="The Wi-Fi access point in its natural habitat.">

  </a>
  <figcaption class="center">The Wi-Fi access point in its natural habitat.</figcaption>
</figure>

<h2 id="why">
  <a class="heading-anchor" href="#why">Why?<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>Because <a href="https://www.armbian.com/">Armbian</a> made it really easy to test out and I was interested in seeing what kind of
performance an old USB Wi-Fi dongle could offer. This cheap AP could also come into handy in situations where my main
access point dies for some reason.</p>
<h2 id="how">
  <a class="heading-anchor" href="#how">How?<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>Go to the <a href="https://www.armbian.com/orange-pi-zero/">Armbian device page for your SBC</a> and download the latest image, then
write it to your microSD card using the tool of your choice, start your SBC and finish the initial setup.</p>
<p>After all that is done, start <code>armbian-config</code> as root. This tool allows you to do many things over a terminal UI, including
a no-hassle method of setting up an Wi-Fi AP.</p>
<p>The option you are looking for is under <code>Network -&gt; Hotspot</code>.</p>









<figure class="center">
  <a href="/posts/2020/07/23/the-little-wifi-ap-that-could/media/armbian-config-1.png">
    <img src="/posts/2020/07/23/the-little-wifi-ap-that-could/media/armbian-config-1_hu_86a28d26c03eeca9.webp"
     width="710"
     height="403"
     loading="lazy"
     decoding="async"
     alt="This image will likely be way out of date in a couple of years.">

  </a>
  <figcaption class="center">This image will likely be way out of date in a couple of years.</figcaption>
</figure>

<p>Follow the on-screen instructions to set everything up. After the initial setup is done, make sure to navigate to the same
menu again to change the SSID and the Wi-Fi password because the defaults are horribly insecure.</p>
<p>After all this you should be good to go!</p>
<h2 id="issues-and-troubleshooting-tips">
  <a class="heading-anchor" href="#issues-and-troubleshooting-tips">Issues and troubleshooting tips<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>It would not be a proper project if something didn&rsquo;t go wrong in the process.</p>
<p>Before starting this configuration, run <code>ip a</code> to see if your Wi-Fi device is present. In my case the Orange Pi Zero also
has onboard Wi-Fi, but that particular one has poor support, so I use an USB Wi-Fi dongle instead.</p>
<p>If your Wi-Fi device is not showing up, make sure that the correct firmware is present. In my case I had to install
<code>firmware-atheros</code> package so that my <code>ath9k_htc</code> based Wi-Fi adapter would start working. <code>dmesg</code> and <code>lsusb -v</code> can help determine
what chip your Wi-Fi adapter is using and what driver it needs.</p>
<p>The Orange Pi Zero that I have could set everything up, but it would crash hard with the logs not really showing much.
After testing the USB Wi-Fi adapter in all the different USB ports with no success, I opted to use <code>armbian-config</code> menu <code>System -&gt; CPU</code>
to limit the CPU clock speed to 480MHz and set the CPU governor to <code>performance</code> to rule out power delivery issues that might be caused by
variations in CPU power usage. That seems to have done the trick, because after that change the Wi-Fi AP has been rock solid.</p>
<h2 id="testing-and-conclusions">
  <a class="heading-anchor" href="#testing-and-conclusions">Testing and conclusions<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>After setting it up and tweaking it a bit I started testing this thing out. The AP had no major issues
throughout the day and did not crash.</p>
<p>The performance, however, isn&rsquo;t anything to write home about. Speed maxes out at 30Mbit/s and is generally lower than that,
especially when more than one client is connected. You can do things like Steam Remote Play (stream your game over your home network),
but the quality will suffer. It was still quite fun to mess around in GTA V regardless of the image quality and during ~1h
of play time there were only 4-5 stutters that were caused by the network.</p>









<figure class="center">
  <a href="/posts/2020/07/23/the-little-wifi-ap-that-could/media/i-am-speed.png">
    <img src="/posts/2020/07/23/the-little-wifi-ap-that-could/media/i-am-speed_hu_a2e4f1e9419a97f.webp"
     width="223"
     height="185"
     loading="lazy"
     decoding="async"
     alt="I am speed.">

  </a>
  <figcaption class="center">I am speed.</figcaption>
</figure>

<p><strong>Would I use this as my main Wi-Fi access point?</strong></p>
<p>Definitely not. Any half-decent router will out-perform this setup.</p>
<p><strong>Can it still be useful?</strong></p>
<p>Definitely. A crappy Wi-Fi AP is better than no AP.</p>
<h2 id="parts-list">
  <a class="heading-anchor" href="#parts-list">Parts list<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<ul>
<li>Orange Pi Zero SBC (512MB RAM)</li>
<li>Orange Pi Zero expansion board (adds two USB 2.0 ports)</li>
<li>Cheap case for the Orange Pi Zero</li>
<li>32GB Sandisk microSD card</li>
<li>TP-Link WN-722N USB Wi-Fi adapter</li>
<li>Official Raspberry Pi microUSB power adapter</li>
</ul>
<h2 id="update-2020-08-22">
  <a class="heading-anchor" href="#update-2020-08-22">Update (2020-08-22)<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>It&rsquo;s still working and is happily serving as a <em>temporary</em> Wi-Fi access point.</p>
<p>During additional testing I could also hit 40 Mbps speeds when testing in an environment with
far fewer competing Wi-Fi access points nearby.</p>
<h2 id="update-2020-08-30">
  <a class="heading-anchor" href="#update-2020-08-30">Update (2020-08-30)<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>It&rsquo;s still running well and it seems that it won&rsquo;t be a simple temporary
solution after all.</p>
<h2 id="update-2020-11-30-approximately">
  <a class="heading-anchor" href="#update-2020-11-30-approximately">Update (2020-11-30, approximately)<svg class="heading-anchor__icon" viewBox="0 0 24 24" width="0.75em" height="0.75em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></a>
</h2>
<p>This setup has now been retired. It had 0 issues throughout its lifespan as a Wi-Fi access point, but I replaced
it with a more standard Wi-Fi access point so that this board can be used somewhere else.</p>
<p>The setup did surprisingly well and even survived nightly automated updates and restarts, which can be attributed to the good
work that <a href="https://www.armbian.com/">the Armbian project</a> has done.</p>
]]></content:encoded></item></channel></rss>