Files
onebox/ts/classes/cloudflare-sync.ts

166 lines
5.0 KiB
TypeScript
Raw Normal View History

/**
* 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<void> {
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<void> {
if (!this.isConfigured()) {
logger.warn('Cloudflare not configured, skipping zone sync');
return;
}
try {
logger.info('Starting Cloudflare zone synchronization...');
// Fetch all zones from Cloudflare
const zones = await this.cloudflareAccount!.getZones();
logger.info(`Found ${zones.length} Cloudflare zone(s)`);
const now = Date.now();
const syncedZoneIds = new Set<string>();
// 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<string>): Promise<void> {
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,
};
}
}