<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Slightly Nerdy</title><link>https://s-n.me/</link><description/><atom:link href="https://s-n.me/feed/" rel="self"/><lastBuildDate>Fri, 03 Apr 2026 22:50:00 +0100</lastBuildDate><item><title>NixOS is great for self-hosting stuff</title><link>https://s-n.me/2026/04/self-hosting-nixos-nicities/</link><description>&lt;p&gt;&lt;em&gt;This isn't a how-to or guide, just a show and tell of something I think is cool.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I've been using NixOS for a few years now. I continue to find it frustrating, in large part because I still haven't internalised the language and how everything hangs together to build a system. Maybe I'll get there eventually, but in the mean time I know just enough to &lt;strike&gt;be dangerous&lt;/strike&gt; assemble useful collections of services.&lt;/p&gt;
&lt;p&gt;For a server, there's something magical about having pre-written modules to enable and configure a whole bunch of services.&lt;/p&gt;
&lt;p&gt;This week, after a particularly trying experience with Google Docs at work, I wanted to try out &lt;a href="https://hedgedoc.org/"&gt;HedgeDoc&lt;/a&gt;. So I threw up a copy on my home server by writing this file and dropping it into my Nix repo, building on what I've setup previously...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# I run a bunch of services inside a NixOS container,&lt;/span&gt;
  &lt;span class="c1"&gt;# which is &amp;quot;system&amp;quot; container (ala Incus or Proxmox)&lt;/span&gt;
  &lt;span class="c1"&gt;# rather than an application container (ala Docker)&lt;/span&gt;
  &lt;span class="c1"&gt;# I&amp;#39;ve got a wildcard DNS entry pointing to this&lt;/span&gt;
  &lt;span class="c1"&gt;# container&amp;#39;s IP&lt;/span&gt;
  containers&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;intsvcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;bindMounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;/var/lib/hedgedoc&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# on the host /services is a ZFS dataset, and&lt;/span&gt;
        &lt;span class="c1"&gt;# I create a dataset for each application.&lt;/span&gt;
        &lt;span class="c1"&gt;# By being in here, it&amp;#39;ll get snapshotted&lt;/span&gt;
        &lt;span class="c1"&gt;# every 10 minutes and sent over to my Netcup VPS&lt;/span&gt;
        &lt;span class="c1"&gt;# for fast recovery if I need it, in addition&lt;/span&gt;
        &lt;span class="c1"&gt;# to a daily snapshot that&amp;#39;s captured by restic,&lt;/span&gt;
        &lt;span class="c1"&gt;# stored locally and rsync&amp;#39;d to a Hetzner Storage&lt;/span&gt;
        &lt;span class="c1"&gt;# Box&lt;/span&gt;
        &lt;span class="ss"&gt;hostPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/services/hedgedoc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;isReadOnly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="ss"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# I use traefik as my frontend load balancer, it&amp;#39;s&lt;/span&gt;
      &lt;span class="c1"&gt;# configured elsewhere with it&amp;#39;s basic settings and &lt;/span&gt;
      &lt;span class="c1"&gt;# wired up with a wildcard LetsEncrypt cert managed&lt;/span&gt;
      &lt;span class="c1"&gt;# by the NixOS `services.acme` module.&lt;/span&gt;
      &lt;span class="c1"&gt;# Here, I just need to tell it about my new service&lt;/span&gt;
      services&lt;span class="o"&gt;.&lt;/span&gt;traefik&lt;span class="o"&gt;.&lt;/span&gt;dynamicConfigOptions&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;loadBalancer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;servers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://localhost:3001&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        routers&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Host(`docs.mydomain`)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;docs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;tls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      services&lt;span class="o"&gt;.&lt;/span&gt;gatus&lt;span class="o"&gt;.&lt;/span&gt;settings&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;endpoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;# I use Gatus to keep an eye on my services, alert&lt;/span&gt;
        &lt;span class="c1"&gt;# me if they&amp;#39;re down, or the certifate renewal is&lt;/span&gt;
        &lt;span class="c1"&gt;# getting nearer than it really should. I just need&lt;/span&gt;
        &lt;span class="c1"&gt;# to add an entry into it&amp;#39;s endpoints list...&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hedgedoc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Intsvcs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;30s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://docs.mydomain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;conditions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;[STATUS] == 200&amp;quot;&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;[CERTIFICATE_EXPIRATION] &amp;gt; 240h&amp;quot;&lt;/span&gt;
          &lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="ss"&gt;alerts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ntfy&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;];&lt;/span&gt;
      services&lt;span class="o"&gt;.&lt;/span&gt;victoriametrics&lt;span class="o"&gt;.&lt;/span&gt;prometheusConfig&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;scrape_configs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;# victoria metrics collects all the metrics from my&lt;/span&gt;
        &lt;span class="c1"&gt;# various services, we just need to let it know&lt;/span&gt;
        &lt;span class="c1"&gt;# about this new one...&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;job_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;hedgedoc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;static_configs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://127.0.0.1:3001/metrics&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="ss"&gt;relabel_configs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;source_labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__address__&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
              &lt;span class="ss"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;.*&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="ss"&gt;target_label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;instance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="ss"&gt;replacement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;intsvcs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;];&lt;/span&gt;
      services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;postgresql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# quite a few of the services running in this container&lt;/span&gt;
        &lt;span class="c1"&gt;# make use of postgres, sometimes the NixOS module does all&lt;/span&gt;
        &lt;span class="c1"&gt;# the work for me, other times, like this one, I just need&lt;/span&gt;
        &lt;span class="c1"&gt;# to add to the existing configuration to let it know I&amp;#39;d like&lt;/span&gt;
        &lt;span class="c1"&gt;# a new DB and user (relies on a system user, which the hedgedoc&lt;/span&gt;
        &lt;span class="c1"&gt;# module will create for us)&lt;/span&gt;
        &lt;span class="ss"&gt;ensureUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;hedgedoc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="ss"&gt;ensureDBOwnership&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="ss"&gt;ensureDatabases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;hedgedoc&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;hedgedoc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# and finally the service itself, a handful of lines&lt;/span&gt;
        &lt;span class="c1"&gt;# here to get the hedgedoc config right for my selfup&lt;/span&gt;
        &lt;span class="c1"&gt;# and we&amp;#39;re good to go.&lt;/span&gt;
        &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;dialect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;postgresql&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="ss"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/run/postgresql&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="ss"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;hedgedoc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
          &lt;span class="ss"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;docs.mydomain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;protocolUseSSL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;allowOrigin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;docs.mydomain&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="ss"&gt;allowAnonymous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="c1"&gt;# not shown are a handful of extra lines configuring&lt;/span&gt;
          &lt;span class="c1"&gt;# it for OAuth2 authentication with Keycloack, the one&lt;/span&gt;
          &lt;span class="c1"&gt;# bit of manual setup I had to do, creating a new client&lt;/span&gt;
          &lt;span class="c1"&gt;# there and dropping in the details here&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A minute of &lt;code&gt;nixos-rebuild&lt;/code&gt; later, and HedgeDoc is up and running.&lt;/p&gt;
&lt;p&gt;I like how I can divide up the configuration for common components (Traefik, PostgreSQL, Gatus and VictoriaMetrics) so that the relevant parts live along the services they're being used for.&lt;/p&gt;
&lt;p&gt;If I wanted to get fancier, I could save myself some boilerplate and make a module with a small handful of inputs (hostnames, ports, paths) that turns all the common stuff into a couple of lines.&lt;/p&gt;
&lt;p&gt;Maybe I'll do that next, or maybe it's time I got around to having native PostgreSQL backups instead of relying on just ZFS snapshots, &lt;a href="https://search.nixos.org/options?channel=25.11&amp;amp;query=services.postgresqlBackup"&gt;&lt;code&gt;services.postgresqlBackup&lt;/code&gt;&lt;/a&gt; should make that a breeze.&lt;/p&gt;
&lt;p&gt;And if I decide in a couple of weeks that HedgeDoc isn't for me? I can just delete the file, &lt;code&gt;nixos-rebuild&lt;/code&gt; and it's gone.&lt;/p&gt;</description><pubDate>Fri, 03 Apr 2026 22:50:00 +0100</pubDate><guid>https://s-n.me/2026/04/self-hosting-nixos-nicities/</guid><category>misc</category></item><item><title>Disabling Javascript</title><link>https://s-n.me/2026/04/disabling-javascript/</link><description>&lt;p&gt;I was reading a blog post&lt;sup&gt;&lt;a href="#fn.1"&gt;1&lt;/a&gt;&lt;/sup&gt; the other day, and in my usual ADHD way, was flicking back and forth to other tabs. I got a little confused when I went to go back to it, and the title and favicon had changed to something entirely different. Returning to the tab, an overlay had been placed over the page, making the case for disabling Javascript by default, and linking to &lt;a href="https://disable-javascript.org/"&gt;disable-javascript.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It turns out, you don't need to do anything as drastic as disabling JS for the entire browser these days, you can ask uBlock Origin to instead have it disabled by default and selectively allow it as needed.&lt;/p&gt;
&lt;p&gt;So I gave it a try. Here's what I found...&lt;/p&gt;
&lt;h2&gt;The frustrating parts&lt;/h2&gt;
&lt;p&gt;Many sites straight up don't work, and of those ~20% show a message about requiring Javascript to function, the rest leave you in limbo.&lt;/p&gt;
&lt;p&gt;Of these, some make sense given their purpose, and for others it's just baffling why they're built that way.&lt;/p&gt;
&lt;p&gt;One site containing an article had some coloured blocks and no text, but clicking the Firefox reader mode presented the otherwise invisible article.&lt;/p&gt;
&lt;p&gt;It's a shame Mastodon doesn't return the contents of a toot when directly linked to, but it does at least let you know directly that it needs Javascript.&lt;/p&gt;
&lt;h2&gt;The PEBKAC part&lt;/h2&gt;
&lt;p&gt;After a few days, I forgot about it, and ocassionally landed on a new site, or one of my own self-hosted services I'd not visited in a while and thought something was wrong with my Internet. The pink (instead of the usual grey) blocked resource counter in the uBlock extension is a good, but not always consistent reminder of this.&lt;/p&gt;
&lt;h2&gt;The good parts&lt;/h2&gt;
&lt;p&gt;Many sites mostly work, and are even improved. All those data sharing consents, social login prompts, giant begging overlays and demands to register, "this is your last free article", etc just stop existing. News sites especially are much improved, usually at the cost of non-working embedded videos and articles where they've tried to do fancy presentation tricks. And clicking around just feels snappier.&lt;/p&gt;
&lt;p&gt;And then there's the satisfaction of all the sites that work just fine where you see that uBlock has blocked Javascript and know that whatever it was, it probably wasn't there to improve your experience.&lt;/p&gt;
&lt;h2&gt;In conclusion&lt;/h2&gt;
&lt;p&gt;Parts of the web are greatly enhanced by turning Javascript off. Sometimes it's annoying having to do three extra clicks every time you visit a new site that doesn't function without it.&lt;/p&gt;
&lt;p&gt;For me, it's worthwhile, so I'm going to stick with it.&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;&lt;a id="fn.1"&gt;1&lt;/a&gt;&lt;/sup&gt; With apologies for not crediting the blog, I can't recall it now, and because the post was entirely unrelated, I've not been able to find it again. But you can take a look &lt;a href="https://xn--gckvb8fzb.com/"&gt;here&lt;/a&gt;, where the same code is running. Just click away to another tab for a moment.&lt;/p&gt;</description><pubDate>Fri, 03 Apr 2026 08:45:00 +0100</pubDate><guid>https://s-n.me/2026/04/disabling-javascript/</guid><category>misc</category></item><item><title>Building a NixOS router for AAISP - L2TP Failover over 4G</title><link>https://s-n.me/building-a-nixos-router-for-aaisp-l2tp-failover-over-4g</link><description>&lt;p&gt;Following on from &lt;a href="https://s-n.me/building-a-nixos-router-for-a-uk-fttp-isp-the-basics"&gt;setting up the basics of our router&lt;/a&gt;, here's how I add a second WAN connection via a 4G router, and use &lt;a href="https://www.aa.net.uk/broadband/l2tp-service/"&gt;Andrews &amp;amp; Arnold's L2TP service&lt;/a&gt; to keep my public IPv4 and IPv6 networks available in the event of a failure of my fibre service.&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;You can simplify the configuration here to just use the 4G connection directly if you don't have an ISP that's quite so fancy. &lt;a href="https://nerdout.club/@stephen/"&gt;Let me know&lt;/a&gt; if you'd like me to write about that.&lt;/p&gt;
&lt;p&gt;The 4G router is one I picked up a while ago from the Three network, a ZTE MF286D. I do nothing special to it's configuration, and have it running in router mode. It does offer a bridge mode where the external IPs are passed to a connected device, but we don't need that here.&lt;/p&gt;
&lt;p&gt;I have an &lt;a href="https://amzn.to/3VDBiIq"&gt;80Gb/month prepaid 26 month SIM&lt;/a&gt; (affiliate link) from Safecom that uses the Three network. It was £60 when I bought it, and is more expensive now, but it's worth checking out there various options over time to get the best prices. I reckon it should cover 2-4 days in a month, so long as we avoid watching a lot of streaming TV.&lt;/p&gt;
&lt;p&gt;Things are configured such that when the fibre's PPPoE connection ends, the L2TP session is initiated. And when the PPPoE connection starts, the L2TP session is terminated. Notably this doesn't cover the router booting whilst the fibre is down, which is something to fix.&lt;/p&gt;
&lt;p&gt;In Linux L2TP is typically implemented with a combination xl2tpd and pppd. There's a NixOS module for xl2tpd, but it makes some assumptions that mean it won't work for this use case. Typically L2TP is used with IPsec for some old school VPN solutions, rather than on it's own, as we need it.&lt;/p&gt;
&lt;p&gt;So the first job is to create a new NixOS module, which we can base on the upstream version and just chop it up a bit to pass more configurability up the stack. I'll be the first to admit my Nix-fu is not strong yet, so I'm sure there are better ways to do this. But it works for me!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# modules/nixos/xl2tpd-flexible.nix&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; config&lt;span class="p"&gt;,&lt;/span&gt; pkgs&lt;span class="p"&gt;,&lt;/span&gt; lib&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; lib&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;xl2tpd-flexible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; mkEnableOption &lt;span class="s2"&gt;&amp;quot;xl2tpd, with more flexible configuration&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="ss"&gt;xl2tpOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; mkOption &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;type&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; types&lt;span class="o"&gt;.&lt;/span&gt;lines&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;xl2tpd configuration file content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;default&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="ss"&gt;pppOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; mkOption &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;type&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; types&lt;span class="o"&gt;.&lt;/span&gt;lines&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ppp options file content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;default&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="ss"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; mkIf config&lt;span class="o"&gt;.&lt;/span&gt;services&lt;span class="o"&gt;.&lt;/span&gt;xl2tpd-flexible&lt;span class="o"&gt;.&lt;/span&gt;enable &lt;span class="p"&gt;{&lt;/span&gt;
    systemd&lt;span class="o"&gt;.&lt;/span&gt;services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;xl2tpd-flexible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt;
      &lt;span class="ss"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; config&lt;span class="o"&gt;.&lt;/span&gt;services&lt;span class="o"&gt;.&lt;/span&gt;xl2tpd-flexible&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="ss"&gt;pppd-options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;writeText &lt;span class="s2"&gt;&amp;quot;ppp-options-xl2tpd.conf&amp;quot;&lt;/span&gt; cfg&lt;span class="o"&gt;.&lt;/span&gt;pppOptions&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="ss"&gt;xl2tpd-conf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;writeText &lt;span class="s2"&gt;&amp;quot;xl2tpd.conf&amp;quot;&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;cfg&lt;span class="o"&gt;.&lt;/span&gt;xl2tpOptions&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        pppoptfile = &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pppd-options&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="ss"&gt;xl2tpd-ppp-wrapped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;stdenv&lt;span class="o"&gt;.&lt;/span&gt;mkDerivation &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;name&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;xl2tpd-ppp-wrapped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;phases&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;installPhase&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="ss"&gt;nativeBuildInputs&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; pkgs&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; makeWrapper &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="ss"&gt;installPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          mkdir -p $out/bin&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;          makeWrapper &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;ppp&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/sbin/pppd $out/bin/pppd \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;            --set LD_PRELOAD    &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;libredirect&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/lib/libredirect.so&amp;quot; \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;            --set NIX_REDIRECTS &amp;quot;/etc/ppp=/etc/xl2tpd/ppp&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;          makeWrapper &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;xl2tpd&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/xl2tpd $out/bin/xl2tpd \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;            --set LD_PRELOAD    &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;libredirect&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/lib/libredirect.so&amp;quot; \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;            --set NIX_REDIRECTS &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;ppp&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/sbin/pppd=$out/bin/pppd&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;xl2tpd flexible server&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="ss"&gt;requires&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;network-online.target&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="ss"&gt;wantedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;multi-user.target&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;

      &lt;span class="ss"&gt;preStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        mkdir -p -m 700 /etc/xl2tpd/ppp&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        mkdir -p -m 700 /run/xl2tpd&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="ss"&gt;serviceConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;ExecStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;xl2tpd-ppp-wrapped&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/xl2tpd -D -c &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;xl2tpd-conf&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -p /run/xl2tpd/pid -C /run/xl2tpd/control&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;KillMode&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;process&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;Restart&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;on-success&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;Type&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;simple&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;PIDFile&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/run/xl2tpd/pid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We need to configure our additional WAN port, which builds on our  configuration from the previous post. xl2tpd doesn't support connecting to an IPv6 endpoint, and although my SIM gives me a /64 network, I chose not to configure it here to keep things simple.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# nixos/hermes/default.nix&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt; config&lt;span class="p"&gt;,&lt;/span&gt; pkgs&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  systemd&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;network&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;20-wan-4g&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;matchConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;MACAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;aa:aa:aa:aa:aa:02&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# port 2&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="ss"&gt;linkConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;wan-4g&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="ss"&gt;networks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;20-wan-4g&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        matchConfig&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;wan-4g&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;networkConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;DHCP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ipv4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;IPv6AcceptRA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;no&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="ss"&gt;dhcpV4Config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;ClientIdentifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mac&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="ss"&gt;RouteMetric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        linkConfig&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;RequiredForOnline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;no&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note the &lt;code&gt;RouteMetric&lt;/code&gt; for the &lt;code&gt;wan-4g&lt;/code&gt; is 200, where I used 100 for &lt;code&gt;pppoe-aaisp&lt;/code&gt;. This means we won't use the 4G connection's default route so long as the fibre is up.&lt;/p&gt;
&lt;p&gt;We'll use our xl2tpd-flexible module and some additional PPP up/down scripts to configure the L2TP service, and modify our PPPoE PPP scripts to trigger the connection. And we'll add a new reference to our CHAP secrets specifically for the pppd used by xl2tpd.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# nixos/hermes/default.nix&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; config&lt;span class="p"&gt;,&lt;/span&gt; pkgs&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# l2tp failover&lt;/span&gt;
  age&lt;span class="o"&gt;.&lt;/span&gt;secrets&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;xl2tpd-chap-secrets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="l"&gt;../../secrets/pppoe-chap-secrets.age&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/etc/xl2tpd/ppp/chap-secrets&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0600&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;xl2tpd-flexible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;xl2tpOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      [global]&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      max retries = 36&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      [lac aaisp]&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      lns = 194.4.172.12&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      require authentication = no&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      redial = yes&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      redial timeout = 10&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;pppOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      +ipv6&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      ipv6cp-use-ipaddr&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      name &amp;lt;USERNAME&amp;gt;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      noauth&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      ifname l2tp-aaisp&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;# routing (pppoe)&lt;/span&gt;
  environment&lt;span class="o"&gt;.&lt;/span&gt;etc&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ppp-up&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ppp/ip-up&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0755&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      #!&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;bash&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/bash&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;$1 is up&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      if [ $IFNAME = &amp;quot;pppoe-aaisp&amp;quot; ]; then&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;AAISP - FTTP PPPoE online&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;Bring L2TP down and remove routes&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        echo &amp;quot;d aaisp&amp;quot; &amp;gt; /run/xl2tpd/control&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;Add default routes via PPPoE&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip route add default dev pppoe-aaisp scope link metric 100&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip -6 route add default dev pppoe-aaisp scope link metric 100&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      fi&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  environment&lt;span class="o"&gt;.&lt;/span&gt;etc&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ppp-down&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ppp/ip-down&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0755&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      #!&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;bash&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/bash&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;$1 is down&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      if [ $IFNAME = &amp;quot;pppoe-aaisp&amp;quot; ]; then&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;AAISP - FTTP PPPoE offline&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;Remove default routes via PPPoE&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip route del default dev pppoe-aaisp scope link metric 100&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip -6 route del default dev pppoe-aaisp scope link metric 100&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;Connect via LT2P failover&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        echo &amp;quot;c aaisp&amp;quot; &amp;gt; /run/xl2tpd/control&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      fi&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;# routing (l2tp)&lt;/span&gt;
  environment&lt;span class="o"&gt;.&lt;/span&gt;etc&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;l2tp-up&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;xl2tpd/ppp/ip-up&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0755&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      #!&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;bash&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/bash&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;$1 is up&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      if [ $IFNAME = &amp;quot;l2tp-aaisp&amp;quot; ]; then&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;AAISP - L2TP failover online&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;Add default routes via L2TP&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip route add 194.4.172.12/32 via 192.168.100.1 metric 10&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip route add default dev l2tp-aaisp scope link metric 100&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip -6 route add default dev l2tp-aaisp scope link metric 100&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      fi&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  environment&lt;span class="o"&gt;.&lt;/span&gt;etc&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;l2tp-down&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;xl2tpd/ppp/ip-down&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0755&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      #!&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;bash&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/bash&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;$1 is down&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      if [ $IFNAME = &amp;quot;l2tp-aaisp&amp;quot; ]; then&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;AAISP - L2TP failover offline&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;Remove default route via L2TP&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip route del default dev l2tp-aaisp scope link metric 100&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip -6 route del default dev l2tp-aaisp scope link metric 100&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip route del 194.4.172.12/32 via 192.168.100.1 metric 10&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      fi&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Changes you might want to make include references to &lt;code&gt;194.4.172.12&lt;/code&gt;, which is the L2TP service endpoint for Andrews &amp;amp; Arnold, and &lt;code&gt;192.168.100.1&lt;/code&gt; which is IP of the 4G router. We add a static route for the L2TP endpoint via this address to prevent it inceptioning.&lt;/p&gt;
&lt;p&gt;Lastly, we need to modify our firewall rules to allow traffic through the new &lt;code&gt;l2tp-aaisp&lt;/code&gt; interface and to NAT traffic on the way out.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; config&lt;span class="p"&gt;,&lt;/span&gt; pkgs&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  networking&lt;span class="o"&gt;.&lt;/span&gt;nftables&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ruleset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    table inet firewall {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      chain rpfilter {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type filter hook prerouting priority mangle + 10; policy drop;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        meta nfproto ipv4 udp sport . udp dport { 68 . 67, 67 . 68 } accept comment &amp;quot;DHCPv4 client/server&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        fib saddr . mark oif exists accept&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      chain input {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type filter hook input priority filter; policy drop;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # assuming we trust our LAN clients&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        iifname { &amp;quot;lo&amp;quot;, &amp;quot;lan&amp;quot; } accept comment &amp;quot;trusted interfaces&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # handle packets according to connection state&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        ct state vmap { invalid : drop, established : accept, related : accept, new : jump input-allow, untracked : jump input-allow }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # if we make it here, block and log&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        tcp flags syn / fin,syn,rst,ack log prefix &amp;quot;refused connection: &amp;quot; level info&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      chain input-allow {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        # make your own choice on whether to allow SSH from outside&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        tcp dport 22 accept comment &amp;quot;ssh from anywhere&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        icmp type echo-request accept comment &amp;quot;allow ping&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        icmpv6 type != { nd-redirect, 139 } accept comment &amp;quot;Accept all ICMPv6 messages except redirects and node information queries (type 139).  See RFC 4890, section 4.4.&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        ip6 daddr fe80::/64 udp dport 546 accept comment &amp;quot;DHCPv6 client&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      chain forward {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type filter hook forward priority 0; policy drop;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # no internet egress to RFC1918 IPs&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        oifname { &amp;quot;pppoe-aaisp&amp;quot;, &amp;quot;l2tp-aaisp&amp;quot; } ip daddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } reject with icmp type net-unreachable comment &amp;quot;outbound rfc1918 not permitted&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # established/related allowed, invalid dropped&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        ct state vmap { established : accept, related : accept, invalid : drop }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # internal interfaces outbound allowed&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        iifname &amp;quot;lan&amp;quot; oifname { &amp;quot;pppoe-aaisp&amp;quot;, &amp;quot;l2tp-aaisp&amp;quot; } accept comment &amp;quot;internal networks out via ISP&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # allow icmp&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        icmp type echo-request accept comment &amp;quot;allow ping&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        icmpv6 type != { nd-redirect, 139 } accept comment &amp;quot;Accept all ICMPv6 messages except redirects and node information queries (type 139).  See RFC 4890, section 4.4.&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # log anything that was blocked&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        tcp flags syn / fin,syn,rst,ack log prefix &amp;quot;refused forward: &amp;quot; level info&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;    table ip nat {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      chain pre {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type nat hook prerouting priority dstnat; policy accept;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        # we&amp;#39;ll add rules for our 1:1 NAT here later&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      chain post {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type nat hook postrouting priority srcnat; policy accept;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        iifname &amp;quot;lan&amp;quot; oifname { &amp;quot;pppoe-aaisp&amp;quot;, &amp;quot;l2tp-aaisp&amp;quot; } masquerade comment &amp;quot;LAN NAT to FTTP&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      chain out {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type nat hook output priority mangle; policy accept;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        # we&amp;#39;ll add rules for our 1:1 NAT here later&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    }&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And that's it! I'm able to bring down the FTTP link by pulling the cable or powering off the ONT, and in under 10 seconds, everything is connected again, albeit with much less bandwidth and much higher latency!&lt;/p&gt;
&lt;p&gt;In the next post, I'll add VLANs for IoT devices, guests, self hosted services, and private clients/services that I prefer to reach the Internet via Mullvad's VPN service.&lt;/p&gt;</description><pubDate>Sat, 14 Dec 2024 16:24:00 +0000</pubDate><guid>https://s-n.me/building-a-nixos-router-for-aaisp-l2tp-failover-over-4g</guid><category>misc</category></item><item><title>Building a NixOS router for a UK FTTP ISP - The Basics</title><link>https://s-n.me/building-a-nixos-router-for-a-uk-fttp-isp-the-basics</link><description>&lt;p&gt;I use NixOS as a router for my FTTP ISP in the UK. The details in this post should apply to most PPPoE-delivered services.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In a later post I'll add 4G failover in a way that's more specific to my ISP, then add multiple VLANs and 1:1 NAT to make the most of the additional IPv4 addresses my ISP offers.&lt;/em&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;I picked up one of those cheap AliExpress tiny PCs a few months ago to have something dedicated to the router role in my setup. I'd previously used the same machine that self hosts my various services for the role, along with a USB ethernet dongle for the WAN side, and I really wanted to decouple them, with the router having a much smaller footprint and attack surface.&lt;/p&gt;
&lt;p&gt;The particular model I got was the "SZBOX G48S Alder Lake N100 Soft Router", with 8Gb RAM, 256Gb NVMe SSD and a 4-core Intel N100 CPU. It was £151 delivered, including UK power cable. It's £131 today!&lt;/p&gt;
&lt;p&gt;I don't necessarily recommend it, so I'm not linking to it directly. It rejected a 16Gb stick of Crucial RAM I tried that works fine in another system (with a lot of hard-hangs). I'm sure not expecting updates to the BIOS. But I wiped the Kingston SSD it shipped with, tried out some things, and reassured myself that it's not behaving nefariously in a way that I could detect. Be careful out there.&lt;/p&gt;
&lt;p&gt;The thing most attractive to me about it was the fanless design and 4x 2.5GbE Intel I226-V NICs. Perfect (or massive overkill) for a "home" router.&lt;/p&gt;
&lt;p&gt;I'm not going to get into the weeds on NixOS in this post, and will assume you're comfortable &lt;a href="https://nixos.org/manual/nixos/stable/"&gt;installing it&lt;/a&gt;. I use a flake-based approach to my systems, all defined in a single Git repo. It's a bit of a time sink getting familiar with it, but for me has been worth it for how quickly I can now configure new systems and services.&lt;/p&gt;
&lt;p&gt;Below is a simplified version of the module for this host. It's missing things like an SSH server, a local non-root user, and some commands you'll probably want to add to debug things (tcpdump, etc).&lt;/p&gt;
&lt;p&gt;It's opinionated, I prefer to use systemd-networkd where possible, and use nftables directly for messing with packets rather than the NixOS firewall module.&lt;/p&gt;
&lt;p&gt;I use dnsmasq for DHCP (IPv4) and internal DNS, with AdGuard Home for ad-blocking DNS. My internal DNS domain is &lt;a href="https://datatracker.ietf.org/doc/html/rfc8375"&gt;home.arpa&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I make use of &lt;a href="https://github.com/ryantm/agenix"&gt;agenix&lt;/a&gt; to keep encrypted secrets in my config repo. It's also possible to provide the password directly in the PPP config, but I wouldn't recommend it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; config&lt;span class="p"&gt;,&lt;/span&gt; lib&lt;span class="p"&gt;,&lt;/span&gt; pkgs&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;imports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="l"&gt;./hardware-configuration.nix&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;# allocate some of our RAM to use as compressed swap&lt;/span&gt;
  &lt;span class="c1"&gt;# which I use instead of disk-backed swap&lt;/span&gt;
  zramSwap&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  boot&lt;span class="o"&gt;.&lt;/span&gt;kernel&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;sysctl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# be more swappy as we&amp;#39;re using zramswap&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;vm.swappiness&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# enable IPv4 and IPv6 forwarding on all interfaces&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;net.ipv4.conf.all.forwarding&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;net.ipv6.conf.all.forwarding&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="s2"&gt;&amp;quot;net.ipv4.conf.all.arp_filter&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;net.ipv4.conf.default.arp_filter&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;# sleep any attached screen after 5 minutes&lt;/span&gt;
  boot&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;kernelParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;consoleblank=300&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="ss"&gt;networking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;hostName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;hermes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;useDHCP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;useNetworkd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    nftables&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# we write our own rules&lt;/span&gt;
    firewall&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# these will be available in the local DNS&lt;/span&gt;
    &lt;span class="c1"&gt;# along with DHCP hostnames&lt;/span&gt;
    &lt;span class="ss"&gt;extraHosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      192.168.32.1 hermes.home.arpa&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      2001:x:x:x::1 hermes.home.arpa&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;# the basic network configuration for systemd-networkd&lt;/span&gt;
  systemd&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;network&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    wait-online&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;10-lan&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# port 0 is plugged into my lan switch&lt;/span&gt;
        &lt;span class="ss"&gt;matchConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;MACAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;aa:aa:aa:aa:aa:00&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="ss"&gt;linkConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;lan&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="c1"&gt;# port 1 is unused&lt;/span&gt;
      &lt;span class="c1"&gt;# port 2 will be attached to our failover 4G router&lt;/span&gt;
      &lt;span class="c1"&gt;# but that&amp;#39;s for a future post&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;20-wan-fttp&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# port 3 is plugged into my ONT&lt;/span&gt;
        &lt;span class="ss"&gt;matchConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;MACAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;aa:aa:aa:aa:aa:03&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="ss"&gt;linkConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;wan-fttp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="ss"&gt;networks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;10-lan&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        linkConfig&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;RequiredForOnline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        matchConfig&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;lan&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;192.168.32.1/23&amp;quot;&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;2001:x:x:x::1/64&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="ss"&gt;dns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;192.168.32.1&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="ss"&gt;domains&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;home.arpa&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="ss"&gt;networkConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;# have networkd send IPv6 router advertisements&lt;/span&gt;
          &lt;span class="ss"&gt;IPv6SendRA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="ss"&gt;ipv6SendRAConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="c1"&gt;# RAs should include the router&amp;#39;s IP for DNS&lt;/span&gt;
          &lt;span class="ss"&gt;DNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2001:x:x:x::1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="ss"&gt;ipv6Prefixes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;Prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2001:x:x:x::/64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="ss"&gt;Assign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;20-wan-fttp&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# networkd should ignore the NIC connected to the ONT&lt;/span&gt;
        matchConfig&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;wan-fttp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        linkConfig&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;Unmanaged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        linkConfig&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;RequiredForOnline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;no&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;# fttp pppoe&lt;/span&gt;
  age&lt;span class="o"&gt;.&lt;/span&gt;secrets&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;pppoe-chap-secrets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="l"&gt;../../secrets/pppoe-chap-secrets.age&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/etc/ppp/chap-secrets&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0600&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;pppd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;peers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# the peer name here, and ifname below can be specific to your setup&lt;/span&gt;
      &lt;span class="ss"&gt;aaisp-pppoe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;autostart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;# wan-fttp is the named NIC from the networkd configuration&lt;/span&gt;
        &lt;span class="c1"&gt;# if your ISP doesn&amp;#39;t offer baby-jumbo frames, set mtu to 1492&lt;/span&gt;
        &lt;span class="ss"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          plugin pppoe.so wan-fttp&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          name &amp;quot;&amp;lt;USERNAME&amp;gt;&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;          noipdefault&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          hide-password&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          lcp-echo-interval 1&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          lcp-echo-failure 4&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          noauth&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          persist&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          maxfail 0&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          holdoff 5&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          mtu 1500&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          noaccomp&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          default-asyncmap&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          +ipv6&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          ipv6cp-use-ipaddr&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;          ifname pppoe-aaisp&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  systemd&lt;span class="o"&gt;.&lt;/span&gt;services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pppd-aaisp-pppoe&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;preStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    # if your ISP doesn&amp;#39;t offer baby-jumbo frames, remove this line&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip link set wan-fttp mtu 1508  &lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    # bring up the interface so ppp can use it&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip link set wan-fttp up&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;# routing (pppoe)&lt;/span&gt;
  environment&lt;span class="o"&gt;.&lt;/span&gt;etc&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ppp-up&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# this script runs after PPP has established a connection&lt;/span&gt;
    &lt;span class="c1"&gt;# we&amp;#39;ll use it to log, and add the default IPv4 and IPv6 routes&lt;/span&gt;
    &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ppp/ip-up&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0755&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      #!&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;bash&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/bash&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;$1 is up&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      if [ $IFNAME = &amp;quot;pppoe-aaisp&amp;quot; ]; then&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;AAISP - FTTP PPPoE online&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;Add default routes via PPPoE&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip route add default dev pppoe-aaisp scope link metric 100&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip -6 route add default dev pppoe-aaisp scope link metric 100&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      fi&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  environment&lt;span class="o"&gt;.&lt;/span&gt;etc&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ppp-down&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# this script runs after the PPP connection drops&lt;/span&gt;
    &lt;span class="c1"&gt;# we&amp;#39;ll use it to log, and remove the default routes&lt;/span&gt;
    &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ppp/ip-down&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0755&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      #!&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;bash&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/bash&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;$1 is down&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      if [ $IFNAME = &amp;quot;pppoe-aaisp&amp;quot; ]; then&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;AAISP - FTTP PPPoE offline&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;logger&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/logger &amp;quot;Remove default routes via PPPoE&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip route del default dev pppoe-aaisp scope link metric 100&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;iproute2&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin/ip -6 route del default dev pppoe-aaisp scope link metric 100&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      fi&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;# dnsmasq configuration, providing DHCP and internal DNS&lt;/span&gt;
  &lt;span class="c1"&gt;# (based on DHCP clients and /etc/hosts)&lt;/span&gt;
  services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;dnsmasq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;resolveLocalQueries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# bind to 8053, we want adguard to provide DNS&lt;/span&gt;
      &lt;span class="c1"&gt;# and we&amp;#39;ll let resolved own the loopback port 53&lt;/span&gt;
      &lt;span class="ss"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8053&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="ss"&gt;no-resolv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="ss"&gt;bind-dynamic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="ss"&gt;dhcp-authoritative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="ss"&gt;domain-needed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="ss"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;home.arpa&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="ss"&gt;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/home.arpa/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="ss"&gt;dhcp-range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;set:lan,192.168.32.100,192.168.33.200,255.255.254.0,12h&amp;quot;&lt;/span&gt;
      &lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="ss"&gt;dhcp-option&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;tag:lan,option:dns-server,192.168.32.1&amp;quot;&lt;/span&gt;
      &lt;span class="p"&gt;];&lt;/span&gt;

      &lt;span class="ss"&gt;dhcp-host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;# add any static DHCP IPs you want to assign here&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;aa:aa:aa:aa:aa:10,192.168.32.5,core-switch&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  services&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;adguardhome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;# any changes made through the web UI will be thrown away&lt;/span&gt;
    &lt;span class="c1"&gt;# on rebuild with this setting...&lt;/span&gt;
    &lt;span class="ss"&gt;mutableSettings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# note this configuration logs queries by default&lt;/span&gt;
      &lt;span class="c1"&gt;# check the docs if you want to avoid this&lt;/span&gt;

      &lt;span class="c1"&gt;# this will allow unauthenticated access to the adguard UI&lt;/span&gt;
      &lt;span class="c1"&gt;# to any host on your LAN.&lt;/span&gt;
      &lt;span class="c1"&gt;# Change it to 127.0.0.1 if you do not want this&lt;/span&gt;
      &lt;span class="ss"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;192.168.32.1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="ss"&gt;dns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;bind_hosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="c1"&gt;# trusted lan&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;192.168.32.1&amp;quot;&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;2001:x:x:x::1&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="ss"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;53&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;# some optimisations I found necessary&lt;/span&gt;
        &lt;span class="ss"&gt;ratelimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;cache_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;67108864&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;max_goroutines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;use_http3_upstreams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="ss"&gt;upstream_dns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="c1"&gt;# you may prefer to use your own ISPs DNS&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;https://dns.quad9.net/dns-query&amp;quot;&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;https://dns.mullvad.net/dns-query&amp;quot;&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;https://cloudflare-dns.com/dns-query&amp;quot;&lt;/span&gt;
          &lt;span class="c1"&gt;# requests for the local domain go to dnsmasq&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;[/home.arpa/]127.0.0.1:8053&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="ss"&gt;local_ptr_upstreams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="c1"&gt;# reverse lookups for local IPs go to dnsmasq&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;127.0.0.1:8053&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="ss"&gt;bootstrap_dns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="c1"&gt;# you may prefer to use your own ISPs DNS&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;9.9.9.10&amp;quot;&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;149.112.112.10&amp;quot;&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;2620:fe::10&amp;quot;&lt;/span&gt;
          &lt;span class="s2"&gt;&amp;quot;2620:fe::fe:10&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  networking&lt;span class="o"&gt;.&lt;/span&gt;nftables&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;ruleset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    table inet firewall {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      chain rpfilter {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type filter hook prerouting priority mangle + 10; policy drop;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        meta nfproto ipv4 udp sport . udp dport { 68 . 67, 67 . 68 } accept comment &amp;quot;DHCPv4 client/server&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        fib saddr . mark oif exists accept&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      chain input {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type filter hook input priority filter; policy drop;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # assuming we trust our LAN clients&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        iifname { &amp;quot;lo&amp;quot;, &amp;quot;lan&amp;quot; } accept comment &amp;quot;trusted interfaces&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # handle packets according to connection state&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        ct state vmap { invalid : drop, established : accept, related : accept, new : jump input-allow, untracked : jump input-allow }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # if we make it here, block and log&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        tcp flags syn / fin,syn,rst,ack log prefix &amp;quot;refused connection: &amp;quot; level info&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      chain input-allow {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        # make your own choice on whether to allow SSH from outside&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        tcp dport 22 accept comment &amp;quot;ssh from anywhere&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        icmp type echo-request accept comment &amp;quot;allow ping&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        icmpv6 type != { nd-redirect, 139 } accept comment &amp;quot;Accept all ICMPv6 messages except redirects and node information queries (type 139).  See RFC 4890, section 4.4.&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        ip6 daddr fe80::/64 udp dport 546 accept comment &amp;quot;DHCPv6 client&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      chain forward {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type filter hook forward priority 0; policy drop;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # no internet egress to RFC1918 IPs&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        oifname &amp;quot;pppoe-aaisp&amp;quot; ip daddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } reject with icmp type net-unreachable comment &amp;quot;outbound rfc1918 not permitted&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # established/related allowed, invalid dropped&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        ct state vmap { established : accept, related : accept, invalid : drop }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # internal interfaces outbound allowed&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        iifname &amp;quot;lan&amp;quot; oifname &amp;quot;pppoe-aaisp&amp;quot; accept comment &amp;quot;internal networks out via ISP&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # allow icmp&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        icmp type echo-request accept comment &amp;quot;allow ping&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        icmpv6 type != { nd-redirect, 139 } accept comment &amp;quot;Accept all ICMPv6 messages except redirects and node information queries (type 139).  See RFC 4890, section 4.4.&amp;quot;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        # log anything that was blocked&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        tcp flags syn / fin,syn,rst,ack log prefix &amp;quot;refused forward: &amp;quot; level info&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;    table ip nat {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      chain pre {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type nat hook prerouting priority dstnat; policy accept;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        # we&amp;#39;ll add rules for our 1:1 NAT here later&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      chain post {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type nat hook postrouting priority srcnat; policy accept;&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;        iifname &amp;quot;lan&amp;quot; oifname &amp;quot;pppoe-aaisp&amp;quot; masquerade comment &amp;quot;LAN NAT to FTTP&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;      chain out {&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        type nat hook output priority mangle; policy accept;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        # we&amp;#39;ll add rules for our 1:1 NAT here later&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      }&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    }&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  system&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;stateVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;24.11&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><pubDate>Fri, 13 Dec 2024 14:10:00 +0000</pubDate><guid>https://s-n.me/building-a-nixos-router-for-a-uk-fttp-isp-the-basics</guid><category>misc</category></item><item><title>Switching from Virgin Media cable to Andrews &amp; Arnold over OpenReach FTTP</title><link>https://s-n.me/switching-from-virgin-media-cable-to-andrews-and-arnold-over-openreach-fttp</link><description>&lt;p&gt;&lt;em&gt;This started as a post about how I use NixOS to build a router, but the aside on AAISP was getting a little unwieldy. I'll get back to that post another time.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Earlier this year, Fibre To The Premises (FTTP) arrived in my street, and so I took the opportunity to end my 23 year relationship with Virgin Media (previously ntl), the UK's last cable operator standing, and jumped at the chance to switch to &lt;a href="https://www.aa.net.uk/"&gt;Andrews &amp;amp; Arnold&lt;/a&gt; (AAISP), probably the nerdiest generally available ISP in the country.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;Aside from all the great things I'd heard from others, I knew I was in good hands when I emailed support 30 minutes after placing the order to request a (free) additional IPv4 &lt;code&gt;/30&lt;/code&gt; subnet for a service that wouldn't start until 6 weeks later. I got a reply in under half an hour with my assigned CIDR.&lt;/p&gt;
&lt;p&gt;In some ways, one might call the switch a downgrade. With cable I had 1130Mbit/s down, 104Mbit/s up, for £43.52 per month. Now I have 1000Mbit/s down, 115Mbit/s up for £85. So, slower download, faster upload and near twice the price.&lt;/p&gt;
&lt;p&gt;What gives?&lt;/p&gt;
&lt;p&gt;Well, for one thing, officially the price of the cable package is around £72 per month. Keeping the price down requires invoking the 30-day cancellation period at the end of each 18-month contract cycle, and then waiting for a call a few days or weeks later where they finally offer you the best prices. With AAISP, the price is the price.&lt;/p&gt;
&lt;p&gt;And talking of price, at the last negotiation I was paying £40 per month, but then the annual "RPI + a little bit extra for our profit margin" rise kicked in, boosting the price for the remaining 6 months of the contract to £43.52. &lt;a href="https://www.ofcom.org.uk/phones-and-broadband/bills-and-charges/ofcom-bans-mid-contract-price-rises-linked-to-inflation/"&gt;Illegal in the UK&lt;/a&gt; for new contracts from 17th January 2025! VM doesn't seem to be in much of a hurry to get aboard that train until it absolutely has to, with the small print on their current offers stating:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Monthly price of Virgin Media’s main services and O2 Airtime Plan will increase each April from April 2025 by the Retail Price Index rate of inflation announced in February each year plus 3.9%.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No such shenanigans with AAISP.&lt;/p&gt;
&lt;p&gt;More importantly, I appreciate how &lt;a href="https://aastatus.net/"&gt;transparent AAISP is&lt;/a&gt;, right down to the technical details.&lt;/p&gt;
&lt;p&gt;There will always be times when things go wrong, but having visibility and honesty in those times makes for a much less frustrating experience. I generally found VM to be pretty reasonable in my area, but occasionally we'd lose service, and half the time the failures weren't even acknowledged, let alone explained.&lt;/p&gt;
&lt;hr&gt;

&lt;p&gt;Let's discuss AAISP features I use, which VM does not offer on their consumer packages.&lt;/p&gt;
&lt;h3&gt;IPv6&lt;/h3&gt;
&lt;p&gt;It's just about to tick over into the year of our lawd 2025, and one of the UK's largest ISPs doesn't offer IPv6! Meanwhile, AAISP assigns my account a /48 IPv6 network, from which I'm free to assign multiple /64 and /60 networks to each of my lines, giving me the freedom to assign separate networks to each of my VLANs (a topic for another time).&lt;/p&gt;
&lt;h3&gt;Multiple IPv4 addresses&lt;/h3&gt;
&lt;p&gt;I like to &lt;a href="https://s-n.me/my-self-hosted-services"&gt;self-host a bunch of services&lt;/a&gt;. I'd prefer to keep the publicly accessible ones on a separate IP from the bulk of our family Internet usage. I used to do this via a small VPS with Wireguard, now I can do it directly to home, thanks to AAISP offering a free /30 or /29 IPv4 network, even to their home customers, on request. Notably this block is in addition to the fixed primary address.&lt;/p&gt;
&lt;h3&gt;Failover&lt;/h3&gt;
&lt;p&gt;AAISP offer a service called &lt;a href="https://www.aa.net.uk/broadband/l2tp-service/"&gt;L2TP&lt;/a&gt;, which gives you Internet access through their network over the top of an existing connection. I used it for a while to get IPv6 for my home network.&lt;/p&gt;
&lt;p&gt;It's also included in their standard broadband packages &lt;a href="https://support.aa.net.uk/Category:Incoming_L2TP"&gt;as a backup&lt;/a&gt;. In practice this means I've been able to setup a 4G router with cheap prepaid SIM, connected to my main router. Should there be an issue with the fibre connection, an L2TP connection is automatically initiated over 4G, and the IPs associated with my fibre service are now routed over the cellular network, with the attending drop in bandwidth and increase in latency.&lt;/p&gt;
&lt;p&gt;Which is really useful when you're using those IPs to host all the things!&lt;/p&gt;
&lt;hr&gt;

&lt;p&gt;Finally, whilst VM is on-paper faster, at least for downloads, the raw advertised throughput to your ISP is not the only metric for the speed of your broadband. The details of their internal infrastructure and peering to the places you want to go will also impact your experience. I got a little hint of this having previously used AAISP's L2TP service, finding that latency to many places (at least measured by ping) was better with their service running over Virgin Media, than from Virgin Media directly!&lt;/p&gt;
&lt;p&gt;Subjectively, it does feel like everything is just a little more snappy now. Objectively, measuring the ping times between my home in Bedford to my VPS in Cambridge, I typically saw 15-20ms over cable, and now 5-8ms with fibre. That's around 30 miles, though it's unlikely the actual path is anything close to the line of sight distance. I don't know for sure, but I would guess the technology differences between cable and FTTP are a factor here, in addition to my choice of ISP.&lt;/p&gt;
&lt;p&gt;In conclusion, I'm happy to, and fortunate to be able to pay a premium for a quality service from a company with &lt;a href="https://www.aa.net.uk/broadband/real-internet/"&gt;values&lt;/a&gt; I align with.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;No money was exchanged for this post, except me paying for my broadband.&lt;/em&gt;&lt;/p&gt;</description><pubDate>Wed, 11 Dec 2024 13:06:00 +0000</pubDate><guid>https://s-n.me/switching-from-virgin-media-cable-to-andrews-and-arnold-over-openreach-fttp</guid><category>misc</category></item><item><title>My self-hosted services</title><link>https://s-n.me/my-self-hosted-services</link><description>&lt;p&gt;Here's a snapshot of the services I'm currently running for myself, and approximately how. Some will go, new ones will be added, but here we are for December 2024.&lt;/p&gt;
&lt;p&gt;Many, if not all are strictly unnecessary, there are free/cheap hosted alternatives available, but there's something very satisfying about knowing these are sitting on a shelf in the cupboard in my office. Not least that for the most part, these services are incredibly responsive!&lt;/p&gt;
&lt;p&gt;In the past I've run a lot of these things in a single-node Kubernetes cluster, mostly using Helm charts. NixOS modules have made things much simpler, whilst still allowing a declarative approach to managing most of these services.&lt;/p&gt;
&lt;p&gt;I typically have &lt;code&gt;/services/&amp;lt;service&amp;gt;/&amp;lt;subcomponent&amp;gt;&lt;/code&gt; directories on host machines, and have these mounted into the various containers and VMs as necessary to store state. This means the rest of the container/VM is usually ephemeral, and can be re-created at any time. Indeed, I've been able to migrate between machines very easily thanks to this. It also provides a gives me a single volume to back up from each host.&lt;/p&gt;
&lt;p&gt;I recently got to grips with &lt;a href="https://astro.github.io/microvm.nix/intro.html"&gt;microvm.nix&lt;/a&gt;, so am starting to migrate things to VMs where that level of isolation feels appropriate.&lt;/p&gt;
&lt;p&gt;One thing to be mindful of when using a single Nix expression to define a system composed of many containers/VMs is that &lt;code&gt;nixos-rebuild&lt;/code&gt; is pretty expensive in CPU and RAM, taking around 2 minutes to make a simple change and using approaching 10Gb RAM on the machine that hosts the majority of the services below!&lt;/p&gt;
&lt;!--more--&gt;

&lt;hr&gt;

&lt;h2&gt;&lt;a href="https://adguard.com/en/adguard-home/overview.html"&gt;AdGuard Home&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My network-wide DNS server, also used by Tailscale/Headscale clients, like my phone, to do surprisingly effective ad blocking at the network level.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.adguardhome"&gt;NixOS module&lt;/a&gt; directly on my router host.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://atuin.sh/"&gt;Atuin&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm running my own instance of the sync server for this amazing utility. Once you get used to cross-machine shell history with blazing-fast search, it's hard to go back.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=atuin"&gt;NixOS module&lt;/a&gt; which also takes care of configuring a PostgreSQL instance for persistence, all in a &lt;a href="https://nixos.org/manual/nixos/stable/#ch-containers"&gt;NixOS container&lt;/a&gt; (a systemd-nspawn container built and managed by NixOS).&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://emoncms.org/"&gt;Emoncms&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Quoting from the website, "Emoncms is a powerful open-source web-app for processing, logging and visualising energy, temperature and other environmental data".&lt;/p&gt;
&lt;p&gt;I use it to collect electricity usage data from clamps on my incoming grid power cable, and from my solar PV inverter, in addition to an optical sensor on the electricity meter. It turns out the little flashing LED flashes at a very exact interval according to your electricity use.&lt;/p&gt;
&lt;p&gt;It also connects with an &lt;a href="https://shop.openenergymonitor.com/emonevse-wifi-connected-ev-charging-station-type-2/"&gt;EmonEVSE&lt;/a&gt; charging station for my car, providing it via MQTT with solar generation data so it can modulate charge according to what's coming from the roof.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed on an EmonPi, a dedicated Raspberry Pi 3 unit in the cupboard with my distribution board, that includes a "hat" for connecting to the clamps, etc.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://forgejo.org/"&gt;Forgejo&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A nice and surprisingly feature complete alternative to Github/Gitlab that's pretty simple to operate at small scale. Handling SSH Git interactions through my load balancer was probably the hardest part of getting this running.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.forgejo"&gt;NixOS module&lt;/a&gt;, which takes care of configuring PostgreSQL, all in a NixOS container.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://grafana.com/oss/grafana/"&gt;Grafana&lt;/a&gt; and &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Prometheus scrapes metrics from a bunch of these services, including Home Assistant, and from each host device using it's &lt;a href="https://prometheus.io/docs/guides/node-exporter/"&gt;node-exporter&lt;/a&gt; tool for data on CPU, memory, network, temperatures, etc.&lt;/p&gt;
&lt;p&gt;Grafana lets me build dashboards to query Prometheus and visualise all that data. There are pre-built dashboards for many of these services, and I've got custom ones I use all the time for energy data sources from my battery inverter and EmonCMS (via Home Assistant).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Each is deployed in it's own NixOS container, using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.grafana"&gt;Grafana&lt;/a&gt; and &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.prometheus"&gt;Prometheus&lt;/a&gt; NixOS modules.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://headscale.net/stable/"&gt;Headscale&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An alternative to using the official service-side part of the Tailscale mesh VPN. I installed this recently, and I'm not sure I'm ready to commit to using it, versus manually configuring Wireguard. But it does what it says on the tin. I have noticed lots of connections continue to be made by the client apps to the official endpoints.&lt;/p&gt;
&lt;p&gt;Using it to provide DNS where the DNS server is in the Tailnet seems to result in delayed/failed queries for a few moments if the connection has gone idle. This wasn't a problem I saw when previously using vanilla Wireguard for the same thing, likely due to the static configuration.&lt;/p&gt;
&lt;p&gt;I also noticed the Wireguard configuration was lighter on my iPhone's battery, likely because it had a much simpler job of connecting to a single, static endpoint.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed in microvm.nix VM using the &lt;a href="https://search.nixos.org/options?query=services.headscale"&gt;NixOS module&lt;/a&gt; which also configured PostgreSQL.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://www.home-assistant.io/"&gt;Home Assistant&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The centre of my home automation universe, acting as my Zigbee coordinator, bridging lights, our central heating and ACs to HomeKit.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed as a VM using HAOS using &lt;a href="https://linuxcontainers.org/incus/docs/main/"&gt;Incus&lt;/a&gt; on NixOS. There's a NixOS module, but it's complicated, and the Home Assistant folks are pretty clear that they only actively support their own OS.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://jellyfin.org/"&gt;Jellyfin&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Although I stopped adding to it when streaming came along, I've got a few thousand tracks mostly ripped from CDs, some bought from iTunes back in the day, that have come with me across each new machine. Now I've dropped them in to Jellyfin, along with a few DVD rips, and can access them anywhere, which is nice.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed directly on a NixOS host with the &lt;a href="https://search.nixos.org/options?query=services.jellyfin"&gt;module&lt;/a&gt;. Recently moved out of a Docker container inside an Incus Debian container, and probably not in it's final form yet.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://www.keycloak.org/"&gt;Keycloak&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm running enough services here that maintaining separate logins for them all gets quickly tedious. So wherever a service supports OIDC, which many listed here do, I connect them to Keycloak as my single sign on provider. It's a single place to maintain an account, including 2FA with multiple passkeys, YubiKeys and TOTP codes.&lt;/p&gt;
&lt;p&gt;It's configured primarily through the web UI right now, which is something I'd like to fix eventually.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed in a NixOS container using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.keycloak"&gt;module&lt;/a&gt; which also takes care of a supporting PostgreSQL instance.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://linkding.link/"&gt;Linkding&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of my favourites. I used Linkding as a read-it-later service. I looked at a few alternatives, and I find this one suits me best. It's got a browser extension for quick-adding links, and an iOS app which doesn't have a share-sheet action out the box, but it's pretty simple to &lt;a href="https://linkding.link/how-to/#create-a-share-action-on-ios-for-adding-bookmarks-to-linkding"&gt;create a Shortcut&lt;/a&gt; for.&lt;/p&gt;
&lt;p&gt;No native Nix package or module for this one! And the author makes Docker the default easy path for deployment, which is understandable. This one might finally get me to try my hand at writing a Nix package and module.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed with Docker Compose inside a dedicated Debian container in Incus.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://joinmastodon.org/"&gt;Mastodon&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is what really got me started on the path to self-hosting. I'm fortunate to have had a very pleasant social media experience on Mastodon, perhaps in part because my single-user instance brings with it some limits on what I see.&lt;/p&gt;
&lt;p&gt;Mastodon is by far the most resource intensive service listed here. But it's still pretty small and lives happily alongside all the other stuff on a reasonably modest small form factor machine.&lt;/p&gt;
&lt;p&gt;I do use &lt;a href="https://blog.thms.uk/fedifetcher"&gt;fedifetcher&lt;/a&gt; to grab extra context for the toots appearing in my timeline, which is often useful, but definitely adds to the resources. It's packaged in NixOS, but there's no module for it. However it's trivial to create the necessary &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=systemd.services"&gt;systemd units in the nix config&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed in a NixOS container using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.mastodon"&gt;module&lt;/a&gt; which takes care of all supporting services, except for Minio.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://miniflux.app/"&gt;Miniflux&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like Linkding, Miniflux is one of many options I looked at, and it's the one that fits me best as an RSS aggregator and reader. I love it's minimalist UI, and it's broad API support for connecting to apps on iOS and elsewhere. And indeed I can save directly to my Linkding instance from the Miniflux web UI.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed in a NixOS container using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.miniflux"&gt;module&lt;/a&gt;, which also takes care of it's PostgreSQL database.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://min.io/"&gt;Minio&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Providing an S3-compatible service for object storage, for now just being used for my Mastodon instance's media. Arguably I could have skipped this and let Mastodon manage the media in it's own filesystem, but here we are. I've worked in DevOps long enough to know having something S3-like often ends up being handy.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed as a NixOS container, using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.minio"&gt;NixOS module&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://nextcloud.com/"&gt;Nextcloud&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Primarily I use it like one might use Dropbox or OneDrive, as a file sync service across devices. &lt;a href="https://syncthing.net/"&gt;Syncthing&lt;/a&gt; might be more suitable for this narrow use case.&lt;/p&gt;
&lt;p&gt;I do appreciate Nextcloud supporting iOS's native Photos, so that any new photos taken on my phone are automatically uploaded to my server.&lt;/p&gt;
&lt;p&gt;It feels like a large attack service, and so is something I want to make VPN-only.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.nextcloud"&gt;NixOS module&lt;/a&gt;, which also takes care of configuring PostgreSQL, Redis and nginx, in a NixOS container. Definitely a candidate for moving to a VM.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://restic.net/"&gt;Restic&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My backup tool of choice, I use the built-in server to receive backups for my various machines on the local network, and remotely over VPN.&lt;/p&gt;
&lt;p&gt;I also use &lt;a href="https://rclone.org/"&gt;rclone&lt;/a&gt; to nightly sync these encrypted backups to OneDrive, which has been the cheapest cloud storage I've found. Effectively 6Tb for around £50 per year when you pick up the Microsoft 365 "gift cards" on offer. With the news of them increasing the cost to force AI into the offering, I suspect I'll eventually move on from this solution. But for now, because they allow stacking the gift cards, I'm covered for the next 3 years. Assuming they don't renege on the deal.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.restic"&gt;module&lt;/a&gt; directly on a NixOS host.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://docs.searxng.org/"&gt;SearXNG&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My default search "engine" is one I self host! SearXNG is a meta-search tool, running queries against multiple public search services, and gives a clean, ad-free set of results that are usually sufficient.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.searx"&gt;module&lt;/a&gt; in a NixOS container.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://snikket.org/"&gt;Snikket&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is my most recent addition, prompted by &lt;a href="https://mastodon.neilzone.co.uk/@neil"&gt;Neil's&lt;/a&gt; &lt;a href="https://mastodon.neilzone.co.uk/@neil/113610257681384054"&gt;recent post&lt;/a&gt; linking to &lt;a href="https://neilzone.co.uk/2023/08/a-month-using-xmpp-using-snikket-for-every-call-and-chat/"&gt;his article&lt;/a&gt; on using Snikket/XMPP for over a year.&lt;/p&gt;
&lt;p&gt;I previously ran Matrix &lt;a href="https://github.com/element-hq/synapse"&gt;Synapse&lt;/a&gt; for a while, which is pretty effective, but never became embedded in my day-to-day. I'd been meaning to try an XMPP-based solution for a while, so took a look at Snikket, and &lt;a href="https://prosody.im/"&gt;Prosody&lt;/a&gt;, which it's built on.&lt;/p&gt;
&lt;p&gt;Prosody has a &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.prosody"&gt;NixOS module&lt;/a&gt;, where Snikket does not. However, it's a pretty complex beast, so there was something quite appealing about dipping my toe with an opinionated all-in-one solution.&lt;/p&gt;
&lt;p&gt;Another project where the author targets Docker for deployment. I don't like running Docker natively on my NixOS host that I use for my server, because I like to be in full control of it's network configuration and nftables rules.&lt;/p&gt;
&lt;p&gt;I started by trying to use Podman and the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=virtualisation.oci-containers."&gt;oci-containers NixOS module&lt;/a&gt; within a NixOS container. Giving the container sufficient privileges to run containers didn't sit well, and it required some hacky use of the module to pass the correct arguments. So this was the project that pushed me to get to grips with microvm.nix, which ended up being quite straightforward, and led me to quickly convert a few containers to VMs.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed using the NixOS oci-containers module, with Podman, inside a microvm.nix VM.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://doc.traefik.io/traefik/"&gt;Traefik&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Traefik is the glue that binds all my services together and makes them available to the Internet (or just my home network). It's a fairly lightweight load balancer with a configuration format that makes sense to me. I could easily swap in Nginx or similar here, but Traefik suits me well.&lt;/p&gt;
&lt;p&gt;I use the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=security.acme"&gt;security.acme NixOS module&lt;/a&gt; to manage certificates with Lets Encrypt, and pass these to the various places they're needed with mounts. I mostly use wildcard certificates to reduce the number required, and do dns-01 validation against my domains hosted with &lt;a href="https://www.mythic-beasts.com/"&gt;Mythic Beasts&lt;/a&gt;, using the built-in support for them in &lt;a href="https://go-acme.github.io/lego/"&gt;Lego&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The public instance is deployed in a micronix.vm VM using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.traefik"&gt;NixOS module&lt;/a&gt;. The private instance uses the same module on the NixOS host.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://github.com/dani-garcia/vaultwarden"&gt;Vaultwarden&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I've been a 1Password user for many years. Two decades of using a Mac will do that for you. Now I'm back to Linux, I wanted something that I could host myself that provided a reasonable user experience on the desktop and iOS. Bitwarden is pretty decent, but I've not quite mustered the enthusiasm to migrate fully yet, so continue to mainly use 1Password. The UX just isn't quite as good in Bitwarden. With that said, I have only positive things to say about Vaultwarden.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed in a NixOS container using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.vaultwarden"&gt;module&lt;/a&gt; which uses SQLite by default, but I've set it up with PostgreSQL using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=services.postgresql"&gt;module&lt;/a&gt; for that.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://writefreely.org/"&gt;WriteFreely&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You're using it now visiting this site. I like it's super-simple writing UI, although I occasionally have thoughts of going back to a static site generator.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployed using the &lt;a href="https://search.nixos.org/options?channel=24.11&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages&amp;amp;query=writefreely"&gt;NixOS module&lt;/a&gt; which also takes care of the MySQL database, all in a NixOS container.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;

&lt;p&gt;I started writing this post thinking "I'll just knock out a quick one to help get me back into the habit". LOL. Turns out it's easy to forget just how much stuff I'm operating!&lt;/p&gt;</description><pubDate>Tue, 10 Dec 2024 13:09:00 +0000</pubDate><guid>https://s-n.me/my-self-hosted-services</guid><category>misc</category><category>selfhosting</category><category>nixos</category></item><item><title>The joy of code review</title><link>https://s-n.me/the-joy-of-code-review</link><description>&lt;p&gt;I've often heard code review spoken of as a chore, and have witnessed (and myself been guilty of) reviews that reflect this attitude. Cursory skims of the proposed changes, nitpicking small but obvious faults, rushing to get the review done to move on to matters more pressing.&lt;/p&gt;
&lt;p&gt;But this attitude, or culture where it's pervasive, denies &lt;strong&gt;the reviewer&lt;/strong&gt; a great opportunity to learn.&lt;/p&gt;
&lt;p&gt;As someone a couple of decades in to my career, I've noticed that whilst I still get excited to learn a new tool, it can be easy to overlook how the tools I'm most comfortable and familiar with continue to evolve.&lt;/p&gt;
&lt;p&gt;I've found myself learning new things about the tools I thought I knew best whilst reviewing the code of someone who's earlier in that journey and discovering all the features of those tools for the first time as they are today, and that's brilliant!&lt;/p&gt;
&lt;p&gt;Code review is also a great opportunity to get some insight into how other folks think and solve problems. Doing my job &lt;em&gt;well&lt;/em&gt; as a reviewer demands that I understand the problem being solved, and is often rewarded by discovering an approach or techniques I may never have considered.&lt;/p&gt;
&lt;p&gt;Regardless of experience, we have so much to learn from each other. So make time for and give reverence to your code reviews. And if your organisation doesn't allow for that, that sucks! If you're able, find one that does.&lt;/p&gt;</description><pubDate>Fri, 21 Jun 2024 17:46:00 +0100</pubDate><guid>https://s-n.me/the-joy-of-code-review</guid><category>misc</category><category>codereview</category><category>programming</category></item><item><title>Differently complicated hosting - The Before</title><link>https://s-n.me/differently-complicated-hosting-the-before</link><description>&lt;p&gt;After a few months of getting comfortable with NixOS as my desktop operating system, I decided it was time to try it out for servers. But first I wanted to write about the setup I had before.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: This post is likely full of bad ideas - you probably shouldn't setup anything you care about like this. It is my opinion that my most valuable learning is when I'm learning what not to do, and I know there are some gems lurking in here.&lt;/p&gt;
&lt;!--more--&gt;

&lt;h2&gt;How it started&lt;/h2&gt;
&lt;p&gt;At home, I was running a 8th-gen i5 &lt;a href="https://en.wikipedia.org/wiki/Next_Unit_of_Computing"&gt;NUC&lt;/a&gt; doing double duty as my home router and server, called &lt;strong&gt;homegw&lt;/strong&gt;. It was running Ubuntu 22.04 with LXD. Router stuff happened on the base OS, with dnsmasq, AdGuard Home and a bunch of ufw rules. It also ran xl2tpd to bring up my &lt;a href="https://www.aa.net.uk/broadband/l2tp-service/"&gt;Andrews and Arnold LT2P&lt;/a&gt; connection so I can have real IPv6 here, which my cable ISP doesn't provide.&lt;sup&gt;&lt;a href="#fn.1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;There was an LXD VM for Home Assistant, and LXD containers for a backup server (&lt;a href="https://restic.net"&gt;restic&lt;/a&gt; and &lt;a href="https://www.arqbackup.com/"&gt;Arq&lt;/a&gt; over SSH), for Plex, and one running various tools for... &lt;em&gt;ahem&lt;/em&gt; downloading Linux ISOs.&lt;/p&gt;
&lt;p&gt;There was a Wireguard tunnel from this machine out to &lt;a href="https://mullvad.net/en"&gt;Mullvad&lt;/a&gt;, and a static route with NAT to 10.64.0.1 on this link. Mullvad operates a SOCKS5 proxy on this address, so I configured all my Linux ISO downloading tools to use this proxy.&lt;/p&gt;
&lt;p&gt;Another Wireguard tunnel connected to a dedicated server, &lt;strong&gt;hetty&lt;/strong&gt;, running in Hetzner's Falkenstein data centre. It was nabbed from their &lt;a href="https://www.hetzner.com/sb"&gt;server auction&lt;/a&gt; a little over a year ago. It was an 8-core 9th-gen i9, 128Gb RAM and 2x1Tb NVMe drives, for the bargain price of 54€ per month. In retrospect, way more computer than I needed, and more money than I should be spending.&lt;/p&gt;
&lt;p&gt;This too ran Ubuntu 22.04, with LXD. In this case, a single LXD container called &lt;strong&gt;kubeservices&lt;/strong&gt; running &lt;a href="https://microk8s.io/"&gt;microk8s&lt;/a&gt;. I had Kubernetes setup with &lt;a href="https://fluxcd.io"&gt;flux&lt;/a&gt;, a GitOps tool that keeps your cluster in sync with a bunch of YAML defined in Git. Hosted here were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This blog (Writefreely with MySQL)&lt;/li&gt;
&lt;li&gt;The CrunchyData &lt;a href="https://github.com/CrunchyData/postgres-operator"&gt;Postgres Operator&lt;/a&gt; providing PostgreSQL instances for most of the other services mentioned here&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nerdout.club"&gt;My Mastodon instance&lt;/a&gt;, with Elasticsearch and Libretranslate&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dani-garcia/vaultwarden"&gt;Vaultwarden&lt;/a&gt;, a full featured and self hosted Bitwarden server&lt;/li&gt;
&lt;li&gt;Synapse, a &lt;a href="https://matrix.org/"&gt;Matrix&lt;/a&gt; chat server&lt;/li&gt;
&lt;li&gt;Prometheus, for gather metrics on all the services, including from Home Assistant on the other end of the Wireguard tunnel, so I can get pretty graphs on my home electricity generation/usage, heating, etc.&lt;/li&gt;
&lt;li&gt;Grafana for visualising those metrics&lt;/li&gt;
&lt;li&gt;Keycloak for single sign-on to most of the above services&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kubernetes PersistentVolumeClaims provided all the stateful storage, using the microk8s host path provisioner, all ultimately ending up in a single directory on the host, an LXD custom volume with daily snapshots and backed up to OneDrive&lt;sup&gt;&lt;a href="#fn.2"&gt;2&lt;/a&gt;&lt;/sup&gt; and to &lt;strong&gt;homegw&lt;/strong&gt; with restic.&lt;/p&gt;
&lt;p&gt;I'd chosen to use LVM on top of a LUKS device made from an mdadm initialised RAID1 mirror. ZFS was tempting, but whilst this was easy to setup with Ubuntu Desktop, it was less straightforward with Ubuntu Server. So I took the easy road. Or so I thought.&lt;/p&gt;
&lt;p&gt;LXD creates a ThinPool for it's storage when using LVM, which eventually bit me when it's space reserved for metadata filled up. Despite having plenty of free data space, I couldn't figure out how to reallocate more space for metadata (I don't think you can). So I ended up ejecting the second SSD from the RAID1 mirror, and adding it as a new disk to the volume group in order to expand the size of the thin pool in order to recover.&lt;/p&gt;
&lt;p&gt;That was my first big "this was a mistake" moment, letting LXD allocate all the remaining volume group space for a single Thin Pool was a bad idea. Ultimately I think LVM was not a good choice for this kind of setup, and I wouldn't make it again.&lt;/p&gt;
&lt;p&gt;Oh, and I'd made the same choice of LVM with LXD on &lt;strong&gt;homegw&lt;/strong&gt;. Multiple times I filled the disk and had a bit of a nightmare recovering from it. &lt;a href="https://duckduckgo.com/?hps=1&amp;amp;q=one+does+not+simply&amp;amp;atb=v403-1&amp;amp;iax=images&amp;amp;ia=images"&gt;One does not simply&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another "this was a mistake" moment was from filling OneDrive with restic backups. It turned out the Postgres Operator by default creates a single initial backup using pgrestbackup, and then captures the write-ahead log &lt;em&gt;forever&lt;/em&gt;. Eventually my daily restic snapshots, even with regular pruning, filled the dedicated OneDrive account I'd setup for the job. At that point, restic could simply not recover. Any kind of pruning operation needed some amount of storage space in OneDrive that impossible to provide. You can pay for extra storage, but only on the primary Microsoft 365 account, so I couldn't buy my way out of it. In the end I trashed the entire restic repository and started again.&lt;/p&gt;
&lt;p&gt;A simple change to the PostgresCluster YAML switched PGO to giving daily backups, storing up to 3 in the cluster. My disk usage went down to 600Gb to 150Gb after 3 days.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;pgbackrest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;global&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;repo1-retention-full&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;repo1-retention-full-type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;count&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;repo1&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;schedules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;full&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Another consequence of filling the metadata for the thin pool mentioned earlier was the disk switched to read-only. Upon recovery I had some corrupted Postgres DBs and needed to restore from backup. The Postgres Operator makes that possible with by poking at different parts of the YAML. It's an incredible tool, but not knowing it inside out, I spent a lot of time feeling frustrated that I couldn't just jump on the server and fix things. Instead, everything is orchestrated, and it's feels a bit like operating a light switch with a broom stick.&lt;/p&gt;
&lt;p&gt;For my use, I don't need anything like the capabilities PGO offers, and I should &lt;a href="https://en.wikipedia.org/wiki/KISS_principle"&gt;KISS&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The big takeaway&lt;/h2&gt;
&lt;p&gt;Understand your tools. Read the docs. Be curious about what can go wrong. Test those scenarios at your leisure, not in production.&lt;/p&gt;
&lt;h2&gt;Next time&lt;/h2&gt;
&lt;p&gt;All of that is gone. I'll be back to describe what replaced it.&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;&lt;a id="fn.1"&gt;1&lt;/a&gt;&lt;/sup&gt; Notably, I've found IPv4 performance is often better over this link too, with lower ping times to many sites with AA compared to Virgin Media, despite having to transit VM to get to AA first.&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;&lt;a id="fn.2"&gt;2&lt;/a&gt;&lt;/sup&gt; You can pick up Microsoft 365 Family for under 50GBP per year if you watch out for offers on the "gift card" version of it. This gives you 6x 1Tb OneDrive accounts, which is some of the cheapest cloud storage out there. Encrypting what you put there is a good idea, so tools like &lt;a href="https://restic.net/"&gt;restic&lt;/a&gt; with &lt;a href="https://rclone.org/"&gt;rclone&lt;/a&gt; are your friends.&lt;/p&gt;</description><pubDate>Sun, 14 Jan 2024 10:17:00 +0000</pubDate><guid>https://s-n.me/differently-complicated-hosting-the-before</guid><category>misc</category><category>hosting</category><category>kubernetes</category><category>ubuntu</category><category>linux</category></item><item><title>Tunnelling VLANs over Wi-Fi with OpenWrt and GRE</title><link>https://s-n.me/tunnelling-vlans-over-wi-fi-with-openwrt-and-gre</link><description>&lt;p&gt;My home network is effectively in two segments, with the cable modem, router/server and access point wired together downstairs and another access point acting as a bridge, connected to a wired network in my office upstairs.&lt;/p&gt;
&lt;p&gt;I'd been wanting to up my security game and split out dedicated networks (VLANs) and SSIDs for trusted devices, guests and untrusted IoT things for a while. One of the frustrating things about using Wi-Fi to bridge the two wired networks is that this typically precludes VLAN functionality.&lt;/p&gt;
&lt;p&gt;I could just do things right and run a cable between the two wired islands, but my willingness to venture into technical escapades vastly exceeds my willingness to drill holes in walls and ceilings. And so, I spent a full day of my 2023 Christmas holiday messing around with &lt;a href="https://openwrt.org/"&gt;OpenWrt&lt;/a&gt;.&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;All my routing is performed by my NUC server, so I'm using both OpenWrt devices purely as access points. Downstairs, I have a &lt;a href="https://openwrt.org/toh/ubiquiti/unifi_6_lr"&gt;Ubiquiti Unifi 6 Long Range&lt;/a&gt; which was easy to reflash with vanilla OpenWrt by following the instructions on that link. Upstairs I have a &lt;a href="https://openwrt.org/toh/linksys/e8450"&gt;Belkin RT3200&lt;/a&gt;, also flashed with vanilla OpenWrt. It turns out both devices are very similar, running the same SoC and Wi-Fi 6 chipsets. The Belkin unit has a 4 x 1GbE port switch + WAN port, where the Unifi has a single 2.5GbE uplink.&lt;/p&gt;
&lt;p&gt;It is possible to pass VLANs over Wi-Fi by tunnelling your L2 network over IP, with the caveat of needing a higher than typically MTU to allow for the overhead. I created a mesh connection between the APs, bringing up network interfaces on either side with static IPs. Over that connection GRETAP devices on either end are able to pass Ethernet frames, and bridge to interfaces exposing endpoints for each VLAN.&lt;/p&gt;
&lt;p&gt;I'm indebted to &lt;a href="https://badgateway.qc.to/vlans-and-wifi/"&gt;oofnikj for his post&lt;/a&gt; and more especially &lt;a href="https://github.com/oofnikj/openwrt-gretap/tree/master"&gt;his config repo&lt;/a&gt; for getting me closer to a working configuration than the official docs.&lt;/p&gt;
&lt;p&gt;If you need or want a similar setup, read on.&lt;/p&gt;
&lt;h2&gt;Tips&lt;/h2&gt;
&lt;p&gt;tcpdump was my friend on this adventure, revealing a storm of DHCP, ARP and multicast traffic at one point that was escaping the VLANs due to the switch in the Belkin unit effectively munging everything together and confusing the rest of the network until I got the configuration correct.&lt;/p&gt;
&lt;p&gt;I started the configuration with both APs on the same wired network, next to each other. There came a point, just after bringing up the tunnel, where I had to make sure to disconnect one of them to avoid loopbacks. STP should take care of this, but didn't seem to for me, though that may have been down to the switch issue above.&lt;/p&gt;
&lt;p&gt;Several times along the way I was saved by the dedicated tunnel network, where one AP was accessible, I could typically SSH from it using the internal IP assigned to the other to fix things.&lt;/p&gt;
&lt;p&gt;My setup involves the trusted network behaving as the primary, untagged VLAN on each other physical Ethernet ports, with the IoT and Guest VLANs tagged.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://nerdout.club/@stephen"&gt;Any feedback&lt;/a&gt; on what I could have done better is most welcome!&lt;/p&gt;
&lt;h2&gt;AP 1 Configuration (Unifi 6 LR, downstairs, attached to router)&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/config/network&lt;/span&gt;

&lt;span class="na"&gt;config interface &amp;#39;loopback&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;lo&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;static&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ipaddr &amp;#39;127.0.0.1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option netmask &amp;#39;255.0.0.0&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config globals &amp;#39;globals&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ula_prefix &amp;#39;fd92&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;bb11:b3e9::/48&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# I&amp;#39;m not clear on why this is a device rather than an interface&lt;/span&gt;
&lt;span class="c1"&gt;# but it was the default config, and it works, so I don&amp;#39;t feel like&lt;/span&gt;
&lt;span class="c1"&gt;# experimenting with it further now!&lt;/span&gt;
&lt;span class="na"&gt;config device&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option name &amp;#39;br-lan&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option type &amp;#39;bridge&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;eth0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# VLAN 1 on the interface connected to the GRE tunnel&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;@trunk.1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option stp &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option igmp_snooping &amp;#39;1&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# this interface holds the IP of the AP on the trusted network&lt;/span&gt;
&lt;span class="na"&gt;config interface &amp;#39;lan&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;br-lan&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# I configure my IPs with fixed entries in DHCP&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;dhcp&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# don&amp;#39;t delegate IPv6, my router will take care of it&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option delegate &amp;#39;0&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# this interface is one end of the underlying IP network for the tunnel&lt;/span&gt;
&lt;span class="c1"&gt;# between the two APs&lt;/span&gt;
&lt;span class="na"&gt;config interface &amp;#39;wtun&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;static&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# any address outside our normal range is fine here&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ipaddr &amp;#39;172.16.0.1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option netmask &amp;#39;255.255.255.0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option delegate &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# it&amp;#39;s here we need a larger-than-normal MTU to be able to pass&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# our 1500 MTU + overhead Ethernet frames&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mtu &amp;#39;2048&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# the trunk bridge acts as the Ethernet endpoint of the GRE tunnel&lt;/span&gt;
&lt;span class="c1"&gt;# and we can create VLAN tagged versions of it to bridge to our&lt;/span&gt;
&lt;span class="c1"&gt;# wired and wireless interfaces&lt;/span&gt;
&lt;span class="na"&gt;config interface &amp;#39;trunk&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option type &amp;#39;bridge&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;none&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option bridge_empty &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option delegate &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option stp &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option defaultroute &amp;#39;0&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# the GRE tunnel itself&lt;/span&gt;
&lt;span class="na"&gt;config interface &amp;#39;gre&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;gretap&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ipaddr &amp;#39;172.16.0.1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option peeraddr &amp;#39;172.16.0.2&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# transit over the dedicated network we created&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option tunlink &amp;#39;wtun&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# attach to our trunk bridge&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option network &amp;#39;trunk&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option df &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mtu &amp;#39;1500&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option delegate &amp;#39;0&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# a bridge connecting the IoT (101) VLAN between the GRE&lt;/span&gt;
&lt;span class="c1"&gt;# tunnel and whatever we want to attach to it&lt;/span&gt;
&lt;span class="na"&gt;config device&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option name &amp;#39;br-iot&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option type &amp;#39;bridge&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;br-lan.101&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;@trunk.101&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option stp &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option igmp_snooping &amp;#39;1&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# bring up an interface on the IoT VLAN for this AP&lt;/span&gt;
&lt;span class="na"&gt;config interface &amp;#39;iot&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;dhcp&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;br-iot&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option defaultroute &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option peerdns &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option delegate &amp;#39;0&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# a bridge connecting the Guest (102) VLAN between the&lt;/span&gt;
&lt;span class="c1"&gt;# GRE tunnel and whatever we want to attach to it&lt;/span&gt;
&lt;span class="na"&gt;config device&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option name &amp;#39;br-guest&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option type &amp;#39;bridge&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;br-lan.102&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;@trunk.102&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option stp &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option igmp_snooping &amp;#39;1&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# bring up an interface on the Guest VLAN for this AP&lt;/span&gt;
&lt;span class="na"&gt;config interface &amp;#39;guest&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;dhcp&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;br-guest&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option defaultroute &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option peerdns &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option delegate &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;AP2 Configuration (RT3200, upstairs)&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/config/network&lt;/span&gt;

&lt;span class="na"&gt;config interface &amp;#39;loopback&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;lo&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;static&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ipaddr &amp;#39;127.0.0.1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option netmask &amp;#39;255.0.0.0&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config globals &amp;#39;globals&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ula_prefix &amp;#39;fddd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;12af:6646::/48&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config interface &amp;#39;wtun&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;static&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ipaddr &amp;#39;172.16.0.2&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option netmask &amp;#39;255.255.255.0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option delegate &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mtu &amp;#39;2048&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config interface &amp;#39;trunk&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option type &amp;#39;bridge&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;none&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option bridge_empty &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option delegate &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option stp &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option defaultroute &amp;#39;0&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config interface &amp;#39;gre&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;gretap&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ipaddr &amp;#39;172.16.0.2&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option peeraddr &amp;#39;172.16.0.1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option tunlink &amp;#39;wtun&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option network &amp;#39;trunk&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option df &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mtu &amp;#39;1500&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# my lan bridge is a little different on this AP due to the switch&lt;/span&gt;
&lt;span class="c1"&gt;# ports available on this device, which allow me to dedicate specific&lt;/span&gt;
&lt;span class="c1"&gt;# ports to VLANs as needed. Here every port is configured with the&lt;/span&gt;
&lt;span class="c1"&gt;# trusted network (VLAN 1) as it&amp;#39;s untagged, primary VLAN and the&lt;/span&gt;
&lt;span class="c1"&gt;# other networks are available with tags on each port.&lt;/span&gt;
&lt;span class="na"&gt;config device&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option name &amp;#39;br-lan&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option type &amp;#39;bridge&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option stp &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option igmp_snooping &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option bridge_empty &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# attach VLAN 1 of the tunnelled Ethernet to this bridge, doesn&amp;#39;t&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# seem quite right, but breaks without it&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;br-trunk.1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan2&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan3&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan4&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;wan&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# the untagged primary trusted VLAN on all ports&lt;/span&gt;
&lt;span class="na"&gt;config bridge-vlan&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;br-lan&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option vlan &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;br-trunk.1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;u*&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;u*&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;u*&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;u*&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan4&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;u*&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;wan&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;u*&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# tag the IoT VLAN on all ports&lt;/span&gt;
&lt;span class="na"&gt;config bridge-vlan&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;br-lan&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option vlan &amp;#39;101&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;t&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;t&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;t&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan4&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;t&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;wan&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;t&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option local &amp;#39;0&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# tag the Guest VLAN on all ports&lt;/span&gt;
&lt;span class="na"&gt;config bridge-vlan&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;br-lan&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option vlan &amp;#39;102&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;t&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;t&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;t&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;lan4&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;t&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;wan&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;t&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# bring up an interface for the AP on the trusted VLAN&lt;/span&gt;
&lt;span class="na"&gt;config interface &amp;#39;lan&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;br-lan.1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;dhcp&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option delegate &amp;#39;0&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config device&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option name &amp;#39;br-iot&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option type &amp;#39;bridge&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option stp &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option igmp_snooping &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;br-lan.101&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;br-trunk.101&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config interface &amp;#39;iot&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;dhcp&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ipv6 &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;br-iot&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option defaultroute &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option peerdns &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option delegate &amp;#39;0&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config device&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option name &amp;#39;br-guest&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option type &amp;#39;bridge&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option stp &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option igmp_snooping &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;br-lan.102&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list ports &amp;#39;br-trunk.102&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config interface &amp;#39;guest&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option proto &amp;#39;dhcp&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ipv6 &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;br-guest&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option defaultroute &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option peerdns &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option delegate &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Common configuration&lt;/h2&gt;
&lt;p&gt;The wireless and firewall configurations are identical on both APs. If your APs have different hardware, you'll likely need to adjust the radio sections for each.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/config/wireless&lt;/span&gt;
&lt;span class="na"&gt;config wifi-device &amp;#39;radio0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option type &amp;#39;mac80211&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option path &amp;#39;platform/18000000.wmac&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option channel &amp;#39;6&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option band &amp;#39;2g&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option htmode &amp;#39;HT20&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option cell_density &amp;#39;0&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config wifi-device &amp;#39;radio1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option type &amp;#39;mac80211&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option phy &amp;#39;wl1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option country &amp;#39;US&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option cell_density &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option htmode &amp;#39;HE80&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option band &amp;#39;5g&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option channel &amp;#39;149&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# the mesh network underlying our GRE tunnel&lt;/span&gt;
&lt;span class="c1"&gt;# I&amp;#39;m only using the 5GHz radio for this, because the signal is strong&lt;/span&gt;
&lt;span class="c1"&gt;# and the access points are static&lt;/span&gt;
&lt;span class="na"&gt;config wifi-iface &amp;#39;wifinet7&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;radio1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mode &amp;#39;mesh&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option encryption &amp;#39;sae&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mesh_id &amp;#39;upstream&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mesh_fwding &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mesh_rssi_threshold &amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option key &amp;#39;SomeRandomString&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option network &amp;#39;wtun&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ifname &amp;#39;wtun&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# the 2GHz version of my trusted network SSID&lt;/span&gt;
&lt;span class="na"&gt;config wifi-iface &amp;#39;wifinet2&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;radio0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mode &amp;#39;ap&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ssid &amp;#39;Trusted&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option encryption &amp;#39;sae&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option key &amp;#39;SuperStrongPassphrase&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option network &amp;#39;lan&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# the 5GHz version of my trusted network SSID&lt;/span&gt;
&lt;span class="na"&gt;config wifi-iface &amp;#39;wifinet5&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;radio1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mode &amp;#39;ap&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ssid &amp;#39;Trusted&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option encryption &amp;#39;sae-mixed&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option key &amp;#39;SuperStrongPassphrase&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option network &amp;#39;lan&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# the 2GHz version of my Guest network SSID&lt;/span&gt;
&lt;span class="na"&gt;config wifi-iface &amp;#39;wifinet9&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;radio0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mode &amp;#39;ap&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ssid &amp;#39;Guest&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option encryption &amp;#39;sae-mixed&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# setting isolate to prevent wireless devices on this&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# SSID from talking to each other (same on IoT)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option isolate &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option key &amp;#39;welcomeguest&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option network &amp;#39;guest&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# the 5GHz version of my Guest network SSID&lt;/span&gt;
&lt;span class="na"&gt;config wifi-iface &amp;#39;wifinet8&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;radio1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mode &amp;#39;ap&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ssid &amp;#39;Guest&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option encryption &amp;#39;sae-mixed&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option key &amp;#39;letmeinplease&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option network &amp;#39;welcomeguest&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option isolate &amp;#39;1&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# the 2GHz version of my IoT network SSID&lt;/span&gt;
&lt;span class="na"&gt;config wifi-iface &amp;#39;wifinet10&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;radio0&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mode &amp;#39;ap&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ssid &amp;#39;IoT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option encryption &amp;#39;psk2&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option isolate &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option key &amp;#39;untrusted&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option network &amp;#39;iot&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# the 5GHz version of our my network SSID&lt;/span&gt;
&lt;span class="na"&gt;config wifi-iface &amp;#39;wifinet11&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option device &amp;#39;radio1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option mode &amp;#39;ap&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option ssid &amp;#39;IoT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option encryption &amp;#39;psk2&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option isolate &amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option key &amp;#39;untrusted&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option network &amp;#39;iot&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/config/firewall&lt;/span&gt;
&lt;span class="na"&gt;config defaults&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option input &amp;#39;REJECT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option output &amp;#39;ACCEPT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option forward &amp;#39;REJECT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option synflood_protect &amp;#39;1&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# I have a firewall zone for each network&lt;/span&gt;
&lt;span class="na"&gt;config zone&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option name &amp;#39;lan&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# only the trusted network and underlying tunnel network&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# have &amp;quot;input &amp;#39;ACCEPT&amp;#39;&amp;quot; which will allow access to the&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# AP admin interfaces&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option input &amp;#39;ACCEPT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option output &amp;#39;ACCEPT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option forward &amp;#39;ACCEPT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list network &amp;#39;lan&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config zone&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option name &amp;#39;guest&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option input &amp;#39;REJECT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option output &amp;#39;ACCEPT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option forward &amp;#39;ACCEPT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list network &amp;#39;guest&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config zone&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option name &amp;#39;iot&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option input &amp;#39;REJECT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option output &amp;#39;ACCEPT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option forward &amp;#39;ACCEPT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list network &amp;#39;iot&amp;#39;&lt;/span&gt;

&lt;span class="na"&gt;config zone&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option name &amp;#39;tunnel&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option input &amp;#39;ACCEPT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option output &amp;#39;ACCEPT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;option forward &amp;#39;ACCEPT&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;list network &amp;#39;wtun&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><pubDate>Wed, 20 Dec 2023 19:19:00 +0000</pubDate><guid>https://s-n.me/tunnelling-vlans-over-wi-fi-with-openwrt-and-gre</guid><category>misc</category></item><item><title>Moving from macOS to Linux: The unknowns</title><link>https://s-n.me/moving-from-macos-to-linux-the-unknowns</link><description>&lt;p&gt;&lt;em&gt;Updated with Preview.app features, prompted by &lt;a href="https://mastodon.neilzone.co.uk/@neil"&gt;Neil's&lt;/a&gt; helpful response &lt;a href="https://neilzone.co.uk/2023/11/desktop-linux-the-software-im-currently-using/"&gt;Desktop Linux: the software I'm currently using&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With my new laptop coming in a few days, I'm finally thinking through the implications of moving away from macOS whilst still having an iPhone. These are some questions I need to answer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How will I listen to music?&lt;/li&gt;
&lt;li&gt;Currently streaming with Apple Music, blended by the Music app with my local music collection&lt;/li&gt;
&lt;li&gt;How will I manage photos?&lt;/li&gt;
&lt;li&gt;Currently in Photos, synced with iCloud, mostly originating from my phone, with a collection of old stuff originally synced from the Mac&lt;/li&gt;
&lt;li&gt;How will I do quick annotations on screenshots, markup PDFs, resize/crop/export images without Preview.app?&lt;/li&gt;
&lt;li&gt;What Passkeys do I need to migrate out of iCloud (or the Mac specifically)?&lt;/li&gt;
&lt;li&gt;What other credentials are in my Mac/iCloud keychain that aren't in 1Password for some reason?&lt;/li&gt;
&lt;li&gt;Safari has been my primary browser and macOS, and will likely continue to be on iOS&lt;/li&gt;
&lt;li&gt;I tend to throw stuff at Reading List to pick up later, which I won't have access to on my new computer, so what to do instead?&lt;/li&gt;
&lt;li&gt;Should I keep an old Mac around, running?&lt;/li&gt;
&lt;li&gt;Logged into iCloud, it could provide another authentication factor for iDevices, which assume you've got some other Apple device nearby to do authentication&lt;/li&gt;
&lt;li&gt;There are bridges for iMessage that could let me continue to read/reply to iMessage and SMS from my phone&lt;/li&gt;
&lt;li&gt;I have an old MacBook Pro with a non-functioning screen that's too expensive to fix that could do the job&lt;/li&gt;
&lt;li&gt;I subscribe to Microsoft 365, mainly for cheap cloud storage (effectively 6Tb for around ~£45 per year when bought on semi-frequent offer), but do use Excel for a few tasks. Should I...&lt;/li&gt;
&lt;li&gt;Migrate away to something else?&lt;/li&gt;
&lt;li&gt;Use the web version?&lt;/li&gt;
&lt;li&gt;Try Wine/Crossover/Windows VM?&lt;/li&gt;
&lt;li&gt;I almost entirely interact with Mastodon through Ivory, on my phone and with the macOS app. I really like that it stays in sync across both with my read position. Am I just going to use the iOS app now, or is there some other solution?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I guess I'll find my answers in the coming weeks.&lt;/p&gt;</description><pubDate>Sun, 12 Nov 2023 18:10:00 +0000</pubDate><guid>https://s-n.me/moving-from-macos-to-linux-the-unknowns</guid><category>misc</category><category>macos</category><category>linux</category></item><item><title>Non-specific nerd thoughts</title><link>https://s-n.me/non-specific-nerd-thoughts</link><description>&lt;p&gt;That whole &lt;a href="https://s-n.me/writing-more"&gt;writing more&lt;/a&gt; thing went well, didn't it? ?&lt;/p&gt;
&lt;p&gt;Let's try a little brain dump of the nerdy things I've assigned myself to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replace macOS with &lt;a href="https://nixos.org/"&gt;NixOS&lt;/a&gt; as my primary computing experience.&lt;/li&gt;
&lt;li&gt;Being a cloud infrastructure engineer, I love me some declarative configuration.&lt;/li&gt;
&lt;li&gt;Apple annoyed me with the offer of a £700 fix for the most expensive computer I ever purchased that was just out of warranty and had display issues. &lt;ul&gt;
&lt;li&gt;The £700 was to replace a display that's screwed because the connecting cable has been damaged by their hinge design. There is a company in London that offer a £300 repair. Still quite ouchy.&lt;/li&gt;
&lt;li&gt;It's an Intel Mac, how long are they even likely to support it anyway?&lt;/li&gt;
&lt;li&gt;It was already passed on to Sean, replaced for me by a 14" M1 Pro.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://frame.work"&gt;Framework&lt;/a&gt; seems kinda great, so I'm eagerly awaiting my Ryzen-based Framework 13. At which point Sean can have the 14" and become untethered from a desk again.&lt;/li&gt;
&lt;li&gt;I'll miss the screen of the MacBook, but I think even more I'll miss the speakers.&lt;/li&gt;
&lt;li&gt;Capture my (non-phone) computing world into a Git repo.&lt;/li&gt;
&lt;li&gt;Nix allows me to describe my systems and my user environments in code!&lt;/li&gt;
&lt;li&gt;On that whole phone thing, wouldn't it be nice if there were some genuinely open-source phone ecosystem without Google that could actually run the apps I've come to depend on (banking, etc)?&lt;/li&gt;
&lt;li&gt;I've sadly accepted my iPhone 12 mini will eventually be replaced with another iPhone.&lt;/li&gt;
&lt;li&gt;Adopt a more keyboard-centric computing life, with a tiling window manager.&lt;/li&gt;
&lt;li&gt;Endless configuration tweaking awaits.&lt;/li&gt;
&lt;li&gt;Have a go at (neo)vim being my primary editor again, or otherwise invest properly in VSCod(e|ium).&lt;/li&gt;
&lt;li&gt;I might as well go all in, eh?&lt;/li&gt;
&lt;li&gt;Failing at this and continuing to use GoLand and PyCharm wouldn't be terrible.&lt;/li&gt;
&lt;li&gt;Migrate my Hetzner dedicated server running this site, and my Masto instance to something else (self-host, Hetzner cloud).&lt;/li&gt;
&lt;li&gt;It's way over-specced for my current use (but 50€ for 128GB RAM, 2x1Tb SSD and 8-core i9-9900K is amazing value).&lt;/li&gt;
&lt;li&gt;I use microk8s on it as a single node instance, which is pretty inflexible, storage being a large part of sense of unease with that.&lt;/li&gt;
&lt;li&gt;I made the mistake of choosing LVM and let LXD create a thin-pool consuming 100% of the remaining storage.&lt;/li&gt;
&lt;li&gt;It ran out of metadata space, and you can't grow it into the unused data space, so I had to de-RAID1 the SSDs to make things work so that's a bit of a mess now.&lt;/li&gt;
&lt;li&gt;Said mess returns errors when trying to do operations like take snapshots for backups, or even delete old snapshots, a reboot might fix it, or it might leave me with a complex failed boot to resolve.&lt;/li&gt;
&lt;li&gt;Hetzner Cloud seems to be pretty darn cheap, and I can create a proper Kubernetes environment with separate control plane and real storage volumes and come in at similar or lower price than the dedicated server, albeit with less overall resources and with non-dedicated CPU.&lt;/li&gt;
&lt;li&gt;I experimented briefly with &lt;a href="https://talos.dev/"&gt;Talos Linux&lt;/a&gt; on it yesterday, and it went pretty smoothly.&lt;/li&gt;
&lt;li&gt;I can Terraform it (or OpenTF eventually, right?)&lt;/li&gt;
&lt;li&gt;Or maybe I can just host it all at home on a single box?&lt;/li&gt;
&lt;li&gt;Migrate my home server from Ubuntu with LXD to NixOS with &lt;em&gt;something&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;It's an 8th gen NUC in a fanless Asaka case.&lt;/li&gt;
&lt;li&gt;It's my router and adds IPv6 to my home Internet with &lt;a href="https://www.aa.net.uk/broadband/l2tp-service/"&gt;AAISP's L2TP service&lt;/a&gt;, because Virgin Media can't even, but I'm definitely taking their 1Gb/100Mb service over my next best option of 40Mb/10Mb DSL.&lt;/li&gt;
&lt;li&gt;It's running an LXD-launched VM for Home Assistant, and LXD containers for Plex, some *aars, etc.&lt;/li&gt;
&lt;li&gt;It's also built on the same LVM thin pool scheme as the Hetzner box, so is ultimately doomed.&lt;/li&gt;
&lt;li&gt;I'm clearly going to NixOS it, and &lt;a href="https://astro.github.io/microvm.nix/"&gt;microvms&lt;/a&gt; looks super interesting.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I can't help think of &lt;a href="https://www.youtube.com/watch?v=FJpVmGiAA_c"&gt;Amelie's father and his toolbox&lt;/a&gt;.&lt;/p&gt;</description><pubDate>Thu, 24 Aug 2023 10:17:00 +0100</pubDate><guid>https://s-n.me/non-specific-nerd-thoughts</guid><category>misc</category><category>nixos</category><category>linux</category><category>cloud</category></item><item><title>Writing more</title><link>https://s-n.me/writing-more</link><description>&lt;p&gt;I want to improve my writing, so I need to do more of it.&lt;/p&gt;
&lt;p&gt;Improving means that I can communicate effectively, efficiently and engagingly. It means you can understand me, that I'm not wasting your time, and that I can hold your attention long enough to impart what I wanted you to know.&lt;/p&gt;
&lt;p&gt;Part of my job as a software engineer is to express ideas in a way that is understandable by machines. More importantly, those ideas can be understood by other engineers who work with me now and in the future, whether I'm present or not. That is what good code means to me.&lt;/p&gt;
&lt;p&gt;When I write for a machine I'm able to get feedback near instantly. Frequently that feedback is unambiguous. I was either successful in communicating my intent, or I was not. Code reviews help ensure I'm successfully communicating with other engineers too.&lt;/p&gt;
&lt;p&gt;Much of my work is useful to people who shouldn't need to understand the details of it’s construction to find it helpful. I should be able to describe it in a way that respects their time and attention. Documentation matters.&lt;/p&gt;
&lt;p&gt;Career progression requires I am able to communicate ideas in a way that places them in a wider context to demonstrate their value. I distill the ideas of multiple people and in doing so am representing them as well as myself.&lt;/p&gt;
&lt;p&gt;I need to write to justify advancements, to mediate conflict, to convince others to invest their resources, to give candid feedback kindly, to explain failure, and to celebrate success.&lt;/p&gt;
&lt;p&gt;I need to be able to do all of that in a timely manner that leaves room for all the other things to be done. Improving also means writing more quickly when that's necessary.&lt;/p&gt;
&lt;p&gt;Success will be hard to measure here. Improvement needs feedback as well as practice. That's why I'm writing this publicly.&lt;/p&gt;
&lt;p&gt;The most uncomfortable part is convincing myself that this is good enough, when I know that it could be better. If the only outcome is that getting easier, then this will be worthwhile.&lt;/p&gt;</description><pubDate>Sun, 19 Feb 2023 08:45:00 +0000</pubDate><guid>https://s-n.me/writing-more</guid><category>misc</category></item></channel></rss>