const axios = require('axios'); const { exec } = require('child_process'); const util = require('util'); const execPromise = util.promisify(exec); require('dotenv').config(); const path = require('path'); const { models } = require(path.resolve(process.env.ROOT_PATH, './db/db.js')); const net = require('net'); const fs = require('fs').promises; const { execSync } = require('child_process'); // Create Cloudflare API client const createCloudflareClient = () => { // If using API Token (preferred method) if (process.env.CF_API_TOKEN) { return axios.create({ baseURL: 'https://api.cloudflare.com/client/v4', headers: { 'Authorization': `Bearer ${process.env.CF_API_TOKEN}`, 'Content-Type': 'application/json' }, timeout: 10000 }); } // If using API Key (legacy method) else if (process.env.CF_API_KEY && process.env.CF_EMAIL) { return axios.create({ baseURL: 'https://api.cloudflare.com/client/v4', headers: { 'X-Auth-Key': process.env.CF_API_KEY, 'X-Auth-Email': process.env.CF_EMAIL, 'Content-Type': 'application/json' }, timeout: 10000 }); } else { throw new Error('No valid Cloudflare authentication method found'); } }; async function generateDkimKey(domain) { try { // Create directory if it doesn't exist const keyPath = `/etc/opendkim/keys/${domain}`; await fs.mkdir(keyPath, { recursive: true }); // Generate the DKIM key using opendkim-genkey directly execSync(`opendkim-genkey -D ${keyPath} -d ${domain} -s mail`); // Set proper permissions (if needed) execSync(`chown -R opendkim:opendkim ${keyPath}`); // Read the generated public key const publicKeyContent = await fs.readFile(`${keyPath}/mail.txt`, 'utf8'); // Extract the DKIM record value const dkimMatch = publicKeyContent.match(/p=([^)]+)/); const dkimValue = dkimMatch ? dkimMatch[1] : null; if (!dkimValue) { throw new Error('Could not extract DKIM value from key file'); } return { host: 'mail._domainkey', value: `v=DKIM1; k=rsa; p=${dkimValue}` }; } catch (error) { console.error('Error generating DKIM key:', error); return null; } } async function configureDNS(domain) { const cf = createCloudflareClient(); try { // Step 1: Get the zone ID for the domain console.log(`Fetching zone for domain: ${domain}`); const zonesResponse = await cf.get('/zones', { params: { name: domain } }); if (!zonesResponse.data.success) { console.error('Cloudflare API request failed:', zonesResponse.data.errors); return false; } if (zonesResponse.data.result.length === 0) { console.error(`Zone not found for domain: ${domain}`); return false; } const zoneId = zonesResponse.data.result[0].id; console.log(`Found zone ID: ${zoneId} for domain: ${domain}`); // Step 2: Get existing DNS records console.log(`Fetching existing DNS records for ${domain}...`); const recordsResponse = await cf.get(`/zones/${zoneId}/dns_records`); if (!recordsResponse.data.success) { console.error('Failed to fetch DNS records:', recordsResponse.data.errors); return false; } // Step 3: Delete existing records (except NS and SOA) const existingRecords = recordsResponse.data.result; console.log(`Found ${existingRecords.length} DNS records`); for (const record of existingRecords) { if (record.type !== 'NS' && record.type !== 'SOA') { console.log(`Deleting record: ${record.type} ${record.name}`); try { await cf.delete(`/zones/${zoneId}/dns_records/${record.id}`); } catch (error) { console.error(`Failed to delete record ${record.id}:`, error.message); } } } // Step 4: Add MX record console.log(`Adding MX record for ${domain}`); await cf.post(`/zones/${zoneId}/dns_records`, { type: 'MX', name: '@', content: `mail.${domain}`, priority: 10, ttl: 3600 }); // Step 5: Add SPF record console.log(`Adding SPF record for ${domain}`); await cf.post(`/zones/${zoneId}/dns_records`, { type: 'TXT', name: '@', content: `"v=spf1 mx a ip4:${process.env.SERVER_IP} -all"`, ttl: 3600 }); // Step 6: Generate and add DKIM record console.log(`Generating DKIM key for ${domain}`); const dkimRecord = await generateDkimKey(domain); if (dkimRecord) { console.log(`Adding DKIM record for ${domain}`); // Fix the content format - remove any existing quotes and add exactly one set const rawValue = dkimRecord.value.replace(/"/g, ''); const dkimContent = `"${rawValue}"`; console.log(`DKIM content: ${dkimContent}`); await cf.post(`/zones/${zoneId}/dns_records`, { type: 'TXT', name: 'mail._domainkey', content: dkimContent, ttl: 3600 }); } // Step 7: Add DMARC record console.log(`Adding DMARC record for ${domain}`); await cf.post(`/zones/${zoneId}/dns_records`, { type: 'TXT', name: '_dmarc', content: `"v=DMARC1; p=reject; rua=mailto:postmaster@${domain}"`, ttl: 3600 }); // Step 8: Add A record for mail subdomain console.log(`Adding A record for mail.${domain}`); await cf.post(`/zones/${zoneId}/dns_records`, { type: 'A', name: 'mail', content: process.env.SERVER_IP, ttl: 3600 }); console.log(`DNS configuration completed for ${domain}`); return true; } catch (error) { console.error(`Error configuring DNS for ${domain}:`, error.message); if (error.response) { console.error('API error details:', error.response.data); } return false; } } function checkOpenDKIM() { return new Promise((resolve, reject) => { const client = new net.Socket(); client.connect(8891, 'opendkim', () => { client.destroy(); resolve(true); }); client.on('error', (err) => { client.destroy(); reject(err); }); // Add timeout setTimeout(() => { client.destroy(); reject(new Error('Connection timeout')); }, 5000); }); } async function configure2WeekMailDNS() { const domain = '2weekmail.fyi'; const cf = createCloudflareClient(); try { // Step 1: Get the zone ID for the domain console.log(`Fetching zone for domain: ${domain}`); const zonesResponse = await cf.get('/zones', { params: { name: domain } }); if (!zonesResponse.data.success) { console.error('Cloudflare API request failed:', zonesResponse.data.errors); return false; } if (zonesResponse.data.result.length === 0) { console.error(`Zone not found for domain: ${domain}`); return false; } const zoneId = zonesResponse.data.result[0].id; console.log(`Found zone ID: ${zoneId} for domain: ${domain}`); // Step 2: Get existing DNS records console.log(`Fetching existing DNS records for ${domain}...`); const recordsResponse = await cf.get(`/zones/${zoneId}/dns_records`); if (!recordsResponse.data.success) { console.error('Failed to fetch DNS records:', recordsResponse.data.errors); return false; } const existingRecords = recordsResponse.data.result; console.log(`Found ${existingRecords.length} DNS records`); // Check for required records let hasMX = false; let hasSPF = false; let hasDMARC = false; let hasMailA = false; let dkimRecordId = null; for (const record of existingRecords) { if (record.type === 'MX' && record.name === domain) { hasMX = true; } else if (record.type === 'TXT' && record.name === domain && record.content.includes('v=spf1')) { hasSPF = true; } else if (record.type === 'TXT' && record.name === '_dmarc.' + domain) { hasDMARC = true; } else if (record.type === 'A' && record.name === 'mail.' + domain) { hasMailA = true; } else if (record.type === 'TXT' && record.name === 'mail._domainkey.' + domain) { dkimRecordId = record.id; } } // Add missing records if needed if (!hasMX) { console.log(`Adding MX record for ${domain}`); await cf.post(`/zones/${zoneId}/dns_records`, { type: 'MX', name: '@', content: `mail.${domain}`, priority: 10, ttl: 3600 }); } if (!hasSPF) { console.log(`Adding SPF record for ${domain}`); await cf.post(`/zones/${zoneId}/dns_records`, { type: 'TXT', name: '@', content: `"v=spf1 mx a ip4:${process.env.SERVER_IP} -all"`, ttl: 3600 }); } if (!hasDMARC) { console.log(`Adding DMARC record for ${domain}`); await cf.post(`/zones/${zoneId}/dns_records`, { type: 'TXT', name: '_dmarc', content: `"v=DMARC1; p=reject; rua=mailto:postmaster@${domain}"`, ttl: 3600 }); } if (!hasMailA) { console.log(`Adding A record for mail.${domain}`); await cf.post(`/zones/${zoneId}/dns_records`, { type: 'A', name: 'mail', content: process.env.SERVER_IP, ttl: 3600 }); } // Always regenerate and update DKIM record console.log(`Generating DKIM key for ${domain}`); const dkimRecord = await generateDkimKey(domain); if (dkimRecord) { // Delete existing DKIM record if it exists if (dkimRecordId) { console.log(`Deleting existing DKIM record for ${domain}`); await cf.delete(`/zones/${zoneId}/dns_records/${dkimRecordId}`); } console.log(`Adding DKIM record for ${domain}`); const rawValue = dkimRecord.value.replace(/"/g, ''); const dkimContent = `"${rawValue}"`; console.log(`DKIM content: ${dkimContent}`); await cf.post(`/zones/${zoneId}/dns_records`, { type: 'TXT', name: 'mail._domainkey', content: dkimContent, ttl: 3600 }); } console.log(`DNS configuration completed for ${domain}`); return true; } catch (error) { console.error(`Error configuring DNS for ${domain}:`, error.message); if (error.response) { console.error('API error details:', error.response.data); } return false; } } async function exportAllDomains() { try { // Check if OpenDKIM is available const isOpenDkimAvailable = await checkOpenDKIM(); if (!isOpenDkimAvailable) { console.error('Cannot proceed: OpenDKIM container is not available'); return; } // First, handle the 2weekmail.fyi domain specifically console.log('--------------------------'); console.log('Configuring DNS for 2weekmail.fyi'); await configure2WeekMailDNS(); console.log('--------------------------'); console.log('Starting DNS configuration for all other domains...'); const domains = await models.Domain.query().where('domain', '!=', '2weekmail.fyi').andWhere('in_cloudflare', 0); console.log(`Found ${domains.length} domains to configure`); let successCount = 0; let failCount = 0; for (const domain of domains) { console.log(`--------------------------`); console.log(`Configuring DNS for domain: ${domain.domain}`); const success = await configureDNS(domain.domain); if (success) { // Update the domain record to mark it as configured in Cloudflare await models.Domain.query().where('domain', domain.domain).patch({ in_cloudflare: 1 }); successCount++; } else { failCount++; } // Add a small delay between API calls to avoid rate limiting await new Promise(resolve => setTimeout(resolve, 1000)); } console.log('--------------------------'); console.log(`DNS configuration completed: ${successCount} succeeded, ${failCount} failed`); } catch (error) { console.error('Error processing domains:', error); } } // Verify required environment variables if (!process.env.CF_API_TOKEN && !(process.env.CF_API_KEY && process.env.CF_EMAIL)) { console.error('Error: Either CF_API_TOKEN or both CF_API_KEY and CF_EMAIL environment variables must be set'); process.exit(1); } if (!process.env.SERVER_IP) { console.error('Error: SERVER_IP environment variable is not set'); process.exit(1); } // Run the script exportAllDomains();