184 lines
5.7 KiB
Nix
184 lines
5.7 KiB
Nix
{ ... }:
|
|
{
|
|
_class = "clan.service";
|
|
manifest.name = "headscale";
|
|
manifest.description = "An open source, self-hosted implementation of the Tailscale control server";
|
|
manifest.readme = "An open source, self-hosted implementation of the Tailscale control server";
|
|
manifest.categories = [ "System" ];
|
|
|
|
roles.server = {
|
|
description = "A server role";
|
|
|
|
interface =
|
|
{ lib, config, ... }:
|
|
{
|
|
options = {
|
|
public_url = lib.mkOption {
|
|
type = with lib.types; nullOr str;
|
|
default = config.services.headscale.settings.server_url;
|
|
description = "Public URL for accessing the instance";
|
|
};
|
|
|
|
base_domain = lib.mkOption {
|
|
type = with lib.types; str;
|
|
default = "";
|
|
description = "Defines the base domain to create the hostnames for MagicDNS in Headscale. `base_domain` must be a FQDN, without the trailing dot. The FQDN of the hosts will be `hostname.base_domain (e.g. myhost.tailnet.example.com)";
|
|
};
|
|
|
|
advertise_routes = lib.mkOption {
|
|
type = with lib.types; listOf str;
|
|
default = [ ];
|
|
description = "Expose physical subnet routes to your entire Tailscale network.";
|
|
example = [ "192.168.1.0/24" ];
|
|
};
|
|
|
|
nameservers = lib.mkOption {
|
|
type = with lib.types; listOf str;
|
|
default = [
|
|
"1.1.1.1"
|
|
"8.8.8.8"
|
|
];
|
|
description = "List of nameservers to pass to Tailscale clients";
|
|
example = [ "10.0.10.1" ];
|
|
};
|
|
};
|
|
};
|
|
|
|
perInstance =
|
|
{ settings, ... }:
|
|
{
|
|
nixosModule =
|
|
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
...
|
|
}:
|
|
let
|
|
preAuthKeyFile = "/var/lib/headscale/preauth.key";
|
|
|
|
routes = lib.concatStringsSep "," settings.advertise_routes;
|
|
in
|
|
{
|
|
|
|
systemd.services.headscale-auto-enroll =
|
|
let
|
|
serverUser = "hcserver";
|
|
in
|
|
{
|
|
description = "Enroll this machine into headscale automatically";
|
|
after = [
|
|
"headscale.service"
|
|
"tailscaled.service"
|
|
];
|
|
requires = [
|
|
"headscale.service"
|
|
"tailscaled.service"
|
|
];
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
User = "root";
|
|
};
|
|
|
|
path = [ pkgs.jq ];
|
|
|
|
script = ''
|
|
set -euo pipefail
|
|
|
|
if ${pkgs.tailscale}/bin/tailscale status &>/dev/null; then
|
|
echo "Already enrolled, skipping."
|
|
exit 0
|
|
fi
|
|
|
|
for i in $(seq 1 30); do
|
|
${pkgs.headscale}/bin/headscale users list &>/dev/null && break
|
|
sleep 1
|
|
done
|
|
|
|
${pkgs.headscale}/bin/headscale users create ${serverUser} 2>/dev/null || true
|
|
|
|
USER_ID=$(${pkgs.headscale}/bin/headscale users list --name ${serverUser} -o json | jq '.[0].id')
|
|
|
|
KEY=$(${pkgs.headscale}/bin/headscale preauthkeys create \
|
|
--user $USER_ID \
|
|
--reusable \
|
|
--expiration 30m \
|
|
--output json | ${pkgs.jq}/bin/jq -r '.key')
|
|
|
|
echo "$KEY" > ${preAuthKeyFile}
|
|
chmod 600 ${preAuthKeyFile}
|
|
|
|
${pkgs.tailscale}/bin/tailscale up \
|
|
--login-server=https://${settings.public_url} \
|
|
--authkey="$KEY" \
|
|
--accept-routes \
|
|
--advertise-routes=${routes}
|
|
'';
|
|
};
|
|
|
|
systemd.services.headscale-approve-routes = {
|
|
description = "Auto approve routes";
|
|
after = [
|
|
"headscale.service"
|
|
"tailscaled.service"
|
|
"headscale-auto-enroll.service"
|
|
];
|
|
requires = [
|
|
"headscale.service"
|
|
"tailscaled.service"
|
|
"headscale-auto-enroll.service"
|
|
];
|
|
|
|
path = [ pkgs.jq ];
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
User = "root";
|
|
};
|
|
|
|
script = ''
|
|
set -euo pipefail
|
|
NODE_ID=$(${pkgs.tailscale}/bin/tailscale status --json | jq '.Self.ID' | tr -d '"')
|
|
${pkgs.headscale}/bin/headscale node approve-routes --identifier $NODE_ID --routes ${routes}
|
|
'';
|
|
|
|
};
|
|
|
|
systemd.services.tailscaled-autoconnect.after = [
|
|
"tailscaled.service"
|
|
"headscale-auto-enroll.service"
|
|
];
|
|
|
|
services.tailscale = {
|
|
enable = true;
|
|
useRoutingFeatures = "server";
|
|
openFirewall = true;
|
|
};
|
|
|
|
networking.firewall.allowedTCPPorts = [
|
|
config.services.headscale.port
|
|
];
|
|
|
|
services.headscale = {
|
|
enable = true;
|
|
address = "0.0.0.0";
|
|
settings.server_url = "https://${settings.public_url}";
|
|
|
|
settings.dns = {
|
|
base_domain = settings.base_domain;
|
|
override_local_dns = true;
|
|
nameservers.global = settings.nameservers;
|
|
magic_dns = false;
|
|
};
|
|
};
|
|
|
|
};
|
|
};
|
|
};
|
|
}
|