146 lines
4.4 KiB
JavaScript
146 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* HSD → CoreDNS Zone File Generator
|
|
*
|
|
* Reads all registered names from the HSD sidechain and generates
|
|
* a BIND-format zone file that CoreDNS can serve.
|
|
*
|
|
* Usage:
|
|
* node dump-zone.js > /opt/noc/coredns/db.lthn
|
|
* # Or via cron:
|
|
* cron: every 5 min, run dump-zone.js > db.lthn.new && mv db.lthn.new db.lthn
|
|
*
|
|
* Environment:
|
|
* HSD_URL — HSD RPC endpoint (default: http://127.0.0.1:14037)
|
|
* HSD_API_KEY — HSD API key (default: testkey)
|
|
*/
|
|
|
|
const HSD_URL = process.env.HSD_URL || 'http://127.0.0.1:14037';
|
|
const HSD_API_KEY = process.env.HSD_API_KEY || 'testkey';
|
|
|
|
async function rpcHSD(method, params = []) {
|
|
const auth = Buffer.from(`x:${HSD_API_KEY}`).toString('base64');
|
|
const resp = await fetch(HSD_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${auth}` },
|
|
body: JSON.stringify({ method, params })
|
|
});
|
|
const data = await resp.json();
|
|
if (data.error) throw new Error(`HSD ${method}: ${data.error.message}`);
|
|
return data.result;
|
|
}
|
|
|
|
async function getAllNames() {
|
|
// Get chain height for serial number
|
|
const info = await rpcHSD('getinfo');
|
|
const height = info.blocks;
|
|
|
|
// Scan for names — iterate through known names
|
|
// HSD doesn't have a "list all names with records" RPC, so we try known names
|
|
// In production, this would scan the UTXO set or maintain a name registry
|
|
const names = [];
|
|
|
|
// Try names from our sunrise list + common names
|
|
const candidates = [
|
|
'lethean', 'snider', 'darbs', 'charon', 'cladius',
|
|
'athena', 'hypnos', 'clotho', 'testnet', 'gateway',
|
|
'vpn', 'dns', 'proxy', 'explorer', 'trade',
|
|
];
|
|
|
|
for (const name of candidates) {
|
|
try {
|
|
const resource = await rpcHSD('getnameresource', [name]);
|
|
if (resource && resource.records && resource.records.length > 0) {
|
|
names.push({ name, records: resource.records });
|
|
}
|
|
} catch {
|
|
// Name not registered — skip
|
|
}
|
|
}
|
|
|
|
return { height, names };
|
|
}
|
|
|
|
function recordToZone(name, record) {
|
|
const lines = [];
|
|
switch (record.type) {
|
|
case 'GLUE4':
|
|
// GLUE4 = NS + A glue record
|
|
lines.push(`${name} IN NS ${record.ns}`);
|
|
// Extract subdomain from ns for glue
|
|
const nsSub = record.ns.replace(/\.$/, '');
|
|
lines.push(`${nsSub} IN A ${record.address}`);
|
|
break;
|
|
case 'GLUE6':
|
|
lines.push(`${name} IN NS ${record.ns}`);
|
|
const ns6Sub = record.ns.replace(/\.$/, '');
|
|
lines.push(`${ns6Sub} IN AAAA ${record.address}`);
|
|
break;
|
|
case 'NS':
|
|
lines.push(`${name} IN NS ${record.ns}`);
|
|
break;
|
|
case 'TXT':
|
|
for (const txt of record.txt || []) {
|
|
lines.push(`${name} IN TXT "${txt}"`);
|
|
}
|
|
break;
|
|
case 'DS':
|
|
lines.push(`; DS record for ${name} (DNSSEC delegation)`);
|
|
break;
|
|
default:
|
|
lines.push(`; Unknown record type ${record.type} for ${name}`);
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
async function generateZone() {
|
|
const { height, names } = await getAllNames();
|
|
const serial = `${new Date().toISOString().slice(0,10).replace(/-/g,'')}${String(height).padStart(4,'0')}`;
|
|
|
|
const lines = [
|
|
`; .lthn zone — auto-generated from HSD sidechain (height ${height})`,
|
|
`; Generated: ${new Date().toISOString()}`,
|
|
`; Source: ${HSD_URL}`,
|
|
`; DO NOT EDIT — this file is overwritten by dump-zone.js`,
|
|
``,
|
|
`$ORIGIN lthn.`,
|
|
`$TTL 300`,
|
|
``,
|
|
`@ IN SOA ns5.lthn.io. admin.lthn.io. (`,
|
|
` ${serial} ; serial (date + chain height)`,
|
|
` 3600 ; refresh`,
|
|
` 600 ; retry`,
|
|
` 86400 ; expire`,
|
|
` 300 ) ; minimum`,
|
|
``,
|
|
`; Nameservers (CoreDNS instances serving this zone)`,
|
|
` IN NS ns5.lthn.io.`,
|
|
` IN NS ns6.lthn.io.`,
|
|
``,
|
|
`; === Names from HSD sidechain ===`,
|
|
];
|
|
|
|
if (names.length === 0) {
|
|
lines.push(`; No names registered on sidechain yet`);
|
|
}
|
|
|
|
for (const { name, records } of names) {
|
|
lines.push(``);
|
|
lines.push(`; ${name}.lthn`);
|
|
for (const record of records) {
|
|
lines.push(...recordToZone(name, record));
|
|
}
|
|
}
|
|
|
|
lines.push(``);
|
|
lines.push(`; === End of auto-generated zone ===`);
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
generateZone().then(zone => {
|
|
process.stdout.write(zone + '\n');
|
|
}).catch(err => {
|
|
process.stderr.write(`Error: ${err.message}\n`);
|
|
process.exit(1);
|
|
});
|