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>
458 lines
18 KiB
JavaScript
Executable File
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);
|