Files
proxmox/scripts/nginx-proxy-manager/configure-npmplus-domains.js
defiQUG fbda1b4beb
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
docs: Ledger Live integration, contract deploy learnings, NEXT_STEPS updates
- 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>
2026-02-12 15:46:57 -08:00

458 lines
18 KiB
JavaScript
Executable File

#!/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);