<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Headscale on Carlos Vaz</title>
    <link>https://carlosvaz.com/tags/headscale/</link>
    <description>Recent content in Headscale on Carlos Vaz</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-US</language>
    <managingEditor>carlos@carjorvaz.com (Carlos Vaz)</managingEditor>
    <webMaster>carlos@carjorvaz.com (Carlos Vaz)</webMaster>
    <lastBuildDate>Sun, 11 Dec 2022 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://carlosvaz.com/tags/headscale/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Setting up Headscale on NixOS</title>
      <link>https://carlosvaz.com/posts/setting-up-headscale-on-nixos/</link>
      <pubDate>Sun, 11 Dec 2022 00:00:00 +0000</pubDate><author>carlos@carjorvaz.com (Carlos Vaz)</author>
      <guid>https://carlosvaz.com/posts/setting-up-headscale-on-nixos/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve recently bought a used USFF computer at a good price, inspired by the &lt;a href=&#34;https://www.servethehome.com/introducing-project-tinyminimicro-home-lab-revolution/&#34;&gt;TinyMiniMicro project&lt;/a&gt;.&#xA;It&amp;rsquo;s super fast, has plenty of storage and memory (32GB of RAM and 2TB of NVMe SSD storage) and, more importantly, is almost completely silent and consumes almost no electricity (~12W in idle, about 2x Raspberry Pi 4).&lt;/p&gt;&#xA;&lt;p&gt;So I decided to start moving my self-hosted applications to this machine, which will become my home server.&#xA;I will begin using my Kimsufi dedicated server only as an email server and as my only system open to the public Internet, as it has a static public IP address and OVH will know how to deal with DDOS attacks and such better than me.&#xA;Even though the Kimsufi machine is at an unbeatable price, around 7€ per month for 2TB of storage, its processor is incredibly slow and under-powered, while also having its networking capped at 100MBps.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve recently bought a used USFF computer at a good price, inspired by the <a href="https://www.servethehome.com/introducing-project-tinyminimicro-home-lab-revolution/">TinyMiniMicro project</a>.
It&rsquo;s super fast, has plenty of storage and memory (32GB of RAM and 2TB of NVMe SSD storage) and, more importantly, is almost completely silent and consumes almost no electricity (~12W in idle, about 2x Raspberry Pi 4).</p>
<p>So I decided to start moving my self-hosted applications to this machine, which will become my home server.
I will begin using my Kimsufi dedicated server only as an email server and as my only system open to the public Internet, as it has a static public IP address and OVH will know how to deal with DDOS attacks and such better than me.
Even though the Kimsufi machine is at an unbeatable price, around 7€ per month for 2TB of storage, its processor is incredibly slow and under-powered, while also having its networking capped at 100MBps.</p>
<p>This new home server will be faster while also being more in line with the philosophy of self-hosting, to be the owner of my hardware.
Not only that, it will also have better networking, not only speed-wise but also because it&rsquo;ll be geographically closer to me.</p>
<h2 id="headscale">Headscale</h2>
<p>But I don&rsquo;t want to have applications running at home being exposed to the public internet, where I would use DynamicDNS and port-forwarding on my router.
The Internet can be very hostile and chaotic, so I&rsquo;ll want my services to only be accessible to people I know and trust.
Well, this is the pitch for using a mesh-VPN, where we build a private network where devices can communicate with each other wherever they are, but are inaccessible from the rest of the Internet.</p>
<p>I have already used Nebula in the past as a mesh-VPN solution.
It&rsquo;s a self-hosted solution, is well integrated with NixOS and has an Android client.
However, after the initial setup, I felt too much friction to add new devices to the network.
I would need to generate keys for each device and then transfer them to my laptop, where I had my CA, sign the keys and then transfer them back.</p>
<p>All over the Web, there has been much talk about this new product called Tailscale.
It was also designed with this usecase in mind and many have described as a new <a href="https://en.wikipedia.org/wiki/LogMeIn_Hamachi">Hamachi</a>, which I have used in the past for having local Minecraft servers for playing with friends.</p>
<p>Although many of Tailscale&rsquo;s components are open-source, especially its clients, the server is closed-source.
It&rsquo;s more or less understandable that it works this way. They provide Tailscale as a SaaS product, especially directed at the enterprise sector, and this allows them to keep developing and innovating.
If they made it all open-source, they would risk ending up in a Docker situation, where they would struggle to monetize their oferring.</p>
<p>However, I was looking for free and open-source solution, hosted by myself.
I then found out that there&rsquo;s an open-source implementation of the Tailscale server, called Headscale (perfect naming).
Sometimes, it evens gets contributions from Tailscale employees.
<a href="https://github.com/kradalby">One of its most important maintainers</a> currently works at Tailscale.</p>
<p>Excellent, Headscale is also well integrated with NixOS and I could basically have all the advantages of using Tailscale such as MagicDNS, Taildrop, Exit Nodes, etc. while only using open-source components and a server managed by myself.</p>
<h2 id="installing-headscale-on-nixos">Installing Headscale on NixOS</h2>
<p>I couldn&rsquo;t find any post showing me how to setup a Headscale server on NixOS.
I only managed to find <a href="https://git.sr.ht/~misterio/nix-config/tree/main/item/hosts/electra/services/headscale.nix">another person&rsquo;s configuration</a>.
Turns out, it wasn&rsquo;t that hard.
All that was needed was to activate the Headscale service as set the relevant options:</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span><span style="color:#66d9ef">let</span> domain <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;headscale.example.com&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">in</span> {
</span></span><span style="display:flex;"><span>  services <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    headscale <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>      address <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;0.0.0.0&#34;</span>;
</span></span><span style="display:flex;"><span>      port <span style="color:#f92672">=</span> <span style="color:#ae81ff">8080</span>;
</span></span><span style="display:flex;"><span>      server_url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;https://</span><span style="color:#e6db74">${</span>domain<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>;
</span></span><span style="display:flex;"><span>      dns <span style="color:#f92672">=</span> { baseDomain <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;example.com&#34;</span>; };
</span></span><span style="display:flex;"><span>      settings <span style="color:#f92672">=</span> { logtail<span style="color:#f92672">.</span>enabled <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>; };
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    nginx<span style="color:#f92672">.</span>virtualHosts<span style="color:#f92672">.</span><span style="color:#e6db74">${</span>domain<span style="color:#e6db74">}</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      forceSSL <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>      enableACME <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>      locations<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;/&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        proxyPass <span style="color:#f92672">=</span>
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;http://localhost:</span><span style="color:#e6db74">${</span>toString config<span style="color:#f92672">.</span>services<span style="color:#f92672">.</span>headscale<span style="color:#f92672">.</span>port<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>;
</span></span><span style="display:flex;"><span>        proxyWebsockets <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>      };
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  environment<span style="color:#f92672">.</span>systemPackages <span style="color:#f92672">=</span> [ config<span style="color:#f92672">.</span>services<span style="color:#f92672">.</span>headscale<span style="color:#f92672">.</span>package ];</span></span></code></pre></div><p><em>2024/04/27 Update</em>: A reader pointed out that the option <code>serverUrl</code> is now <code>server_url</code>, and that the error wasn&rsquo;t too obvious. It&rsquo;s now updated above.</p>
<p>And it just worked.
I should note that the option <code>proxyWebsockets</code> is need according to the <a href="https://github.com/juanfont/headscale/blob/main/docs/reverse-proxy.md#websockets">docs</a>.</p>
<p>All that&rsquo;s left is to create our first namespace, to which we&rsquo;ll our nodes later on:</p>





<pre tabindex="0"><code class="language-nil" data-lang="nil">headscale namespaces create &lt;namespace_name&gt;</code></pre><h2 id="using-the-tailscale-client-and-daemon-on-nixos">Using the Tailscale client and daemon on NixOS</h2>
<p>Using the Tailscale client on NixOS is also really easy.
It&rsquo;s just a matter of activating the following configuration:</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>services<span style="color:#f92672">.</span>tailscale<span style="color:#f92672">.</span>enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>networking<span style="color:#f92672">.</span>firewall <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  checkReversePath <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;loose&#34;</span>;
</span></span><span style="display:flex;"><span>  trustedInterfaces <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;tailscale0&#34;</span> ];
</span></span><span style="display:flex;"><span>  allowedUDPPorts <span style="color:#f92672">=</span> [ config<span style="color:#f92672">.</span>services<span style="color:#f92672">.</span>tailscale<span style="color:#f92672">.</span>port ];
</span></span><span style="display:flex;"><span>};</span></span></code></pre></div><p>And running the command to add the node to the network:</p>





<pre tabindex="0"><code class="language-nil" data-lang="nil">tailscale up --login-server &lt;headscale_url&gt;</code></pre><p>This command will output a link with instructions to add the node to the specified namespace on the server-side, which will look something like:</p>





<pre tabindex="0"><code class="language-nil" data-lang="nil">headscale --namespace &lt;namespace_name&gt; nodes register --key &lt;machine_key&gt;</code></pre><p>Where <code>&lt;machine_key&gt;</code> is the string at the end of the URL shown by the Tailscale client.</p>
<p>After that, I noticed that, by default, the node names end up being the hostname followed by a string of random characters.
In order to make the names more legible and memorable, I chose to change them to be the machines&rsquo; hostname only.
I have read that using the option <code>--hostname</code> when running the <code>tailscale up</code> command sets the hostname right away but I haven&rsquo;t tested it yet.</p>
<p>To rename a single node on headscale:</p>





<pre tabindex="0"><code class="language-nil" data-lang="nil">headscale nodes list
headscale node rename -i &lt;node_id&gt; &lt;new_name&gt;</code></pre><h2 id="magicdns">MagicDNS</h2>
<p>As I use systemd-resolved on my systems, Tailscale takes care of setting up all of the DNS-related configuration.
However, if I didn&rsquo;t use systemd-resolved, I&rsquo;d have to configure the DNS myself, as explained <a href="https://tailscale.com/kb/1063/install-nixos/#using-magicdns">here</a>.</p>
<p>Thanks to MagicDNS, after setting up the clients, we can connect to any node in the network, wherever we are in the world, by referring to them as &lt;name&gt;.&lt;namespace&gt;.&lt;baseDomain&gt;.
For example:</p>





<pre tabindex="0"><code class="language-nil" data-lang="nil">ping node1.headnet.example.com</code></pre><h2 id="using-the-tailscale-android-client-on-grapheneos">Using the Tailscale Android client on GrapheneOS</h2>
<p>Setting up Tailscale on Android is also supposed to be quite straightforward, especially because Tailscale added a way to use external servers, allowing Headscale users to use the same client (<a href="https://github.com/juanfont/headscale/blob/main/docs/android-client.md#connecting-an-android-client">link</a>).</p>
<p>However, every time I tapped the &ldquo;Save and Restart&rdquo; button, the app would crash immediately.
When I restarted it, the sign-in button kept redirecting me to Tailscale&rsquo;s login page, which led me to believe it didn&rsquo;t actually configure the app to use my Headscale server.</p>
<p>Another user seemed to have just the same problem (<a href="https://forum.tailscale.com/t/tailscale-with-headscale-with-graphene-os/3312">link</a>), but there were no answers.</p>
<p>My remaining option was to modify the client&rsquo;s source code in order to use my Headscale server by default.
It was a quite simple modification (<a href="https://github.com/carjorvaz/tailscale-android">link</a>) but it requires me to sync my own fork and build the app every time there&rsquo;s an update.</p>
<p>To make matters worse, Tailscale&rsquo;s client is also incompatible with the Private DNS setting on Android, which I used in order to have DNS-level ad-blocking, using <a href="https://adguard-dns.io/en/public-dns.html">Adguard</a>.
I&rsquo;ll have to investigate further if it makes sense to set up a DNS server on my home server and use it on my Android device so I can have ad-blocking again (<a href="https://adguard-dns.io/en/public-dns.html">according to Daniel Micay, this is the only reasonable way to have system-wide ad-blocking on an Android device</a>).</p>
<h2 id="closing-remarks">Closing remarks</h2>
<p>The overall feeling of this experiment was that setting all of this up was very straightforward, with the exception of the Android client on my phone. Admittedly, it&rsquo;s also a more esoteric setup than usual (GrapheneOS).</p>
<p>In contrast to Nebula, adding new devices to the network is a matter of running a command both on the client and on the server (or tapping a button if on an Android device) and not of sending files around.</p>
<p>Also, MagicDNS and Taildrop just work and are great additions.</p>
<p>All that&rsquo;s left is to set up services on my home-server, in order for this mesh-VPN be of any use.</p>
<p>It would be interesing to keep having Let&rsquo;s Encrypt SSL certificates on my services, running inside the private network.
However, this will be harder than usual as I won&rsquo;t be able to complete the HTTP-01 challenge.
I&rsquo;ll need to setup the DNS-01 challenge which will allow me to use wildcard certificates. But that&rsquo;s a matter for a future post.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
