<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>~f1nn</title>
    <subtitle>posts &amp; thoughts of a random person on the internet</subtitle>
    <link rel="self" type="application/atom+xml" href="/atom.xml"/>
    <link rel="alternate" type="text/html" href="/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2025-11-24T00:00:00+00:00</updated>
    <id>/atom.xml</id>
    <entry xml:lang="en">
        <title>Using Mullvad VPN on top of Headscale</title>
        <published>2025-11-24T00:00:00+00:00</published>
        <updated>2025-11-24T00:00:00+00:00</updated>
        <author>
          <name>
            
              f1nn
            
          </name>
        </author>
        <link rel="alternate" type="text/html" href="/posts/headscale-with-mullvad/"/>
        <id>/posts/headscale-with-mullvad/</id>
        <content type="html" xml:base="/posts/headscale-with-mullvad/">&lt;p&gt;I recently switched to a self-hosted &lt;strong&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;juanfont&#x2F;headscale&quot;&gt;Headscale&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; instance for my homelab, as I don’t like to be dependent on a central service (&lt;strong&gt;Tailscale&lt;&#x2F;strong&gt;) for connectivity between my devices.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-issue&quot;&gt;The issue&lt;&#x2F;h1&gt;
&lt;p&gt;I consider myself pretty paranoid (&lt;em&gt;as you should be&lt;&#x2F;em&gt;), so I like to be connected to Mullvad VPN at all times. To make the Mullvad standalone app and Tailscale work together nicely, I had to use a hacky &lt;code&gt;nftables&lt;&#x2F;code&gt; rule to tell Mullvad to pass-through all traffic from Tailscale, by marking it with a special “tracking mark” — you can read more about this on &lt;a href=&quot;https:&#x2F;&#x2F;mullvad.net&#x2F;en&#x2F;help&#x2F;split-tunneling-with-linux-advanced#allow-ip&quot;&gt;Mullvad’s site&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#272822;color:#f8f8f2;&quot;&gt;&lt;code&gt;&lt;span&gt;table inet mullvad_tailscale {
&lt;&#x2F;span&gt;&lt;span&gt;  chain output {
&lt;&#x2F;span&gt;&lt;span&gt;    type route hook output priority 0; policy accept;
&lt;&#x2F;span&gt;&lt;span&gt;    ip daddr 100.64.0.0&#x2F;10 ct mark set 0x00000f41 meta mark set 0x6d6f6c65;
&lt;&#x2F;span&gt;&lt;span&gt;  }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This works fine on my desktop and laptop, but how can you use Tailscale and Mullvad simultaneously on mobile? Android only supports one connected VPN at a time, and it would also be nice to get rid of the standalone Mullvad app on desktop and just connect to the relays directly, without the &lt;code&gt;nftables&lt;&#x2F;code&gt; hack.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;doing-some-research&quot;&gt;Doing some research&lt;&#x2F;h1&gt;
&lt;p&gt;While scouting the Headscale repo, I stumbled across issue &lt;strong&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;juanfont&#x2F;headscale&#x2F;issues&#x2F;1545&quot;&gt;#1545&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; requesting support for “Wireguard-only peers,” exactly what Tailscale added to support Mullvad exit nodes.&lt;&#x2F;p&gt;
&lt;p&gt;At first the issue seemed pretty stale, but coincidentally a &lt;strong&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;juanfont&#x2F;headscale&#x2F;pull&#x2F;2892&quot;&gt;pull request&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; was just recently opened to implement this feature. The developer said they’ve been using this feature for a while already &amp;amp; it sounded pretty feature-complete, so I decided to give it a try.&lt;&#x2F;p&gt;
&lt;p&gt;If you’re on NixOS, making use of this pull request is as easy as …&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;nix&quot; style=&quot;background-color:#272822;color:#f8f8f2;&quot; class=&quot;language-nix &quot;&gt;&lt;code class=&quot;language-nix&quot; data-lang=&quot;nix&quot;&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;pkgs&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;, ... &lt;&#x2F;span&gt;&lt;span&gt;}: {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a6e22e;&quot;&gt;services&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a6e22e;&quot;&gt;headscale &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#75715e;&quot;&gt;# ...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a6e22e;&quot;&gt;package &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;pkgs&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;headscale&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;overrideAttrs &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;: {
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a6e22e;&quot;&gt;src &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;pkgs&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;fetchFromGitHub &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a6e22e;&quot;&gt;owner &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;iridated&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a6e22e;&quot;&gt;repo &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;headscale&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a6e22e;&quot;&gt;rev &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;wireguard-only-peers&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a6e22e;&quot;&gt;hash &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#75715e;&quot;&gt;# build once to get the hash
&lt;&#x2F;span&gt;&lt;span&gt;      };
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a6e22e;&quot;&gt;vendorHash &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#75715e;&quot;&gt;# build once to get the hash
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;  };
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;how-to-the-manual-way&quot;&gt;How to (the manual way)&lt;&#x2F;h1&gt;
&lt;p&gt;First, I’ll cover the rather painful manual way to create a Mullvad Wireguard-only node in your tailnet. I assume you already have some basic knowledge about Headscale and its CLI interface.&lt;&#x2F;p&gt;
&lt;p&gt;If you don’t care about how this works behind the scenes, you can just do it the &lt;strong&gt;&lt;a href=&quot;&#x2F;posts&#x2F;headscale-with-mullvad&#x2F;#how-to-the-automatic-way&quot;&gt;automatic way&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;1-pick-a-relay-and-add-it-to-headscale&quot;&gt;1. Pick a relay and add it to Headscale&lt;&#x2F;h2&gt;
&lt;p&gt;You can search for relays on &lt;a href=&quot;https:&#x2F;&#x2F;mullvad.net&#x2F;en&#x2F;servers&quot;&gt;Mullvad’s site&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#272822;color:#f8f8f2;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span&gt;RELAY_PUBLIC_KEY&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;zOBWmQ3BEOZKsYKbj4dC2hQjxCbr3eKa6wGWyEDYbC4=
&lt;&#x2F;span&gt;&lt;span&gt;RELAY_IPV4&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;176.125.235.73
&lt;&#x2F;span&gt;&lt;span&gt;RELAY_IPV6&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;2a02:20c8:4124::a03f
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;USER_ID&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;1234
&lt;&#x2F;span&gt;&lt;span&gt;NODE_NAME&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;cool-node-name
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To make my life easier I’ll use this function to convert a Wireguard key to a Tailscale &lt;code&gt;nodekey&lt;&#x2F;code&gt;, which the Headscale CLI interface expects.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#272822;color:#f8f8f2;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#a6e22e;&quot;&gt;wg2ts&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#66d9ef;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;nodekey:&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#66d9ef;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;$1&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;base64&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt; -d &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ae81ff;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;dev&#x2F;null &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;od&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt; -An -tx1 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;tr&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt; -d &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;#39; \n&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, let’s create the Wireguard node in the tailnet. This makes the Mullvad relay known to Headscale, but I can’t really do anything with it yet.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#272822;color:#f8f8f2;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span&gt;$ headscale node register-wg-only \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    --name &lt;&#x2F;span&gt;&lt;span&gt;$NODE_NAME \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    --user &lt;&#x2F;span&gt;&lt;span&gt;$USER_ID \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    --public-key &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;$(wg2ts $RELAY_PUBLIC_KEY)&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;\
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    --allowed-ips &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;0.0.0.0&#x2F;0, ::&#x2F;0&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;\
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    --endpoints &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;$RELAY_IPV4:51820, $RELAY_IPV6:51820&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;\
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    --extra-config &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;#39;{&amp;quot;suggestExitNode&amp;quot;: true}&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;WireGuard-only peer $NODE_NAME registered (allocated IPs: ..., ...)&lt;&#x2F;span&gt;&lt;span style=&quot;color:#66d9ef;&quot;&gt;.
&lt;&#x2F;span&gt;&lt;span&gt;Use &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;#39;nodes add-wg-connection&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt; to connect nodes.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I’ll use this command to figure out the ID of our Wireguard node, as the above command doesn’t echo it. Save this as &lt;code&gt;WIREGUARD_NODE_ID&lt;&#x2F;code&gt; for later use.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#272822;color:#f8f8f2;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span&gt;$ headscale node list&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt; -o&lt;&#x2F;span&gt;&lt;span&gt; json \
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;jq&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt; -r &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;.wireguard_only_peers[] | select(.name == &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ae81ff;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;$NODE_NAME&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ae81ff;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;) | .id&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span&gt;100000001
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;2-register-a-tailscale-device-with-mullvad-to-allow-authentication-with-relays&quot;&gt;2. Register a Tailscale device with Mullvad to allow authentication with relays&lt;&#x2F;h2&gt;
&lt;p&gt;To actually make use of the added Mullvad relay, I’ll first have to make it known to Mullvad using their API.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#272822;color:#f8f8f2;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span&gt;MULLVAD_ACCOUNT&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;1234567890123456
&lt;&#x2F;span&gt;&lt;span&gt;NODE_ID&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;5678
&lt;&#x2F;span&gt;&lt;span&gt;NODEKEY&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;$(headscale node list&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt; -o&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt; json \
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;jq&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt; -r &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;.nodes[] | select(.id == $NODE_ID) | .node_key&amp;quot;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now the other way around: Mullvad expects you to register a Wireguard public key, so I’ll use yet another function to convert the &lt;code&gt;nodekey&lt;&#x2F;code&gt; to one.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#272822;color:#f8f8f2;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#a6e22e;&quot;&gt;ts2wg&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    hex&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#66d9ef;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;$1&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;sed &amp;#39;s&#x2F;^nodekey:&#x2F;&#x2F;i; s&#x2F;[^0-9a-fA-F]&#x2F;&#x2F;g&amp;#39;)&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#66d9ef;&quot;&gt;printf &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;#39;%b&amp;#39; &amp;quot;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#66d9ef;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;$hex&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;sed &amp;#39;s&#x2F;..&#x2F;\\x&amp;amp;&#x2F;g&amp;#39;)&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;base64 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f92672;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;tr&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt; -d &lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;#39;\n&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#66d9ef;&quot;&gt;echo
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Let’s register the device with Mullvad now. This makes the Wireguard public key of the chosen device known to Mullvad and allows my device to connect to their relays.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#272822;color:#f8f8f2;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span&gt;$ curl&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt; -sSL &lt;&#x2F;span&gt;&lt;span&gt;\
&lt;&#x2F;span&gt;&lt;span&gt;    https:&#x2F;&#x2F;api.mullvad.net&#x2F;wg \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    -d&lt;&#x2F;span&gt;&lt;span&gt; account=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;$MULLVAD_ACCOUNT&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;\
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    --data-urlencode&lt;&#x2F;span&gt;&lt;span&gt; pubkey=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#e6db74;&quot;&gt;&amp;quot;$(ts2wg $NODEKEY)&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;10.70.37.216&#x2F;32,fc00:bbbb:bbbb:bb01::7:3bf2&#x2F;128
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;You can also register the public key using &lt;a href=&quot;https:&#x2F;&#x2F;mullvad.net&#x2F;en&#x2F;account&#x2F;devices&quot;&gt;Mullvad’s web interface&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;3-create-a-connection-to-the-relay&quot;&gt;3. Create a connection to the relay&lt;&#x2F;h2&gt;
&lt;p&gt;Now that I know the IPs Mullvad wants my device to connect with, I can make the connection known to my device in the tailnet. Without these special masquerade IPs, connections to the relay would not work. The masquerade IPs are unique for each public key, so you can re-use them for other relays.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#272822;color:#f8f8f2;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span&gt;$ headscale node add-wg-connection \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    --node-id &lt;&#x2F;span&gt;&lt;span&gt;$NODE_ID \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    --wg-peer-id &lt;&#x2F;span&gt;&lt;span&gt;$WIREGUARD_NODE_ID \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    --ipv4-masq-addr &lt;&#x2F;span&gt;&lt;span&gt;$IPV4_FROM_ABOVE \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    --ipv6-masq-addr &lt;&#x2F;span&gt;&lt;span&gt;$IPV6_FROM_ABOVE
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;Connection created between node $NODE_ID and WireGuard peer $WIREGUARD_NODE_ID
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;how-to-the-automatic-way&quot;&gt;How to (the automatic way)&lt;&#x2F;h1&gt;
&lt;p&gt;Understandably, no one wants to manually create all of the Mullvad relays by hand. So I wrote a &lt;strong&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;f1nniboy&#x2F;headscale-mullvad&quot;&gt;small Python script&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; to help with maintaining these relays &amp;amp; “connections” to nodes.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;And you’re &lt;strong&gt;done&lt;&#x2F;strong&gt;! Now you just have to&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;force-close the Tailscale mobile app &amp;amp; select the exit node from the drop down menu &lt;strong&gt;or&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;restart the Tailscale daemon and &lt;code&gt;tailscale set --exit-node=$NODE_NAME&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;closing-words&quot;&gt;Closing words&lt;&#x2F;h1&gt;
&lt;p&gt;These steps should also apply to any other VPN provider giving you direct access to the Wireguard relays. I have been daily-driving this setup for a few days now without encountering any issues. Keep in mind though, this feature is pretty much still &lt;strong&gt;work-in-progress&lt;&#x2F;strong&gt; and not merged into the repository yet. I’ll do my best to keep the steps up-to-date.&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Chromecast sucks at certificates</title>
        <published>2025-11-22T00:00:00+00:00</published>
        <updated>2025-11-22T00:00:00+00:00</updated>
        <author>
          <name>
            
              f1nn
            
          </name>
        </author>
        <link rel="alternate" type="text/html" href="/posts/chromecast-sucks/"/>
        <id>/posts/chromecast-sucks/</id>
        <content type="html" xml:base="/posts/chromecast-sucks/">&lt;p&gt;I needed to install a self-signed certificate on my Chromecast&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-1-1&quot;&gt;&lt;a href=&quot;#fn-1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; for my homelab, since I decided to go the &lt;strong&gt;run-your-own-certificate-authority&lt;&#x2F;strong&gt; route — more about that setup in a later blog post.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;android-means-freedom-right&quot;&gt;Android means freedom, right?&lt;&#x2F;h1&gt;
&lt;p&gt;With Android being the most open OS of the mobile monopoly, I thought it would be a 5-minute task to install the root certificate. First, I tried downloading it using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;truefedex&#x2F;tv-bro&quot;&gt;TV Bro&lt;&#x2F;a&gt;, a browser for Chromecast, and installing it by simply opening the file, which …&lt;&#x2F;p&gt;
&lt;img
    src=&quot;first-try.png&quot;
    alt=&quot;Popup with title &amp;#x27;Install CA certificates in Settings&amp;#x27; and subtext &amp;#x27;This certificate from null must be installed in Settings. Only install CA certificates from organizations you trust.&amp;#x27;&quot;
    fetchpriority=&quot;high&quot;
    width=&quot;1920&quot;
    height=&quot;1080&quot;
&gt;
&lt;p&gt;… didn’t work. Supposedly, installing certificates like this used to work fine on older Android versions, but they decided it was a consumer-friendly idea to lock down the device I paid for.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;settings&quot;&gt;Settings&lt;&#x2F;h2&gt;
&lt;p&gt;Easy, you might say: just follow what the system popup says and look for …&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Security &amp;amp; Privacy
&lt;ul&gt;
&lt;li&gt;More security &amp;amp; privacy
&lt;ul&gt;
&lt;li&gt;Encryption &amp;amp; credentials&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;… in Settings, where you can usually install certificates on Android devices. Well, for whatever ungodly reason, Google decided to strip this setting from the Chromecast entirely.&lt;&#x2F;p&gt;
&lt;img
    src=&quot;security-options.png&quot;
    alt=&quot;&amp;#x27;Privacy&amp;#x27; menu in Settings, with the &amp;#x27;Security&amp;#x27; sub-menu open. The &amp;#x27;Security&amp;#x27; sub-menu only contains the entries &amp;#x27;Scan apps with Play Protect&amp;#x27; and &amp;#x27;Improve harmful app detection&amp;#x27;.&quot;
    fetchpriority=&quot;high&quot;
    width=&quot;1920&quot;
    height=&quot;1080&quot;
&gt;
&lt;h2 id=&quot;certinstaller&quot;&gt;CertInstaller&lt;&#x2F;h2&gt;
&lt;p&gt;Following that failure, I tried being slick by copying the root certificate to some random location on the Chromecast using &lt;code&gt;adb&lt;&#x2F;code&gt; and manually installing it using the &lt;code&gt;com.android.certinstaller&lt;&#x2F;code&gt; package.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#272822;color:#f8f8f2;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span&gt;adb shell am start \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    -n&lt;&#x2F;span&gt;&lt;span&gt; com.android.certinstaller&#x2F;.CertInstallerMain \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    -a&lt;&#x2F;span&gt;&lt;span&gt; android.intent.action.VIEW \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    -t&lt;&#x2F;span&gt;&lt;span&gt; application&#x2F;x-x509-ca-cert \
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#fd971f;&quot;&gt;    -d&lt;&#x2F;span&gt;&lt;span&gt; file:&#x2F;&#x2F;&#x2F;data&#x2F;local&#x2F;tmp&#x2F;root.crt
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I ran this command to no avail; it resulted in the same error popup as before. This wasn’t very surprising, as opening the certificate in the browser does pretty much the same thing as the command above.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-now&quot;&gt;What now?&lt;&#x2F;h1&gt;
&lt;p&gt;Funnily enough, it is actually a breeze to install certificates on an Apple TV, allowing you to directly download a root certificate via URL. Thanks to Google’s restrictions, I have just &lt;em&gt;given up&lt;&#x2F;em&gt; on installing the root certificate for now and just connect to my Jellyfin instance using &lt;code&gt;http:&#x2F;&#x2F;&lt;&#x2F;code&gt;. Not the solution I was hoping for, but it will do for now.&lt;&#x2F;p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;or Google TV, &lt;em&gt;whatever&lt;&#x2F;em&gt; &lt;a href=&quot;#fr-1-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;section&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>My fellow nixxers</title>
        <published>2025-11-09T00:00:00+00:00</published>
        <updated>2025-11-09T00:00:00+00:00</updated>
        <author>
          <name>
            
              f1nn
            
          </name>
        </author>
        <link rel="alternate" type="text/html" href="/posts/my-fellow-nixxers/"/>
        <id>/posts/my-fellow-nixxers/</id>
        <content type="html" xml:base="/posts/my-fellow-nixxers/">&lt;p&gt;I have been planning on creating a blog for myself for a pretty long time. I like to share my knowledge and random stuff I discover over time. As I recently started out on my journey with NixOS, I thought &lt;em&gt;now&lt;&#x2F;em&gt; would be a great time to build this small echo chamber for myself.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-backend&quot;&gt;The backend&lt;&#x2F;h1&gt;
&lt;p&gt;This blog is being generated by &lt;a href=&quot;https:&#x2F;&#x2F;www.getzola.org&quot;&gt;Zola&lt;&#x2F;a&gt;, a (to me) new static generator. It’s pretty neat and, of course, &lt;strong&gt;blazingly fast&lt;&#x2F;strong&gt;&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-1-1&quot;&gt;&lt;a href=&quot;#fn-1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; 🚀🚀🚀. Everything is fully declaratively managed using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;f1nniboy&#x2F;dots&quot;&gt;NixOS&lt;&#x2F;a&gt;. Then, to let you see this page, I use &lt;a href=&quot;https:&#x2F;&#x2F;caddyserver.com&quot;&gt;Caddy&lt;&#x2F;a&gt; as the web server, running on a cheap &lt;a href=&quot;https:&#x2F;&#x2F;hetzner.com&quot;&gt;Hetzner&lt;&#x2F;a&gt; VPS.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-s-to-come&quot;&gt;What’s to come?&lt;&#x2F;h1&gt;
&lt;p&gt;I have pretty much just scratched the surface on NixOS and getting to know its “&lt;em&gt;beauty&lt;&#x2F;em&gt;”. I’ll probably use this blog as a place to vent or share random bits of knowledge in the future.&lt;&#x2F;p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;Yes, that’s one of the first things mentioned on their site. Why are Rust devs like this? &lt;a href=&quot;#fr-1-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;section&gt;
</content>
    </entry>
</feed>
