desktop/tailscale.nix
2026-07-19 11:34:28 -03:00

175 lines
5.9 KiB
Nix

{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.tailscale-netns;
instanceOptions = {
options.extraDaemonFlags = mkOption {
type = types.listOf types.str;
default = [ ];
description = "Extra flags passed to `tailscaled`.";
};
};
mkNetnsSetup =
index: name: instanceCfg:
let
ns = "ts-${name}ns";
hostAddr = "10.201.${toString index}.1";
hostIP = "${hostAddr}/24";
nsIP = "10.201.${toString index}.2/24";
vethHost = "veth-ts-${name}";
vethNS = "veth-ts-${name}-ns";
fallbackLines = concatMapStrings (n: " echo nameserver ${n}\n") config.networking.nameservers;
in
{
"netns@ts-${name}" = {
description = "Network namespace for Tailscale instance ${name}";
before = [ "network.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writers.writeBash "ts-${name}-netns-up" ''
set -euo pipefail
${pkgs.coreutils}/bin/mkdir -p /etc/netns/${ns}
{
dns_found=0
if ${pkgs.systemd}/bin/resolvectl dns 2>/dev/null; then
saved_ifs="$IFS"
IFS=$'\n'
for line in $(${pkgs.systemd}/bin/resolvectl dns 2>/dev/null); do
case "$line" in
*:*)
server=$(echo "$line" | ${pkgs.gnused}/bin/sed 's/^[^:]*: //')
case "$server" in
127.*|::1|"") ;;
*) echo "nameserver $server"; dns_found=1 ;;
esac
;;
esac
done
IFS="$saved_ifs"
fi
if [ "$dns_found" -eq 0 ]; then
while IFS= read -r rline; do
case "$rline" in
nameserver*)
ip=$(echo "$rline" | ${pkgs.gnused}/bin/sed 's/^nameserver //')
case "$ip" in
127.*|::1) ;;
*) echo "$rline"; dns_found=1 ;;
esac
;;
esac
done < /etc/resolv.conf
fi
${optionalString (fallbackLines != "") ''
if [ "$dns_found" -eq 0 ]; then
${fallbackLines} fi
''}
} > /etc/netns/${ns}/resolv.conf
${pkgs.iproute2}/bin/ip netns add ${ns}
${pkgs.iproute2}/bin/ip link add ${vethHost} type veth peer name ${vethNS}
${pkgs.iproute2}/bin/ip link set ${vethNS} netns ${ns}
${pkgs.iproute2}/bin/ip addr add ${hostIP} dev ${vethHost}
${pkgs.iproute2}/bin/ip link set ${vethHost} up
${pkgs.iproute2}/bin/ip -n ${ns} addr add ${nsIP} dev ${vethNS}
${pkgs.iproute2}/bin/ip -n ${ns} link set ${vethNS} up
${pkgs.iproute2}/bin/ip -n ${ns} link set lo up
${pkgs.iproute2}/bin/ip -n ${ns} route add default via ${hostAddr}
${pkgs.iptables}/bin/iptables -t nat -C POSTROUTING -s ${nsIP} -j MASQUERADE 2>/dev/null \
|| ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s ${nsIP} -j MASQUERADE
'';
ExecStop = pkgs.writers.writeBash "ts-${name}-netns-down" ''
set -euo pipefail
${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s ${nsIP} -j MASQUERADE 2>/dev/null || true
${pkgs.iproute2}/bin/ip link del ${vethHost} 2>/dev/null || true
${pkgs.iproute2}/bin/ip netns del ${ns} 2>/dev/null || true
'';
};
};
"tailscaled-${name}" = {
description = "Tailscale daemon for instance ${name}";
bindsTo = [ "netns@ts-${name}.service" ];
after = [ "netns@ts-${name}.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = pkgs.writers.writeBash "tailscaled-${name}-start" ''
exec ${pkgs.iproute2}/bin/ip netns exec ${ns} \
${pkgs.tailscale}/bin/tailscaled \
--statedir=/var/lib/tailscale-${name} \
--socket=/run/tailscale-${name}/tailscaled.sock \
--tun=ts-${name} \
--port=41641 \
${escapeShellArgs instanceCfg.extraDaemonFlags}
'';
ExecStopPost = pkgs.writers.writeBash "tailscaled-${name}-stop" ''
${pkgs.iproute2}/bin/ip netns exec ${ns} \
${pkgs.tailscale}/bin/tailscaled --cleanup \
--statedir=/var/lib/tailscale-${name} \
--socket=/run/tailscale-${name}/tailscaled.sock \
2>/dev/null || true
'';
Restart = "on-failure";
RuntimeDirectory = "tailscale-${name}";
RuntimeDirectoryMode = "0755";
StateDirectory = "tailscale-${name}";
StateDirectoryMode = "0700";
CacheDirectory = "tailscale-${name}";
CacheDirectoryMode = "0750";
AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_RAW";
LimitNOFILE = "infinity";
};
};
};
in
{
options.services.tailscale-netns = {
enable = mkEnableOption "Tailscale instances in isolated network namespaces";
instances = mkOption {
type = types.attrsOf (types.submodule instanceOptions);
default = { };
description = "Tailscale instances configured in separate network namespaces.";
example = literalExpression ''
{
work = {};
}
'';
};
};
config = mkIf cfg.enable {
systemd.services = listToAttrs (
flatten (
imap1 (
index: elem:
let
name = elem.name;
units = mkNetnsSetup index name elem.value;
in
[
{ name = "netns@ts-${name}"; value = units."netns@ts-${name}"; }
{ name = "tailscaled-${name}"; value = units."tailscaled-${name}"; }
]
) (attrsToList cfg.instances)
)
);
};
}