Created
November 2, 2025 20:13
-
-
Save nickjuntilla/0bc821ab6d0f91083da1c129a57f63c0 to your computer and use it in GitHub Desktop.
Google DNS manager
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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