mirror of
https://github.com/tale/headplane.git
synced 2026-08-28 16:07:07 +00:00
feat(TALE-30): add support for new DNS configs
This is a breaking change to support 0.23-beta2
This commit is contained in:
@@ -47,7 +47,7 @@ export default function AddDNS({ records }: Props) {
|
|||||||
setIp('')
|
setIp('')
|
||||||
|
|
||||||
submit({
|
submit({
|
||||||
'dns_config.extra_records': [
|
'dns.extra_records': [
|
||||||
...records,
|
...records,
|
||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default function AddNameserver({ nameservers }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submit({
|
submit({
|
||||||
'dns_config.restricted_nameservers': splitNs,
|
'dns.nameservers.split': splitNs,
|
||||||
}, {
|
}, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
encType: 'application/json',
|
encType: 'application/json',
|
||||||
@@ -65,7 +65,7 @@ export default function AddNameserver({ nameservers }: Props) {
|
|||||||
globalNs.push(ns)
|
globalNs.push(ns)
|
||||||
|
|
||||||
submit({
|
submit({
|
||||||
'dns_config.nameservers': globalNs,
|
'dns.nameservers.global': globalNs,
|
||||||
}, {
|
}, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
encType: 'application/json',
|
encType: 'application/json',
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function DNS({ records, isDisabled }: Props) {
|
|||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
submit({
|
submit({
|
||||||
'dns_config.extra_records': records
|
'dns.extra_records': records
|
||||||
.filter((_, i) => i !== index),
|
.filter((_, i) => i !== index),
|
||||||
}, {
|
}, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export default function Domains({ baseDomain, searchDomains, disabled }: Propert
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
fetcher.submit({
|
fetcher.submit({
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
'dns_config.domains': [...localDomains, newDomain]
|
'dns.search_domains': [...localDomains, newDomain]
|
||||||
}, {
|
}, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
encType: 'application/json'
|
encType: 'application/json'
|
||||||
@@ -212,8 +212,7 @@ function Domain({ domain, id, localDomains, isDrag, disabled, fetcher }: DomainP
|
|||||||
isDisabled={disabled}
|
isDisabled={disabled}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
fetcher.submit({
|
fetcher.submit({
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
'dns.search_domains': localDomains.filter((_, index) => index !== id - 1)
|
||||||
'dns_config.domains': localDomains.filter((_, index) => index !== id - 1)
|
|
||||||
}, {
|
}, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
encType: 'application/json'
|
encType: 'application/json'
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default function Modal({ isEnabled, disabled }: Properties) {
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
fetcher.submit({
|
fetcher.submit({
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
'dns_config.magic_dns': !isEnabled
|
'dns.magic_dns': !isEnabled
|
||||||
}, {
|
}, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
encType: 'application/json'
|
encType: 'application/json'
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ import AddNameserver from './dialogs/nameserver'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
nameservers: Record<string, string[]>
|
nameservers: Record<string, string[]>
|
||||||
override: boolean
|
|
||||||
isDisabled: boolean
|
isDisabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Nameservers({ nameservers, override, isDisabled }: Props) {
|
export default function Nameservers({ nameservers, isDisabled }: Props) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-2/3">
|
<div className="flex flex-col w-2/3">
|
||||||
<h1 className="text-2xl font-medium mb-4">Nameservers</h1>
|
<h1 className="text-2xl font-medium mb-4">Nameservers</h1>
|
||||||
@@ -37,7 +36,6 @@ export default function Nameservers({ nameservers, override, isDisabled }: Props
|
|||||||
isGlobal={key === 'global'}
|
isGlobal={key === 'global'}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
nameservers={nameservers[key]}
|
nameservers={nameservers[key]}
|
||||||
override={override}
|
|
||||||
name={key}
|
name={key}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -57,11 +55,9 @@ interface ListProps {
|
|||||||
isDisabled: boolean
|
isDisabled: boolean
|
||||||
nameservers: string[]
|
nameservers: string[]
|
||||||
name: string
|
name: string
|
||||||
override: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function NameserverList({ isGlobal, isDisabled, nameservers, name, override }: ListProps) {
|
function NameserverList({ isGlobal, isDisabled, nameservers, name }: ListProps) {
|
||||||
const [localOverride, setLocalOverride] = useState(override)
|
|
||||||
const submit = useSubmit()
|
const submit = useSubmit()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -70,30 +66,6 @@ function NameserverList({ isGlobal, isDisabled, nameservers, name, override }: L
|
|||||||
<h2 className="text-md font-medium opacity-80">
|
<h2 className="text-md font-medium opacity-80">
|
||||||
{isGlobal ? 'Global Nameservers' : name}
|
{isGlobal ? 'Global Nameservers' : name}
|
||||||
</h2>
|
</h2>
|
||||||
{isGlobal
|
|
||||||
? (
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<span className="text-sm opacity-50">
|
|
||||||
Override local DNS
|
|
||||||
</span>
|
|
||||||
<Switch
|
|
||||||
label="Override local DNS"
|
|
||||||
defaultSelected={localOverride}
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
onChange={() => {
|
|
||||||
submit({
|
|
||||||
'dns_config.override_local_dns': !localOverride,
|
|
||||||
}, {
|
|
||||||
method: 'PATCH',
|
|
||||||
encType: 'application/json',
|
|
||||||
})
|
|
||||||
|
|
||||||
setLocalOverride(!localOverride)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
: undefined}
|
|
||||||
</div>
|
</div>
|
||||||
<TableList>
|
<TableList>
|
||||||
{nameservers.map((ns, index) => (
|
{nameservers.map((ns, index) => (
|
||||||
@@ -111,14 +83,14 @@ function NameserverList({ isGlobal, isDisabled, nameservers, name, override }: L
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (isGlobal) {
|
if (isGlobal) {
|
||||||
submit({
|
submit({
|
||||||
'dns_config.nameservers': nameservers
|
'dns.nameservers.global': nameservers
|
||||||
.filter((_, i) => i !== index),
|
.filter((_, i) => i !== index),
|
||||||
}, {
|
}, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
encType: 'application/json',
|
encType: 'application/json',
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const key = `dns_config.restricted_nameservers."${name}"`
|
const key = `dns.nameservers.split."${name}"`
|
||||||
submit({
|
submit({
|
||||||
[key]: nameservers
|
[key]: nameservers
|
||||||
.filter((_, i) => i !== index),
|
.filter((_, i) => i !== index),
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default function Modal({ name, disabled }: Properties) {
|
|||||||
variant='confirm'
|
variant='confirm'
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
fetcher.submit({
|
fetcher.submit({
|
||||||
'dns_config.base_domain': newName
|
'dns.base_domain': newName
|
||||||
}, {
|
}, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
encType: 'application/json'
|
encType: 'application/json'
|
||||||
|
|||||||
@@ -24,13 +24,12 @@ export async function loader() {
|
|||||||
const config = await loadConfig()
|
const config = await loadConfig()
|
||||||
const dns = {
|
const dns = {
|
||||||
prefixes: config.prefixes,
|
prefixes: config.prefixes,
|
||||||
magicDns: config.dns_config.magic_dns,
|
magicDns: config.dns.magic_dns,
|
||||||
baseDomain: config.dns_config.base_domain,
|
baseDomain: config.dns.base_domain,
|
||||||
overrideLocal: config.dns_config.override_local_dns,
|
nameservers: config.dns.nameservers.global,
|
||||||
nameservers: config.dns_config.nameservers,
|
splitDns: config.dns.nameservers.split,
|
||||||
splitDns: config.dns_config.restricted_nameservers,
|
searchDomains: config.dns.search_domains,
|
||||||
searchDomains: config.dns_config.domains,
|
extraRecords: config.dns.extra_records,
|
||||||
extraRecords: config.dns_config.extra_records,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -87,7 +86,6 @@ export default function Page() {
|
|||||||
<RenameModal name={data.baseDomain} disabled={!data.config.write} />
|
<RenameModal name={data.baseDomain} disabled={!data.config.write} />
|
||||||
<Nameservers
|
<Nameservers
|
||||||
nameservers={allNs}
|
nameservers={allNs}
|
||||||
override={data.overrideLocal}
|
|
||||||
isDisabled={!data.config.write}
|
isDisabled={!data.config.write}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
|
|||||||
|
|
||||||
if (context.config.read) {
|
if (context.config.read) {
|
||||||
const config = await loadConfig()
|
const config = await loadConfig()
|
||||||
if (config.dns_config.magic_dns) {
|
if (config.dns.magic_dns) {
|
||||||
magic = config.dns_config.base_domain
|
magic = config.dns.base_domain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
|
|
||||||
if (context.config.read) {
|
if (context.config.read) {
|
||||||
const config = await loadConfig()
|
const config = await loadConfig()
|
||||||
if (config.dns_config.magic_dns) {
|
if (config.dns.magic_dns) {
|
||||||
magic = config.dns_config.base_domain
|
magic = config.dns.base_domain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
|
|
||||||
if (context.config.read) {
|
if (context.config.read) {
|
||||||
const config = await loadConfig()
|
const config = await loadConfig()
|
||||||
if (config.dns_config.magic_dns) {
|
if (config.dns.magic_dns) {
|
||||||
magic = config.dns_config.base_domain
|
magic = config.dns.base_domain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,18 +89,20 @@ const HeadscaleConfig = z.object({
|
|||||||
v6: z.string(),
|
v6: z.string(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
dns_config: z.object({
|
dns: z.object({
|
||||||
override_local_dns: goBool.default(false),
|
magic_dns: goBool.default(true),
|
||||||
nameservers: z.array(z.string()).default([]),
|
base_domain: z.string().default('headscale.net'),
|
||||||
restricted_nameservers: z.record(z.array(z.string())).default({}),
|
nameservers: z.object({
|
||||||
domains: z.array(z.string()).default([]),
|
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({
|
extra_records: z.array(z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
type: z.literal('A'),
|
type: z.literal('A'),
|
||||||
value: z.string(),
|
value: z.string(),
|
||||||
})).default([]),
|
})).default([]),
|
||||||
magic_dns: goBool.default(false),
|
use_username_in_magic_dns: goBool.default(false),
|
||||||
base_domain: z.string().default('headscale.net'),
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
oidc: z.object({
|
oidc: z.object({
|
||||||
@@ -225,11 +227,12 @@ export async function loadConfig(path?: string) {
|
|||||||
v6: '',
|
v6: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
dns_config: loaded.dns_config ?? {
|
dns: loaded.dns ?? {
|
||||||
override_local_dns: false,
|
nameservers: {
|
||||||
nameservers: [],
|
global: [],
|
||||||
restricted_nameservers: {},
|
split: {},
|
||||||
domains: [],
|
},
|
||||||
|
search_domains: [],
|
||||||
extra_records: [],
|
extra_records: [],
|
||||||
magic_dns: false,
|
magic_dns: false,
|
||||||
base_domain: 'headscale.net',
|
base_domain: 'headscale.net',
|
||||||
|
|||||||
+59
-1
@@ -198,7 +198,7 @@ policy:
|
|||||||
# - https://tailscale.com/kb/1081/magicdns/
|
# - https://tailscale.com/kb/1081/magicdns/
|
||||||
# - https://tailscale.com/blog/2021-09-private-dns-with-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.
|
# Whether to prefer using Headscale provided DNS or use local.
|
||||||
override_local_dns: true
|
override_local_dns: true
|
||||||
|
|
||||||
@@ -259,6 +259,64 @@ dns_config:
|
|||||||
type: A
|
type: A
|
||||||
value: 1.1.1.1
|
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
|
# Unix socket used for the CLI to connect without authentication
|
||||||
# Note: for production you will want to set this to something like:
|
# Note: for production you will want to set this to something like:
|
||||||
unix_socket: /var/run/headscale/headscale.sock
|
unix_socket: /var/run/headscale/headscale.sock
|
||||||
|
|||||||
Reference in New Issue
Block a user