Skip to content

Instantly share code, notes, and snippets.

@scolton99
Created November 16, 2022 06:00
Show Gist options
  • Save scolton99/3b99e6462fdc6b70a025724dc39c24ae to your computer and use it in GitHub Desktop.
Save scolton99/3b99e6462fdc6b70a025724dc39c24ae to your computer and use it in GitHub Desktop.
$TTL 60
$ORIGIN local.example.com.
@ IN SOA examplecom.ddns.net. admin.example.com. {{SERIAL}} 300 900 604800 60
@ IN NS examplecom.ddns.net.
@ IN A {{EXT_IP}}
* IN CNAME examplecom.ddns.net.
import { readFileSync, writeFile, writeFileSync } from 'fs';
import fetch from 'node-fetch';
import { fileURLToPath } from 'url';
import { resolve, dirname } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const { UDM_USERNAME, UDM_PASSWORD, UDM_HOST, NS1_IP, NS2_FQDN, EXT_IP } = process.env;
const DOMAIN = '';
const authenticate = async (host, username, password) => {
const result = await fetch(`https://${host}/api/auth/login`, {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: {
'Content-Type': 'application/json'
}
});
const cookieHeader = result.headers.get('set-cookie');
const { groups: { token } } = cookieHeader.match(/^TOKEN=(?<token>.*?);/);
if (!token)
throw new Error('h');
return token;
};
const formatLines = lines => {
if (!Array.isArray(lines) || lines.length === 0)
return '';
const cols = lines[0].length;
const columnLengths = new Array(cols).fill(0);
for (const line of lines) {
if (line.length !== cols) throw new Error('Inconsistent data');
for (let i = 0; i < line.length; ++i)
columnLengths[i] = Math.max(line[i].length + 4, columnLengths[i]);
}
return lines.map(it => it.map((col, colIdx) => col.padEnd(columnLengths[colIdx])).join('')).join('\n');
};
const cleanName = name => name.replace(/[., _+|=]/g, '-').replace(/é/g, 'e').replace(/[^a-zA-Z0-9-]/g, '').toLowerCase();
const main = async () => {
const token = await authenticate(UDM_HOST, UDM_USERNAME, UDM_PASSWORD);
const deviceResponse = await fetch(`https://${UDM_HOST}/proxy/network/api/s/default/stat/sta`, {
headers: {
'Cookie': `TOKEN=${token}`
}
});
const { data: rawDevices } = await deviceResponse.json();
const filteredDevices = rawDevices.filter(it => (it.hostname || it.name) && it.ip);
const devices = filteredDevices.map(it => ({
cname: cleanName(it.name ?? it.hostname),
mac: it.mac,
ip: it.ip
}));
const configurationResponse = await fetch(`https://${UDM_HOST}/proxy/network/api/s/default/rest/user`, {
headers: {
'Cookie': `TOKEN=${token}`
}
});
const { data: rawConfigurations } = await configurationResponse.json();
const filteredConfigurations = rawConfigurations.filter(it => it.use_fixedip && it.fixed_ip && !devices.find(it2 => it2.mac === it.mac));
devices.push(...filteredConfigurations.map(it => ({
cname: cleanName(it.name ?? it.hostname),
mac: it.mac,
ip: it.fixed_ip
})));
const nameMap = {};
for (const { cname, mac } of devices) {
if (!(cname in nameMap))
nameMap[cname] = [];
nameMap[cname].push(mac);
}
for (const value of Object.values(nameMap))
value.sort();
for (const device of devices) {
const { cname, mac } = device;
console.log
if (!(cname in nameMap) || nameMap[cname].length === 1)
continue;
const order = nameMap[cname].indexOf(mac);
device.cname = `${cname}-${order}`;
}
const deviceLines = devices.map(it => [it.cname, 'IN', 'A', it.ip]);
const tsStr = `${Math.floor(Date.now() / 1000)}`;
const lines = [
[`${DOMAIN}.`, 'IN', 'SOA', `ns1.${DOMAIN}. admin.example.comF. ${tsStr} 300 900 604800 60`],
[`${DOMAIN}.`, 'IN', 'NS', `ns1.${DOMAIN}.`],
[`${DOMAIN}.`, 'IN', 'NS', `${NS2_FQDN}.`],
['ns1', 'IN', 'A', NS1_IP],
...deviceLines
];
const forwardZone = `$ORIGIN ${DOMAIN}.\n$TTL 60\n${formatLines(lines)}\n`;
const octet = (ip, num) => ip.split('.')[num];
const makePtrLine = device => [octet(device.ip, 3), 'IN', 'PTR', `${device.cname}.${DOMAIN}.`];
const rev1DeviceLines = devices.map(makePtrLine);
const rev1Lines = [
['@', 'IN', 'SOA', `ns1.${DOMAIN}. admin.example.com. ${tsStr} 300 900 604800 60`],
['', 'IN', 'NS', `ns1.${DOMAIN}.`],
...rev1DeviceLines
];
const reverseZone1 = `$TTL 60\n${formatLines(rev1Lines)}\n`;
writeFileSync('db.192.168.1', reverseZone1);
writeFileSync('internal.db.local.example.com', forwardZone);
const externalZone = readFileSync(resolve(__dirname, 'db.external.template'), { encoding: 'utf-8'}).replace('{{EXT_IP}}', EXT_IP).replace('{{SERIAL}}', tsStr);
writeFileSync('external.db.local.example.com', externalZone);
};
main();
@scolton99
Copy link
Author

Variables:
UDM_USERNAME The username for the UDM Pro.
UDM_PASSWORD The password for the UDM Pro.
UDM_HOST The hostname for the UDM Pro.
NS1_IP The internal IP of the primary nameserver.
NS2_FQDN The external FQDN (e.g., ns2.example.com) of the secondary nameserver.
EXT_IP The public IP of the private network.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment