#!/usr/bin/env node /** * Configure all VLANs on UDM Pro via Private API * Node.js version for better password handling */ import { readFileSync } from 'fs'; import { join } from 'path'; import { homedir } from 'os'; import { UnifiClient, ApiMode } from '../../unifi-api/dist/index.js'; import { NetworksService } from '../../unifi-api/dist/index.js'; // Load environment variables const envPath = join(homedir(), '.env'); function loadEnvFile(filePath) { try { const envFile = readFileSync(filePath, 'utf8'); const envVars = envFile.split('\n').filter( (line) => line.includes('=') && !line.trim().startsWith('#') ); for (const line of envVars) { const [key, ...values] = line.split('='); if (key && values.length > 0 && /^[A-Z_][A-Z0-9_]*$/.test(key.trim())) { let value = values.join('=').trim(); if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1); } process.env[key.trim()] = value; } } return true; } catch { return false; } } loadEnvFile(envPath); const baseUrl = process.env.UNIFI_UDM_URL || 'https://192.168.0.1'; const username = process.env.UNIFI_USERNAME || 'unifi_api'; const password = process.env.UNIFI_PASSWORD; const siteId = process.env.UNIFI_SITE_ID || 'default'; if (!password) { console.error('❌ UNIFI_PASSWORD not set in environment'); process.exit(1); } console.log('UDM Pro VLAN Configuration Script'); console.log('=================================='); console.log(''); console.log(`UDM URL: ${baseUrl}`); console.log(`Site ID: ${siteId}`); console.log(`Username: ${username}`); console.log(''); // Create client const client = new UnifiClient({ baseUrl, apiMode: ApiMode.PRIVATE, username, password, siteId, verifySSL: false, }); const networksService = new NetworksService(client); // VLAN configurations const vlanConfigs = [ { id: 11, name: 'MGMT-LAN', subnet: '192.168.11.0/24', gateway: '192.168.11.1', dhcpStart: '192.168.11.100', dhcpStop: '192.168.11.200' }, { id: 110, name: 'BESU-VAL', subnet: '10.110.0.0/24', gateway: '10.110.0.1', dhcpStart: '10.110.0.10', dhcpStop: '10.110.0.250' }, { id: 111, name: 'BESU-SEN', subnet: '10.111.0.0/24', gateway: '10.111.0.1', dhcpStart: '10.111.0.10', dhcpStop: '10.111.0.250' }, { id: 112, name: 'BESU-RPC', subnet: '10.112.0.0/24', gateway: '10.112.0.1', dhcpStart: '10.112.0.10', dhcpStop: '10.112.0.250' }, { id: 120, name: 'BLOCKSCOUT', subnet: '10.120.0.0/24', gateway: '10.120.0.1', dhcpStart: '10.120.0.10', dhcpStop: '10.120.0.250' }, { id: 121, name: 'CACTI', subnet: '10.121.0.0/24', gateway: '10.121.0.1', dhcpStart: '10.121.0.10', dhcpStop: '10.121.0.250' }, { id: 130, name: 'CCIP-OPS', subnet: '10.130.0.0/24', gateway: '10.130.0.1', dhcpStart: '10.130.0.10', dhcpStop: '10.130.0.250' }, { id: 132, name: 'CCIP-COMMIT', subnet: '10.132.0.0/24', gateway: '10.132.0.1', dhcpStart: '10.132.0.10', dhcpStop: '10.132.0.250' }, { id: 133, name: 'CCIP-EXEC', subnet: '10.133.0.0/24', gateway: '10.133.0.1', dhcpStart: '10.133.0.10', dhcpStop: '10.133.0.250' }, { id: 134, name: 'CCIP-RMN', subnet: '10.134.0.0/24', gateway: '10.134.0.1', dhcpStart: '10.134.0.10', dhcpStop: '10.134.0.250' }, { id: 140, name: 'FABRIC', subnet: '10.140.0.0/24', gateway: '10.140.0.1', dhcpStart: '10.140.0.10', dhcpStop: '10.140.0.250' }, { id: 141, name: 'FIREFLY', subnet: '10.141.0.0/24', gateway: '10.141.0.1', dhcpStart: '10.141.0.10', dhcpStop: '10.141.0.250' }, { id: 150, name: 'INDY', subnet: '10.150.0.0/24', gateway: '10.150.0.1', dhcpStart: '10.150.0.10', dhcpStop: '10.150.0.250' }, { id: 160, name: 'SANKOFA-SVC', subnet: '10.160.0.0/22', gateway: '10.160.0.1', dhcpStart: '10.160.0.10', dhcpStop: '10.160.0.254' }, { id: 200, name: 'PHX-SOV-SMOM', subnet: '10.200.0.0/20', gateway: '10.200.0.1', dhcpStart: '10.200.0.10', dhcpStop: '10.200.15.250' }, { id: 201, name: 'PHX-SOV-ICCC', subnet: '10.201.0.0/20', gateway: '10.201.0.1', dhcpStart: '10.201.0.10', dhcpStop: '10.201.15.250' }, { id: 202, name: 'PHX-SOV-DBIS', subnet: '10.202.0.0/20', gateway: '10.202.0.1', dhcpStart: '10.202.0.10', dhcpStop: '10.202.15.250' }, { id: 203, name: 'PHX-SOV-AR', subnet: '10.203.0.0/20', gateway: '10.203.0.1', dhcpStart: '10.203.0.10', dhcpStop: '10.203.15.250' }, ]; // Helper function to sleep function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function createVlan(config, networksService) { try { console.log(`Creating VLAN ${config.id}: ${config.name} (${config.subnet})...`); const networkConfig = { name: config.name, purpose: 'corporate', vlan: config.id, ip_subnet: config.subnet, ipv6_interface_type: 'none', dhcpd_enabled: true, dhcpd_start: config.dhcpStart, dhcpd_stop: config.dhcpStop, dhcpd_leasetime: 86400, domain_name: 'localdomain', is_nat: true, networkgroup: 'LAN', }; await networksService.createNetwork(networkConfig); console.log(` ✅ VLAN ${config.id} created successfully`); return true; } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); if (errorMsg.includes('already exists') || errorMsg.includes('duplicate')) { console.log(` ⚠️ VLAN ${config.id} already exists (skipping)`); return true; } else { console.log(` ❌ Failed to create VLAN ${config.id}: ${errorMsg}`); return false; } } } async function main() { console.log('Authenticating...'); // Force authentication by making a test request try { await networksService.listNetworks(); console.log('✅ Authentication successful'); } catch (error) { console.error('❌ Authentication failed:', error); process.exit(1); } console.log(''); console.log('Creating VLANs...'); console.log(''); // Create VLANs sequentially to avoid rate limiting const results = []; for (const config of vlanConfigs) { const result = await createVlan(config, networksService); results.push({ status: result ? 'fulfilled' : 'rejected', value: result }); // Small delay between requests to avoid rate limiting if (config !== vlanConfigs[vlanConfigs.length - 1]) { await sleep(500); } } console.log(''); console.log('Verifying created networks...'); try { const networks = await networksService.listNetworks(); const vlanNetworks = networks.filter(n => n.vlan && n.vlan > 0); console.log(''); console.log(`✅ Found ${vlanNetworks.length} VLAN networks:`); vlanNetworks.forEach(network => { console.log(` VLAN ${network.vlan}: ${network.name} (${network.ip_subnet || 'N/A'})`); }); } catch (error) { console.error('Error listing networks:', error); } const successCount = results.filter(r => r.status === 'fulfilled' && r.value).length; const failCount = results.length - successCount; console.log(''); console.log('=================================='); console.log(`✅ Successfully created: ${successCount}/${vlanConfigs.length} VLANs`); if (failCount > 0) { console.log(`❌ Failed: ${failCount} VLANs`); } console.log(''); } main().catch(error => { console.error('Fatal error:', error); process.exit(1); });