feat(dns): add built-in dcrouter DNS provider support and rename manual domains to dcrouter-hosted/local

This commit is contained in:
2026-04-08 14:54:49 +00:00
parent 5689e93665
commit 93cc5c7b06
21 changed files with 245 additions and 91 deletions

View File

@@ -1,9 +1,28 @@
/**
* Supported DNS provider types. Initially Cloudflare; the abstraction is
* designed so additional providers (Route53, Gandi, DigitalOcean…) can be
* added by implementing the IDnsProvider class interface in ts/dns/providers/.
* Stable ID for the built-in DcRouter pseudo-provider. The Providers list
* surfaces this as the first, non-deletable row so operators see a uniform
* "who serves this?" answer for every domain. The ID is magic — it never
* exists in the DnsProviderDoc collection; handlers inject it at read time
* and reject any mutation that targets it.
*/
export type TDnsProviderType = 'cloudflare';
export const DCROUTER_BUILTIN_PROVIDER_ID = '__dcrouter__';
/**
* Supported DNS provider types.
*
* - 'cloudflare' → Cloudflare account (API token-based). Provider stays
* authoritative; dcrouter pushes record changes via API.
* - 'dcrouter' → Built-in pseudo-provider for dcrouter-hosted zones.
* dcrouter itself is the authoritative DNS server. No
* credentials, cannot be created/edited/deleted through
* the provider CRUD — the Providers view renders it from
* a handler-level synthetic row.
*
* The abstraction is designed so additional providers (Route53, Gandi,
* DigitalOcean, foreign dcrouters…) can be added by implementing the
* IDnsProvider class interface in ts/dns/providers/.
*/
export type TDnsProviderType = 'cloudflare' | 'dcrouter';
/**
* Status of the last connection test against a provider.
@@ -58,6 +77,12 @@ export interface IDnsProviderPublic {
createdBy: string;
/** Whether credentials are configured (true after creation). Never the secret itself. */
hasCredentials: boolean;
/**
* True for the built-in DcRouter pseudo-provider — read-only, cannot be
* created / edited / deleted. Injected by the handler layer, never
* persisted in the DnsProviderDoc collection.
*/
builtIn?: boolean;
}
/**
@@ -114,6 +139,13 @@ export interface IDnsProviderTypeDescriptor {
* credentials each one needs. Used by both backend and frontend.
*/
export const dnsProviderTypeDescriptors: ReadonlyArray<IDnsProviderTypeDescriptor> = [
{
type: 'dcrouter',
displayName: 'DcRouter (built-in)',
description:
'Built-in authoritative DNS. Records are served directly by dcrouter — delegate the domain\'s NS records to make this effective.',
credentialFields: [],
},
{
type: 'cloudflare',
displayName: 'Cloudflare',

View File

@@ -6,16 +6,18 @@ export type TDnsRecordType = 'A' | 'AAAA' | 'CNAME' | 'MX' | 'TXT' | 'NS' | 'SOA
/**
* Where a DNS record came from.
*
* - 'manual' → created in the dcrouter UI / API
* - 'synced' → pulled from a provider during a sync operation
* - 'local' originated in this dcrouter (created via UI / API)
* - 'synced' → pulled from an upstream provider (Cloudflare, foreign
* dcrouter, …) during a sync operation
*/
export type TDnsRecordSource = 'manual' | 'synced';
export type TDnsRecordSource = 'local' | 'synced';
/**
* A DNS record. For manual (authoritative) domains, the record is registered
* with the embedded smartdns.DnsServer. For provider-managed domains, the
* record is mirrored from / pushed to the provider API and `providerRecordId`
* holds the provider's internal record id (for updates and deletes).
* A DNS record. For dcrouter-hosted (authoritative) domains, the record is
* registered with the embedded smartdns.DnsServer. For provider-managed
* domains, the record is mirrored from / pushed to the provider API and
* `providerRecordId` holds the provider's internal record id (for updates
* and deletes).
*/
export interface IDnsRecord {
id: string;

View File

@@ -1,14 +1,15 @@
/**
* Where a domain came from / how it is managed.
*
* - 'manual' → operator added the domain manually. dcrouter is the
* authoritative DNS server for it; records are served by
* the embedded smartdns.DnsServer.
* - 'dcrouter' → dcrouter is the authoritative DNS server for this domain;
* records are served by the embedded smartdns.DnsServer.
* Operators delegate the domain's NS records to make this
* effective.
* - 'provider' → domain was imported from an external DNS provider
* (e.g. Cloudflare). The provider stays authoritative;
* dcrouter only reads/writes records via the provider API.
* (e.g. Cloudflare). The provider stays authoritative;
* dcrouter only reads/writes records via the provider API.
*/
export type TDomainSource = 'manual' | 'provider';
export type TDomainSource = 'dcrouter' | 'provider';
/**
* A domain under management by dcrouter.
@@ -20,7 +21,7 @@ export interface IDomain {
source: TDomainSource;
/** ID of the DnsProvider that owns this domain — only set when source === 'provider'. */
providerId?: string;
/** True when dcrouter is the authoritative DNS server for this domain (source === 'manual'). */
/** True when dcrouter is the authoritative DNS server for this domain (source === 'dcrouter'). */
authoritative: boolean;
/** Authoritative nameservers (display only — populated from provider for imported domains). */
nameservers?: string[];

View File

@@ -45,7 +45,7 @@ export interface IReq_GetDnsRecord extends plugins.typedrequestInterfaces.implem
/**
* Create a new DNS record.
*
* For manual domains: registers the record with the embedded DnsServer.
* For dcrouter-hosted domains: registers the record with the embedded DnsServer.
* For provider domains: pushes the record to the provider API.
*/
export interface IReq_CreateDnsRecord extends plugins.typedrequestInterfaces.implementsTR<

View File

@@ -42,8 +42,8 @@ export interface IReq_GetDomain extends plugins.typedrequestInterfaces.implement
}
/**
* Create a manual (authoritative) domain. dcrouter will serve DNS
* records for this domain via the embedded smartdns.DnsServer.
* Create a dcrouter-hosted (authoritative) domain. dcrouter will serve
* DNS records for this domain via the embedded smartdns.DnsServer.
*/
export interface IReq_CreateDomain extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
@@ -130,7 +130,7 @@ export interface IReq_DeleteDomain extends plugins.typedrequestInterfaces.implem
/**
* Force-resync a provider-managed domain: re-pulls all records from the
* provider API, replacing the cached DnsRecordDocs.
* No-op for manual domains.
* No-op for dcrouter-hosted domains.
*/
export interface IReq_SyncDomain extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,