Skip to content

Instantly share code, notes, and snippets.

@nickjuntilla
Created November 2, 2025 20:13
Show Gist options
  • Save nickjuntilla/0bc821ab6d0f91083da1c129a57f63c0 to your computer and use it in GitHub Desktop.
Save nickjuntilla/0bc821ab6d0f91083da1c129a57f63c0 to your computer and use it in GitHub Desktop.
Google DNS manager
/**
* Google Cloud DNS Manager for WTTP Bridge
*
* This utility can be used to automatically create DNS zones and records
* when users add custom domains to the bridge service. Users can then
* point their domain's nameservers to Google Cloud DNS to get full DNS
* management including A/AAAA records pointing to the bridge server.
*
* Usage:
* const dnsManager = require('./dns-manager');
*
* // Create a DNS zone for a domain
* const zone = await dnsManager.createZone('example-com', 'example.com');
*
* // Get nameservers for the zone (users point their domain here)
* const nameservers = await dnsManager.getNameservers('example-com');
*
* // Add A record pointing to bridge server IP
* await dnsManager.createARecord('example-com', 'example.com', '192.0.2.1');
*
* // Add WTTP TXT record
* await dnsManager.createTXTRecord('example-com', 'example.com', 'wordl3.eth', '11155111');
*/
const { DNS } = require("@google-cloud/dns");
const dns = new DNS();
/**
* Create a WTTP TXT record for a custom domain
* @param {string} zoneName - The DNS zone name (e.g., 'my-zone')
* @param {string} domain - The domain to create the record for (e.g., 'example.com')
* @param {string} wttpAddress - The WTTP address (e.g., 'wordl3.eth')
* @param {string|null} chainId - Optional chain ID (e.g., '11155111')
* @returns {Promise<object>} The created DNS record
*/
async function createTXTRecord(zoneName, domain, wttpAddress, chainId = null) {
try {
const zone = dns.zone(zoneName);
// Build TXT record value
let txtValue = `v=wttp3; a=${wttpAddress};`;
if (chainId) {
txtValue += ` chain=${chainId};`;
}
// Create the record at wttp.{domain}
const record = zone.record("TXT", {
name: `wttp.${domain}.`,
data: txtValue,
ttl: 300, // 5 minutes TTL
});
// Add the record to the zone
const [change] = await zone.addRecords([record]);
console.log(`[DNS] Created TXT record: wttp.${domain} = ${txtValue}`);
console.log(`[DNS] Change ID: ${change.id}`);
return change;
} catch (error) {
console.error(`[DNS] Error creating TXT record: ${error.message}`);
throw error;
}
}
/**
* Delete a WTTP TXT record
* @param {string} zoneName - The DNS zone name
* @param {string} domain - The domain to delete the record for
* @returns {Promise<object>} The DNS change result
*/
async function deleteTXTRecord(zoneName, domain) {
try {
const zone = dns.zone(zoneName);
// Get existing records
const [records] = await zone.getRecords({
name: `wttp.${domain}.`,
type: "TXT",
});
if (records.length === 0) {
throw new Error(`No TXT record found for wttp.${domain}`);
}
// Remove the records
const [change] = await zone.deleteRecords(records);
console.log(`[DNS] Deleted TXT record: wttp.${domain}`);
return change;
} catch (error) {
console.error(`[DNS] Error deleting TXT record: ${error.message}`);
throw error;
}
}
/**
* List all WTTP TXT records in a zone
* @param {string} zoneName - The DNS zone name
* @returns {Promise<Array>} Array of WTTP TXT records
*/
async function listWTTPRecords(zoneName) {
try {
const zone = dns.zone(zoneName);
const [records] = await zone.getRecords({ type: "TXT" });
// Filter for WTTP records
const wttpRecords = records.filter((record) => {
const data = Array.isArray(record.data)
? record.data.join("")
: record.data;
return data.includes("v=wttp3");
});
return wttpRecords.map((record) => ({
name: record.name,
data: Array.isArray(record.data) ? record.data.join("") : record.data,
ttl: record.ttl,
}));
} catch (error) {
console.error(`[DNS] Error listing WTTP records: ${error.message}`);
throw error;
}
}
/**
* Create a DNS zone for a domain
* @param {string} zoneName - The DNS zone name (e.g., 'example-com')
* @param {string} dnsName - The DNS name for the zone (e.g., 'example.com.')
* @param {string} description - Optional description for the zone
* @returns {Promise<object>} The created DNS zone
*/
async function createZone(
zoneName,
dnsName,
description = "WTTP Bridge DNS Zone"
) {
try {
// Ensure dnsName ends with a dot
const normalizedDnsName = dnsName.endsWith(".") ? dnsName : `${dnsName}.`;
const [zone] = await dns.createZone(zoneName, {
dnsName: normalizedDnsName,
description: description,
});
console.log(`[DNS] Created zone: ${zoneName} (${normalizedDnsName})`);
console.log(`[DNS] Zone ID: ${zone.metadata.name}`);
return zone;
} catch (error) {
console.error(`[DNS] Error creating zone: ${error.message}`);
throw error;
}
}
/**
* Get nameservers for a DNS zone
* @param {string} zoneName - The DNS zone name
* @returns {Promise<Array<string>>} Array of nameserver hostnames
*/
async function getNameservers(zoneName) {
try {
const zone = dns.zone(zoneName);
const [metadata] = await zone.getMetadata();
const nameservers = metadata.nameServers || [];
console.log(`[DNS] Nameservers for ${zoneName}:`, nameservers);
return nameservers;
} catch (error) {
console.error(`[DNS] Error getting nameservers: ${error.message}`);
throw error;
}
}
/**
* Create an A record (IPv4)
* @param {string} zoneName - The DNS zone name
* @param {string} domain - The domain to create the record for (e.g., 'example.com' or 'www.example.com')
* @param {string} ipAddress - The IPv4 address
* @param {number} ttl - Time to live in seconds (default: 300)
* @returns {Promise<object>} The DNS change result
*/
async function createARecord(zoneName, domain, ipAddress, ttl = 300) {
try {
const zone = dns.zone(zoneName);
// Ensure domain ends with a dot
const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
const record = zone.record("A", {
name: normalizedDomain,
data: ipAddress,
ttl: ttl,
});
const [change] = await zone.addRecords([record]);
console.log(`[DNS] Created A record: ${normalizedDomain} -> ${ipAddress}`);
console.log(`[DNS] Change ID: ${change.id}`);
return change;
} catch (error) {
console.error(`[DNS] Error creating A record: ${error.message}`);
throw error;
}
}
/**
* Create an AAAA record (IPv6)
* @param {string} zoneName - The DNS zone name
* @param {string} domain - The domain to create the record for (e.g., 'example.com' or 'www.example.com')
* @param {string} ipv6Address - The IPv6 address
* @param {number} ttl - Time to live in seconds (default: 300)
* @returns {Promise<object>} The DNS change result
*/
async function createAAAARecord(zoneName, domain, ipv6Address, ttl = 300) {
try {
const zone = dns.zone(zoneName);
// Ensure domain ends with a dot
const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
const record = zone.record("AAAA", {
name: normalizedDomain,
data: ipv6Address,
ttl: ttl,
});
const [change] = await zone.addRecords([record]);
console.log(
`[DNS] Created AAAA record: ${normalizedDomain} -> ${ipv6Address}`
);
console.log(`[DNS] Change ID: ${change.id}`);
return change;
} catch (error) {
console.error(`[DNS] Error creating AAAA record: ${error.message}`);
throw error;
}
}
/**
* Create a CNAME record
* @param {string} zoneName - The DNS zone name
* @param {string} alias - The alias domain (e.g., 'www.example.com')
* @param {string} canonicalName - The canonical name (e.g., 'example.com.')
* @param {number} ttl - Time to live in seconds (default: 300)
* @returns {Promise<object>} The DNS change result
*/
async function createCNAMERecord(zoneName, alias, canonicalName, ttl = 300) {
try {
const zone = dns.zone(zoneName);
// Ensure both end with dots
const normalizedAlias = alias.endsWith(".") ? alias : `${alias}.`;
const normalizedCanonical = canonicalName.endsWith(".")
? canonicalName
: `${canonicalName}.`;
const record = zone.record("CNAME", {
name: normalizedAlias,
data: normalizedCanonical,
ttl: ttl,
});
const [change] = await zone.addRecords([record]);
console.log(
`[DNS] Created CNAME record: ${normalizedAlias} -> ${normalizedCanonical}`
);
console.log(`[DNS] Change ID: ${change.id}`);
return change;
} catch (error) {
console.error(`[DNS] Error creating CNAME record: ${error.message}`);
throw error;
}
}
/**
* Setup a complete domain with DNS zone, A/AAAA records, and WTTP TXT record
* This is a convenience function that sets up everything needed for a domain
* @param {string} zoneName - The DNS zone name (e.g., 'example-com')
* @param {string} domain - The domain name (e.g., 'example.com')
* @param {Object} options - Configuration options
* @param {string} options.ipv4Address - IPv4 address for A record (optional)
* @param {string} options.ipv6Address - IPv6 address for AAAA record (optional)
* @param {string} options.cnameTarget - CNAME target instead of A/AAAA (optional)
* @param {string} options.wttpAddress - WTTP address for TXT record (required)
* @param {string} options.chainId - Chain ID for TXT record (optional)
* @param {number} options.ttl - TTL for records (default: 300)
* @param {string} options.description - Zone description (optional)
* @returns {Promise<object>} Object with zone, nameservers, and created records
*/
async function setupDomain(zoneName, domain, options = {}) {
try {
const {
ipv4Address,
ipv6Address,
cnameTarget,
wttpAddress,
chainId = null,
ttl = 300,
description = "WTTP Bridge DNS Zone",
} = options;
if (!wttpAddress) {
throw new Error("wttpAddress is required");
}
// Ensure domain is normalized
const normalizedDomain = domain.endsWith(".")
? domain.slice(0, -1)
: domain;
const dnsName = `${normalizedDomain}.`;
console.log(`[DNS] Setting up domain: ${normalizedDomain}`);
// Create DNS zone
const zone = await createZone(zoneName, normalizedDomain, description);
// Get nameservers
const nameservers = await getNameservers(zoneName);
const createdRecords = [];
// Create A/AAAA or CNAME records
if (cnameTarget) {
const change = await createCNAMERecord(
zoneName,
normalizedDomain,
cnameTarget,
ttl
);
createdRecords.push({ type: "CNAME", change });
} else {
if (ipv4Address) {
const change = await createARecord(
zoneName,
normalizedDomain,
ipv4Address,
ttl
);
createdRecords.push({ type: "A", change });
}
if (ipv6Address) {
const change = await createAAAARecord(
zoneName,
normalizedDomain,
ipv6Address,
ttl
);
createdRecords.push({ type: "AAAA", change });
}
if (!ipv4Address && !ipv6Address) {
console.warn(
`[DNS] Warning: No A or AAAA record created. Domain won't resolve to an IP.`
);
}
}
// Create WTTP TXT record
const txtChange = await createTXTRecord(
zoneName,
normalizedDomain,
wttpAddress,
chainId
);
createdRecords.push({ type: "TXT", change: txtChange });
return {
zone,
nameservers,
domain: normalizedDomain,
records: createdRecords,
instructions: {
nameservers: nameservers,
message: `Point your domain's nameservers to: ${nameservers.join(
", "
)}`,
},
};
} catch (error) {
console.error(`[DNS] Error setting up domain: ${error.message}`);
throw error;
}
}
module.exports = {
createZone,
getNameservers,
createARecord,
createAAAARecord,
createCNAMERecord,
createTXTRecord,
deleteTXTRecord,
listWTTPRecords,
setupDomain,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment