docs: Ledger Live integration, contract deploy learnings, NEXT_STEPS updates
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands - CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround - CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check - NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere - MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates - LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
189
scripts/unifi/configure-vlans-node.js
Executable file
189
scripts/unifi/configure-vlans-node.js
Executable file
@@ -0,0 +1,189 @@
|
||||
#!/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);
|
||||
});
|
||||
Reference in New Issue
Block a user