#!/usr/bin/env node /** * Configure all 19 domains in NPMplus using browser automation * This script uses Playwright to interact with the NPMplus web UI */ import { chromium } from 'playwright'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { config } from 'dotenv'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const PROJECT_ROOT = join(__dirname, '../..'); config({ path: join(PROJECT_ROOT, '.env') }); // Configuration const NPM_URL = process.env.NPM_URL || 'https://192.168.11.167:81'; const NPM_EMAIL = process.env.NPM_EMAIL || 'nsatoshi2007@hotmail.com'; const NPM_PASSWORD = process.env.NPM_PASSWORD; const HEADLESS = process.env.HEADLESS !== 'false'; if (!NPM_PASSWORD) { console.error('❌ NPM_PASSWORD is required. Set it in .env or export NPM_PASSWORD=...'); process.exit(1); } const PAUSE_MODE = process.env.PAUSE_MODE === 'true'; // All domains to configure (proxy hosts) // UPDATED: 2026-01-18 - Correct VMIDs and IP addresses verified // Reference: docs/04-configuration/RPC_ENDPOINTS_MASTER.md and ALL_VMIDS_ENDPOINTS.md const DOMAINS = [ // sankofa.nexus zone - REMOVED: Services not deployed, were incorrectly routing to Blockscout // { domain: 'sankofa.nexus', target: 'http://192.168.11.140:80', websocket: false }, // { domain: 'phoenix.sankofa.nexus', target: 'http://192.168.11.140:80', websocket: false }, // { domain: 'the-order.sankofa.nexus', target: 'http://192.168.11.140:80', websocket: false }, // d-bis.org zone - Blockchain Explorer // Web UI via nginx: NPMplus → Blockscout:80 (nginx serves UI and proxies /api/* to 4000) { domain: 'explorer.d-bis.org', target: 'http://192.168.11.140:80', websocket: false }, // VMID 5000 // d-bis.org zone - Public RPC endpoints (VMID 2201: besu-rpc-public-1) { domain: 'rpc-http-pub.d-bis.org', target: 'http://192.168.11.221:8545', websocket: true }, // VMID 2201 { domain: 'rpc-ws-pub.d-bis.org', target: 'http://192.168.11.221:8546', websocket: true }, // VMID 2201 // d-bis.org zone - Private RPC endpoints (VMID 2101: besu-rpc-core-1) { domain: 'rpc-http-prv.d-bis.org', target: 'http://192.168.11.211:8545', websocket: true }, // VMID 2101 { domain: 'rpc-ws-prv.d-bis.org', target: 'http://192.168.11.211:8546', websocket: true }, // VMID 2101 // d-bis.org zone - DBIS Core Services { domain: 'dbis-admin.d-bis.org', target: 'http://192.168.11.130:80', websocket: false }, // VMID 10130: dbis-frontend { domain: 'dbis-api.d-bis.org', target: 'http://192.168.11.155:3000', websocket: false }, // VMID 10150: dbis-api-primary { domain: 'dbis-api-2.d-bis.org', target: 'http://192.168.11.156:3000', websocket: false }, // VMID 10151: dbis-api-secondary { domain: 'secure.d-bis.org', target: 'http://192.168.11.130:80', websocket: false }, // VMID 10130: dbis-frontend (path-based routing) // mim4u.org zone - Miracles In Motion Services // VMID 7810: mim-web-1 (Web Frontend) @ 192.168.11.37 - serves static files and proxies /api/* to backend // VMID 7811: mim-api-1 (API Backend) @ 192.168.11.36 - handles API requests { domain: 'mim4u.org', target: 'http://192.168.11.37:80', websocket: false }, // VMID 7810: mim-web-1 { domain: 'secure.mim4u.org', target: 'http://192.168.11.37:80', websocket: false }, // VMID 7810: mim-web-1 { domain: 'training.mim4u.org', target: 'http://192.168.11.37:80', websocket: false }, // VMID 7810: mim-web-1 // defi-oracle.io zone - ThirdWeb RPC (VMID 2400: thirdweb-rpc-1) // Note: Uses HTTPS and port 443 (Nginx with RPC Translator) { domain: 'rpc.public-0138.defi-oracle.io', target: 'https://192.168.11.240:443', websocket: true }, // VMID 2400 { domain: 'rpc.defi-oracle.io', target: 'https://192.168.11.240:443', websocket: true }, // VMID 2400 - HTTP RPC { domain: 'wss.defi-oracle.io', target: 'https://192.168.11.240:443', websocket: true }, // VMID 2400 - WebSocket RPC ]; // www.* domains that redirect to parent domains const REDIRECT_DOMAINS = [ // REMOVED: Sankofa redirects - services not deployed // { domain: 'www.sankofa.nexus', redirectTo: 'sankofa.nexus' }, // { domain: 'www.phoenix.sankofa.nexus', redirectTo: 'phoenix.sankofa.nexus' }, { domain: 'www.mim4u.org', redirectTo: 'mim4u.org' }, ]; function log(message, type = 'info') { const icons = { success: '✅', error: '❌', warning: '⚠️', info: '📋' }; const icon = icons[type] || '📋'; console.log(`${icon} ${message}`); } async function pause(page, message) { if (PAUSE_MODE) { log(`Paused: ${message}`, 'info'); await page.pause(); } } async function login(page) { log('Logging in to NPMplus...'); await page.goto(NPM_URL, { waitUntil: 'domcontentloaded', timeout: 30000 }); await pause(page, 'At login page'); // Wait for login form await page.waitForSelector('input[type="email"], input[name="email"], input[placeholder*="email" i]', { timeout: 10000 }); // Fill email const emailInput = await page.$('input[type="email"]') || await page.$('input[name="email"]') || await page.$('input[placeholder*="email" i]'); if (emailInput) { await emailInput.fill(NPM_EMAIL); log(`Filled email: ${NPM_EMAIL}`); } // Fill password const passwordInput = await page.$('input[type="password"]'); if (passwordInput) { await passwordInput.fill(NPM_PASSWORD); log('Filled password'); } // Click login button const loginButton = await page.$('button[type="submit"]') || await page.$('button:has-text("Sign In")') || await page.$('button:has-text("Login")'); if (loginButton) { await loginButton.click(); log('Clicked login button'); } else { await page.keyboard.press('Enter'); } // Wait for dashboard - NPMplus might use different URL patterns try { await page.waitForTimeout(3000); const currentURL = page.url(); log(`Current URL after login: ${currentURL}`); // Check if we're logged in by looking for dashboard elements const dashboardElements = await page.$('text=/dashboard|hosts|proxy|Proxy Hosts/i').catch(() => null); if (dashboardElements || currentURL.includes('dashboard') || currentURL.includes('hosts') || currentURL.includes('proxy')) { log('Logged in successfully', 'success'); await pause(page, 'After login'); return true; } // Try waiting for URL change await page.waitForURL(/dashboard|hosts|proxy|#/, { timeout: 15000 }); log('Logged in successfully', 'success'); await pause(page, 'After login'); return true; } catch (e) { // Check if we're actually logged in despite the timeout const currentURL = page.url(); const pageContent = await page.textContent('body').catch(() => ''); if (currentURL.includes('dashboard') || currentURL.includes('hosts') || pageContent.includes('Proxy Hosts')) { log('Logged in (detected via page content)', 'success'); return true; } log(`Login check failed: ${e.message}`, 'error'); log(`Current URL: ${currentURL}`, 'info'); await page.screenshot({ path: '/tmp/npmplus-login-error.png' }); return false; } } async function configureProxyHost(page, domainConfig) { const { domain, target, websocket } = domainConfig; const url = new URL(target); const scheme = url.protocol.replace(':', ''); const hostname = url.hostname; const port = url.port || (scheme === 'https' ? '443' : '80'); log(`Configuring ${domain}...`); try { // Navigate to proxy hosts await page.goto(`${NPM_URL}/#/proxy-hosts`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2000); // Click "Add Proxy Host" button const addButton = await page.$('button:has-text("Add Proxy Host"), button:has-text("Add"), a:has-text("Add Proxy Host")'); if (addButton) { await addButton.click(); log(` Clicked Add Proxy Host for ${domain}`); } else { // Try alternative selectors await page.click('button.btn-primary, .btn-add, [data-action="add"]'); } await page.waitForTimeout(1000); // Fill domain name const domainInput = await page.$('input[name="domain_names"], input[placeholder*="domain" i], #domain_names'); if (domainInput) { await domainInput.fill(domain); log(` Filled domain: ${domain}`); } // Fill forward scheme const schemeSelect = await page.$('select[name="forward_scheme"], select#forward_scheme'); if (schemeSelect) { await schemeSelect.selectOption(scheme); log(` Set scheme: ${scheme}`); } // Fill forward host const hostInput = await page.$('input[name="forward_host"], input[name="forward_hostname"], input#forward_host'); if (hostInput) { await hostInput.fill(hostname); log(` Filled host: ${hostname}`); } // Fill forward port const portInput = await page.$('input[name="forward_port"], input#forward_port'); if (portInput) { await portInput.fill(port); log(` Filled port: ${port}`); } // Enable websocket if needed if (websocket) { const websocketCheckbox = await page.$('input[type="checkbox"][name*="websocket" i], input[type="checkbox"][id*="websocket" i]'); if (websocketCheckbox && !(await websocketCheckbox.isChecked())) { await websocketCheckbox.check(); log(` Enabled WebSocket for ${domain}`); } } // Enable block exploits const blockExploitsCheckbox = await page.$('input[type="checkbox"][name*="block" i], input[type="checkbox"][id*="block" i]'); if (blockExploitsCheckbox && !(await blockExploitsCheckbox.isChecked())) { await blockExploitsCheckbox.check(); } // Navigate to SSL tab const sslTab = await page.$('a[href*="ssl" i], button:has-text("SSL"), .tab:has-text("SSL")'); if (sslTab) { await sslTab.click(); await page.waitForTimeout(1000); } // Request SSL certificate const requestCertButton = await page.$('button:has-text("Request"), button:has-text("SSL Certificate"), a:has-text("Request")'); if (requestCertButton) { await requestCertButton.click(); await page.waitForTimeout(1000); // Fill email for Let's Encrypt const emailInput = await page.$('input[name="letsencrypt_email"], input[name="email"], input[type="email"]'); if (emailInput) { await emailInput.fill('nsatoshi2007@hotmail.com'); } // Agree to terms const agreeCheckbox = await page.$('input[type="checkbox"][name*="agree" i]'); if (agreeCheckbox) { await agreeCheckbox.check(); } // Enable Force SSL const forceSSLCheckbox = await page.$('input[type="checkbox"][name*="ssl_forced" i], input[type="checkbox"][name*="force" i]'); if (forceSSLCheckbox && !(await forceSSLCheckbox.isChecked())) { await forceSSLCheckbox.check(); } // Enable HTTP/2 const http2Checkbox = await page.$('input[type="checkbox"][name*="http2" i]'); if (http2Checkbox && !(await http2Checkbox.isChecked())) { await http2Checkbox.check(); } // Enable HSTS const hstsCheckbox = await page.$('input[type="checkbox"][name*="hsts" i]'); if (hstsCheckbox && !(await hstsCheckbox.isChecked())) { await hstsCheckbox.check(); } } // Save const saveButton = await page.$('button:has-text("Save"), button.btn-primary:has-text("Save"), button[type="submit"]'); if (saveButton) { await saveButton.click(); log(` Saved ${domain}`, 'success'); await page.waitForTimeout(2000); return true; } else { log(` Could not find save button for ${domain}`, 'warning'); return false; } } catch (error) { log(` Error configuring ${domain}: ${error.message}`, 'error'); await page.screenshot({ path: `/tmp/npmplus-error-${domain.replace(/\./g, '-')}.png` }); return false; } } async function configureRedirectHost(page, redirectConfig) { const { domain, redirectTo } = redirectConfig; log(`Configuring redirect ${domain} → ${redirectTo}...`); try { // Navigate to proxy hosts await page.goto(`${NPM_URL}/#/proxy-hosts`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2000); // Click "Add Proxy Host" button const addButton = await page.$('button:has-text("Add Proxy Host"), button:has-text("Add"), a:has-text("Add Proxy Host")'); if (addButton) { await addButton.click(); log(` Clicked Add Proxy Host for ${domain}`); } else { await page.click('button.btn-primary, .btn-add, [data-action="add"]'); } await page.waitForTimeout(1000); // Switch to "Redirect" type (if available) const redirectType = await page.$('input[type="radio"][value="redirect"], input[type="radio"][name*="redirect" i]'); if (redirectType) { await redirectType.click(); log(` Selected Redirect type for ${domain}`); await page.waitForTimeout(500); } // Fill domain name const domainInput = await page.$('input[name="domain_names"], input[placeholder*="domain" i], #domain_names'); if (domainInput) { await domainInput.fill(domain); log(` Filled domain: ${domain}`); } // Fill redirect target const redirectInput = await page.$('input[name="forward_host"], input[name="redirect"], input[placeholder*="redirect" i]'); if (redirectInput) { await redirectInput.fill(redirectTo); log(` Set redirect to: ${redirectTo}`); } // Navigate to SSL tab if exists const sslTab = await page.$('a[href*="ssl" i], button:has-text("SSL"), .tab:has-text("SSL")'); if (sslTab) { await sslTab.click(); await page.waitForTimeout(1000); // Request SSL certificate for redirect const requestCertButton = await page.$('button:has-text("Request"), button:has-text("SSL Certificate"), a:has-text("Request")'); if (requestCertButton) { await requestCertButton.click(); await page.waitForTimeout(1000); // Fill email for Let's Encrypt const emailInput = await page.$('input[name="letsencrypt_email"], input[name="email"], input[type="email"]'); if (emailInput) { await emailInput.fill('nsatoshi2007@hotmail.com'); } // Agree to terms const agreeCheckbox = await page.$('input[type="checkbox"][name*="agree" i]'); if (agreeCheckbox) { await agreeCheckbox.check(); } // Enable Force SSL const forceSSLCheckbox = await page.$('input[type="checkbox"][name*="ssl_forced" i], input[type="checkbox"][name*="force" i]'); if (forceSSLCheckbox && !(await forceSSLCheckbox.isChecked())) { await forceSSLCheckbox.check(); } } } // Save const saveButton = await page.$('button:has-text("Save"), button.btn-primary:has-text("Save"), button[type="submit"]'); if (saveButton) { await saveButton.click(); log(` Saved redirect ${domain} → ${redirectTo}`, 'success'); await page.waitForTimeout(2000); return true; } else { log(` Could not find save button for ${domain}`, 'warning'); return false; } } catch (error) { log(` Error configuring redirect ${domain}: ${error.message}`, 'error'); await page.screenshot({ path: `/tmp/npmplus-redirect-error-${domain.replace(/\./g, '-')}.png` }); return false; } } async function main() { log('Starting NPMplus domain configuration...', 'info'); log(`Target: ${NPM_URL}`, 'info'); log(`Proxy hosts to configure: ${DOMAINS.length}`, 'info'); log(`Redirects to configure: ${REDIRECT_DOMAINS.length}`, 'info'); console.log(''); const browser = await chromium.launch({ headless: HEADLESS, ignoreHTTPSErrors: true }); const context = await browser.newContext({ ignoreHTTPSErrors: true, viewport: { width: 1920, height: 1080 } }); const page = await context.newPage(); try { // Login const loggedIn = await login(page); if (!loggedIn) { log('Failed to login. Exiting.', 'error'); await browser.close(); process.exit(1); } // Configure proxy hosts let proxySuccess = 0; let proxyFailed = 0; for (const domainConfig of DOMAINS) { const result = await configureProxyHost(page, domainConfig); if (result) { proxySuccess++; } else { proxyFailed++; } await page.waitForTimeout(1000); // Small delay between domains } // Configure redirects let redirectSuccess = 0; let redirectFailed = 0; for (const redirectConfig of REDIRECT_DOMAINS) { const result = await configureRedirectHost(page, redirectConfig); if (result) { redirectSuccess++; } else { redirectFailed++; } await page.waitForTimeout(1000); // Small delay between redirects } console.log(''); log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info'); log('Configuration Summary', 'info'); log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info'); log(`✅ Proxy Hosts - Successful: ${proxySuccess}, Failed: ${proxyFailed}`, proxyFailed > 0 ? 'warning' : 'success'); log(`✅ Redirects - Successful: ${redirectSuccess}, Failed: ${redirectFailed}`, redirectFailed > 0 ? 'warning' : 'success'); log(`📋 Total Proxy Hosts: ${DOMAINS.length}`, 'info'); log(`📋 Total Redirects: ${REDIRECT_DOMAINS.length}`, 'info'); console.log(''); log('⏳ SSL certificates may take 1-2 minutes to be issued', 'info'); } catch (error) { log(`Fatal error: ${error.message}`, 'error'); await page.screenshot({ path: '/tmp/npmplus-fatal-error.png' }); } finally { await browser.close(); } } main().catch(console.error);