Created
November 16, 2022 06:00
-
-
Save scolton99/3b99e6462fdc6b70a025724dc39c24ae to your computer and use it in GitHub Desktop.
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
$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. |
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
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(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.