diff --git a/app/routes/_data.dns._index/dialogs/dns.tsx b/app/routes/_data.dns._index/dialogs/dns.tsx index 28949dc..f3668a3 100644 --- a/app/routes/_data.dns._index/dialogs/dns.tsx +++ b/app/routes/_data.dns._index/dialogs/dns.tsx @@ -47,7 +47,7 @@ export default function AddDNS({ records }: Props) { setIp('') submit({ - 'dns_config.extra_records': [ + 'dns.extra_records': [ ...records, { name, diff --git a/app/routes/_data.dns._index/dialogs/nameserver.tsx b/app/routes/_data.dns._index/dialogs/nameserver.tsx index c22c5b5..d7f6309 100644 --- a/app/routes/_data.dns._index/dialogs/nameserver.tsx +++ b/app/routes/_data.dns._index/dialogs/nameserver.tsx @@ -55,7 +55,7 @@ export default function AddNameserver({ nameservers }: Props) { } submit({ - 'dns_config.restricted_nameservers': splitNs, + 'dns.nameservers.split': splitNs, }, { method: 'PATCH', encType: 'application/json', @@ -65,7 +65,7 @@ export default function AddNameserver({ nameservers }: Props) { globalNs.push(ns) submit({ - 'dns_config.nameservers': globalNs, + 'dns.nameservers.global': globalNs, }, { method: 'PATCH', encType: 'application/json', diff --git a/app/routes/_data.dns._index/dns.tsx b/app/routes/_data.dns._index/dns.tsx index d016829..0c6b0cf 100644 --- a/app/routes/_data.dns._index/dns.tsx +++ b/app/routes/_data.dns._index/dns.tsx @@ -63,7 +63,7 @@ export default function DNS({ records, isDisabled }: Props) { isDisabled={isDisabled} onPress={() => { submit({ - 'dns_config.extra_records': records + 'dns.extra_records': records .filter((_, i) => i !== index), }, { method: 'PATCH', diff --git a/app/routes/_data.dns._index/domains.tsx b/app/routes/_data.dns._index/domains.tsx index 7bdbfbe..cb48cf3 100644 --- a/app/routes/_data.dns._index/domains.tsx +++ b/app/routes/_data.dns._index/domains.tsx @@ -134,7 +134,7 @@ export default function Domains({ baseDomain, searchDomains, disabled }: Propert onPress={() => { fetcher.submit({ // eslint-disable-next-line @typescript-eslint/naming-convention - 'dns_config.domains': [...localDomains, newDomain] + 'dns.search_domains': [...localDomains, newDomain] }, { method: 'PATCH', encType: 'application/json' @@ -212,8 +212,7 @@ function Domain({ domain, id, localDomains, isDrag, disabled, fetcher }: DomainP isDisabled={disabled} onPress={() => { fetcher.submit({ - // eslint-disable-next-line @typescript-eslint/naming-convention - 'dns_config.domains': localDomains.filter((_, index) => index !== id - 1) + 'dns.search_domains': localDomains.filter((_, index) => index !== id - 1) }, { method: 'PATCH', encType: 'application/json' diff --git a/app/routes/_data.dns._index/magic.tsx b/app/routes/_data.dns._index/magic.tsx index d538642..acfa31e 100644 --- a/app/routes/_data.dns._index/magic.tsx +++ b/app/routes/_data.dns._index/magic.tsx @@ -42,7 +42,7 @@ export default function Modal({ isEnabled, disabled }: Properties) { onPress={() => { fetcher.submit({ // eslint-disable-next-line @typescript-eslint/naming-convention - 'dns_config.magic_dns': !isEnabled + 'dns.magic_dns': !isEnabled }, { method: 'PATCH', encType: 'application/json' diff --git a/app/routes/_data.dns._index/nameservers.tsx b/app/routes/_data.dns._index/nameservers.tsx index ce0036f..45ce719 100644 --- a/app/routes/_data.dns._index/nameservers.tsx +++ b/app/routes/_data.dns._index/nameservers.tsx @@ -11,11 +11,10 @@ import AddNameserver from './dialogs/nameserver' interface Props { nameservers: Record - override: boolean isDisabled: boolean } -export default function Nameservers({ nameservers, override, isDisabled }: Props) { +export default function Nameservers({ nameservers, isDisabled }: Props) { return (

Nameservers

@@ -37,7 +36,6 @@ export default function Nameservers({ nameservers, override, isDisabled }: Props isGlobal={key === 'global'} isDisabled={isDisabled} nameservers={nameservers[key]} - override={override} name={key} /> ))} @@ -57,11 +55,9 @@ interface ListProps { isDisabled: boolean nameservers: string[] name: string - override: boolean } -function NameserverList({ isGlobal, isDisabled, nameservers, name, override }: ListProps) { - const [localOverride, setLocalOverride] = useState(override) +function NameserverList({ isGlobal, isDisabled, nameservers, name }: ListProps) { const submit = useSubmit() return ( @@ -70,30 +66,6 @@ function NameserverList({ isGlobal, isDisabled, nameservers, name, override }: L

{isGlobal ? 'Global Nameservers' : name}

- {isGlobal - ? ( -
- - Override local DNS - - { - submit({ - 'dns_config.override_local_dns': !localOverride, - }, { - method: 'PATCH', - encType: 'application/json', - }) - - setLocalOverride(!localOverride) - }} - /> -
- ) - : undefined}
{nameservers.map((ns, index) => ( @@ -111,14 +83,14 @@ function NameserverList({ isGlobal, isDisabled, nameservers, name, override }: L onPress={() => { if (isGlobal) { submit({ - 'dns_config.nameservers': nameservers + 'dns.nameservers.global': nameservers .filter((_, i) => i !== index), }, { method: 'PATCH', encType: 'application/json', }) } else { - const key = `dns_config.restricted_nameservers."${name}"` + const key = `dns.nameservers.split."${name}"` submit({ [key]: nameservers .filter((_, i) => i !== index), diff --git a/app/routes/_data.dns._index/rename.tsx b/app/routes/_data.dns._index/rename.tsx index afe00b8..b3be339 100644 --- a/app/routes/_data.dns._index/rename.tsx +++ b/app/routes/_data.dns._index/rename.tsx @@ -80,7 +80,7 @@ export default function Modal({ name, disabled }: Properties) { variant='confirm' onPress={() => { fetcher.submit({ - 'dns_config.base_domain': newName + 'dns.base_domain': newName }, { method: 'PATCH', encType: 'application/json' diff --git a/app/routes/_data.dns._index/route.tsx b/app/routes/_data.dns._index/route.tsx index 2feed7d..c09be2b 100644 --- a/app/routes/_data.dns._index/route.tsx +++ b/app/routes/_data.dns._index/route.tsx @@ -24,13 +24,12 @@ export async function loader() { const config = await loadConfig() const dns = { prefixes: config.prefixes, - magicDns: config.dns_config.magic_dns, - baseDomain: config.dns_config.base_domain, - overrideLocal: config.dns_config.override_local_dns, - nameservers: config.dns_config.nameservers, - splitDns: config.dns_config.restricted_nameservers, - searchDomains: config.dns_config.domains, - extraRecords: config.dns_config.extra_records, + magicDns: config.dns.magic_dns, + baseDomain: config.dns.base_domain, + nameservers: config.dns.nameservers.global, + splitDns: config.dns.nameservers.split, + searchDomains: config.dns.search_domains, + extraRecords: config.dns.extra_records, } return { @@ -87,7 +86,6 @@ export default function Page() { diff --git a/app/routes/_data.machines.$id.tsx b/app/routes/_data.machines.$id.tsx index 866f714..de433a1 100644 --- a/app/routes/_data.machines.$id.tsx +++ b/app/routes/_data.machines.$id.tsx @@ -27,8 +27,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) { if (context.config.read) { const config = await loadConfig() - if (config.dns_config.magic_dns) { - magic = config.dns_config.base_domain + if (config.dns.magic_dns) { + magic = config.dns.base_domain } } diff --git a/app/routes/_data.machines._index/route.tsx b/app/routes/_data.machines._index/route.tsx index 08b2568..4eb3aad 100644 --- a/app/routes/_data.machines._index/route.tsx +++ b/app/routes/_data.machines._index/route.tsx @@ -29,8 +29,8 @@ export async function loader({ request }: LoaderFunctionArgs) { if (context.config.read) { const config = await loadConfig() - if (config.dns_config.magic_dns) { - magic = config.dns_config.base_domain + if (config.dns.magic_dns) { + magic = config.dns.base_domain } } diff --git a/app/routes/_data.users._index/route.tsx b/app/routes/_data.users._index/route.tsx index 8163e9e..b6d79c2 100644 --- a/app/routes/_data.users._index/route.tsx +++ b/app/routes/_data.users._index/route.tsx @@ -41,8 +41,8 @@ export async function loader({ request }: LoaderFunctionArgs) { if (context.config.read) { const config = await loadConfig() - if (config.dns_config.magic_dns) { - magic = config.dns_config.base_domain + if (config.dns.magic_dns) { + magic = config.dns.base_domain } } diff --git a/app/utils/config/headscale.ts b/app/utils/config/headscale.ts index e45252c..b50992d 100644 --- a/app/utils/config/headscale.ts +++ b/app/utils/config/headscale.ts @@ -89,18 +89,20 @@ const HeadscaleConfig = z.object({ v6: z.string(), }), - dns_config: z.object({ - override_local_dns: goBool.default(false), - nameservers: z.array(z.string()).default([]), - restricted_nameservers: z.record(z.array(z.string())).default({}), - domains: z.array(z.string()).default([]), + dns: z.object({ + magic_dns: goBool.default(true), + base_domain: z.string().default('headscale.net'), + nameservers: z.object({ + global: z.array(z.string()).default([]), + split: z.record(z.array(z.string())).default({}), + }).default({ global: [], split: {} }), + search_domains: z.array(z.string()).default([]), extra_records: z.array(z.object({ name: z.string(), type: z.literal('A'), value: z.string(), })).default([]), - magic_dns: goBool.default(false), - base_domain: z.string().default('headscale.net'), + use_username_in_magic_dns: goBool.default(false), }), oidc: z.object({ @@ -225,11 +227,12 @@ export async function loadConfig(path?: string) { v6: '', }, - dns_config: loaded.dns_config ?? { - override_local_dns: false, - nameservers: [], - restricted_nameservers: {}, - domains: [], + dns: loaded.dns ?? { + nameservers: { + global: [], + split: {}, + }, + search_domains: [], extra_records: [], magic_dns: false, base_domain: 'headscale.net', diff --git a/test/config.yaml b/test/config.yaml index d5e10f0..d66df0c 100644 --- a/test/config.yaml +++ b/test/config.yaml @@ -198,7 +198,7 @@ policy: # - https://tailscale.com/kb/1081/magicdns/ # - https://tailscale.com/blog/2021-09-private-dns-with-magicdns/ # -dns_config: +dns_config2: # Whether to prefer using Headscale provided DNS or use local. override_local_dns: true @@ -259,6 +259,64 @@ dns_config: type: A value: 1.1.1.1 +dns: + # Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/). + # Only works if there is at least a nameserver defined. + magic_dns: true + + # Defines the base domain to create the hostnames for MagicDNS. + # This domain _must_ be different from the server_url domain. + # `base_domain` must be a FQDN, without the trailing dot. + # The FQDN of the hosts will be + # `hostname.base_domain` (e.g., _myhost.example.com_). + base_domain: example.com + + # List of DNS servers to expose to clients. + nameservers: + global: + - 1.1.1.1 + - 1.0.0.1 + - 2606:4700:4700::1111 + - 2606:4700:4700::1001 + + # NextDNS (see https://tailscale.com/kb/1218/nextdns/). + # "abc123" is example NextDNS ID, replace with yours. + # - https://dns.nextdns.io/abc123 + + # Split DNS (see https://tailscale.com/kb/1054/dns/), + # a map of domains and which DNS server to use for each. + split: + {} + # foo.bar.com: + # - 1.1.1.1 + # darp.headscale.net: + # - 1.1.1.1 + # - 8.8.8.8 + + # Set custom DNS search domains. With MagicDNS enabled, + # your tailnet base_domain is always the first search domain. + search_domains: [] + + # Extra DNS records + # so far only A-records are supported (on the tailscale side) + # See https://github.com/juanfont/headscale/blob/main/docs/dns-records.md#Limitations + extra_records: [] + # - name: "grafana.myvpn.example.com" + # type: "A" + # value: "100.64.0.3" + # + # # you can also put it in one line + # - { name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.3" } + + # DEPRECATED + # Use the username as part of the DNS name for nodes, with this option enabled: + # node1.username.example.com + # while when this is disabled: + # node1.example.com + # This is a legacy option as Headscale has have this wrongly implemented + # while in upstream Tailscale, the username is not included. + use_username_in_magic_dns: false + # Unix socket used for the CLI to connect without authentication # Note: for production you will want to set this to something like: unix_socket: /var/run/headscale/headscale.sock