/** * Cloudflare Domain Sync Manager * * Synchronizes Cloudflare DNS zones with the local Domain table. * Automatically imports zones and marks obsolete domains when zones are removed. */ import * as plugins from '../plugins.ts'; import { logger } from '../logging.ts'; import { OneboxDatabase } from './database.ts'; import type { IDomain } from '../types.ts'; export class CloudflareDomainSync { private database: OneboxDatabase; private cloudflareAccount: plugins.cloudflare.CloudflareAccount | null = null; constructor(database: OneboxDatabase) { this.database = database; } /** * Initialize Cloudflare connection */ async init(): Promise { try { const apiKey = this.database.getSetting('cloudflareAPIKey'); if (!apiKey) { logger.warn('Cloudflare API key not configured. Domain sync will be limited.'); return; } this.cloudflareAccount = new plugins.cloudflare.CloudflareAccount(apiKey); logger.info('Cloudflare domain sync initialized'); } catch (error) { logger.error(`Failed to initialize Cloudflare sync: ${error.message}`); throw error; } } /** * Check if Cloudflare is configured */ isConfigured(): boolean { return this.cloudflareAccount !== null; } /** * Sync all Cloudflare zones with Domain table */ async syncZones(): Promise { if (!this.isConfigured()) { logger.warn('Cloudflare not configured, skipping zone sync'); return; } try { logger.info('Starting Cloudflare zone synchronization...'); // Fetch all zones from Cloudflare (v6+ API uses convenience.listZones()) const zones = await this.cloudflareAccount!.convenience.listZones(); logger.info(`Found ${zones.length} Cloudflare zone(s)`); const now = Date.now(); const syncedZoneIds = new Set(); // Sync each zone to the Domain table for (const zone of zones) { try { const domain = zone.name; const zoneId = zone.id; syncedZoneIds.add(zoneId); // Check if domain already exists const existingDomain = this.database.getDomainByName(domain); if (existingDomain) { // Update existing domain this.database.updateDomain(existingDomain.id!, { dnsProvider: 'cloudflare', cloudflareZoneId: zoneId, isObsolete: false, // Re-activate if it was marked obsolete updatedAt: now, }); logger.debug(`Updated domain: ${domain}`); } else { // Create new domain this.database.createDomain({ domain, dnsProvider: 'cloudflare', cloudflareZoneId: zoneId, isObsolete: false, defaultWildcard: true, // Default to wildcard certificates createdAt: now, updatedAt: now, }); logger.info(`Added new domain from Cloudflare: ${domain}`); } } catch (error) { logger.error(`Failed to sync zone ${zone.name}: ${error.message}`); } } // Mark domains as obsolete if their Cloudflare zones no longer exist await this.markObsoleteDomains(syncedZoneIds); logger.success(`Cloudflare zone sync completed: ${zones.length} zone(s) synced`); } catch (error) { logger.error(`Cloudflare zone sync failed: ${error.message}`); throw error; } } /** * Mark domains as obsolete if their Cloudflare zones have been removed */ private async markObsoleteDomains(activeZoneIds: Set): Promise { try { // Get all domains managed by Cloudflare const cloudflareDomains = this.database.getDomainsByProvider('cloudflare'); let obsoleteCount = 0; for (const domain of cloudflareDomains) { // If domain has a Cloudflare zone ID but it's not in the active set, mark obsolete if (domain.cloudflareZoneId && !activeZoneIds.has(domain.cloudflareZoneId)) { this.database.updateDomain(domain.id!, { isObsolete: true, updatedAt: Date.now(), }); logger.warn(`Marked domain as obsolete (zone removed): ${domain.domain}`); obsoleteCount++; } } if (obsoleteCount > 0) { logger.info(`Marked ${obsoleteCount} domain(s) as obsolete`); } } catch (error) { logger.error(`Failed to mark obsolete domains: ${error.message}`); } } /** * Get sync status information */ getSyncStatus(): { configured: boolean; totalDomains: number; cloudflareDomains: number; obsoleteDomains: number; } { const allDomains = this.database.getAllDomains(); const cloudflareDomains = allDomains.filter(d => d.dnsProvider === 'cloudflare'); const obsoleteDomains = allDomains.filter(d => d.isObsolete); return { configured: this.isConfigured(), totalDomains: allDomains.length, cloudflareDomains: cloudflareDomains.length, obsoleteDomains: obsoleteDomains.length, }; } }