402 lines
12 KiB
JavaScript
402 lines
12 KiB
JavaScript
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(); |