feat: DNS provider abstraction — Technitium, Cloudflare, RFC 2136, Manual
- dns-providers/: adapter base class + registry with auto-discovery - technitium.js: wraps existing Technitium API calls into adapter interface - cloudflare.js: Cloudflare API v4 adapter (zones, records, credentials) - rfc2136.js: RFC 2136 dynamic DNS via nsupdate (BIND, PowerDNS, etc.) - manual.js: no-op adapter for external DNS management with instructions - provider-dns.js: provider-aware DNS context, resolves active adapter from config - Universal helper methods: universalCreateRecord/Delete/ResolveRecord - All 7 route files updated to use universal methods instead of raw dns.call() - Setup wizard: provider dropdown (Technitium, Cloudflare, RFC 2136, Manual) - DNS template selector: added Cloudflare and External/Manual options - Config schema: validates dns.provider field - Capability gating on Technitium-specific endpoints (logs, restart, update) - Backward compatible: no provider set = auto-detect (technitium if dns.ip exists)
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Manual DNS Provider Adapter
|
||||
* No-op adapter for users who manage DNS externally (manual, cPanel, other control panels).
|
||||
* Provides propagation checking only — all record operations return helpful instructions.
|
||||
*/
|
||||
const BaseDNSProvider = require('./base');
|
||||
|
||||
class ManualDNSProvider extends BaseDNSProvider {
|
||||
constructor(config, ctx) {
|
||||
super(config, ctx);
|
||||
this.providerId = 'manual';
|
||||
this.displayName = 'Manual / External DNS';
|
||||
this.description = 'Manage DNS records yourself via your provider\'s control panel';
|
||||
}
|
||||
|
||||
supportsCapability(cap) {
|
||||
return ['credentials'].includes(cap);
|
||||
}
|
||||
|
||||
getCapabilities() {
|
||||
return ['credentials'];
|
||||
}
|
||||
|
||||
async authenticate() {
|
||||
return { success: true, message: 'Manual DNS — no authentication needed' };
|
||||
}
|
||||
|
||||
async createRecord({ domain, zone, type, value, ttl }) {
|
||||
return {
|
||||
status: 'manual',
|
||||
message: `Create this record manually in your DNS control panel:`,
|
||||
instructions: {
|
||||
name: domain,
|
||||
type: type || 'A',
|
||||
value,
|
||||
ttl: ttl || 300
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async deleteRecord({ domain, type, value }) {
|
||||
return {
|
||||
status: 'manual',
|
||||
message: `Delete this record manually from your DNS control panel:`,
|
||||
instructions: {
|
||||
name: domain,
|
||||
type: type || 'A',
|
||||
value: value || '(any)'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async resolveRecords({ domain, zone, type }) {
|
||||
// Use Node.js built-in DNS to resolve regardless of provider
|
||||
const dns = require('dns').promises;
|
||||
try {
|
||||
const resolver = new dns.Resolver();
|
||||
resolver.setServers(['1.1.1.1', '8.8.8.8']);
|
||||
const records = await resolver.resolve(domain, type || 'A');
|
||||
return {
|
||||
status: 'ok',
|
||||
response: {
|
||||
records: records.map(r => ({
|
||||
type: type || 'A',
|
||||
domain,
|
||||
rData: { ipAddress: r },
|
||||
ttl: 0,
|
||||
manual: true
|
||||
}))
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
return { status: 'ok', response: { records: [] } };
|
||||
}
|
||||
}
|
||||
|
||||
async getStatus() {
|
||||
return {
|
||||
providerId: this.providerId,
|
||||
displayName: this.displayName,
|
||||
description: this.description,
|
||||
capabilities: this.getCapabilities(),
|
||||
authenticated: true,
|
||||
note: 'DNS records are managed externally. Use propagation checks to verify changes.'
|
||||
};
|
||||
}
|
||||
|
||||
validateConfig() {
|
||||
return { valid: true, errors: [] };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ManualDNSProvider;
|
||||
Reference in New Issue
Block a user