Files
proxmox/scripts/nginx-proxy-manager/configure-ssl-all-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

416 lines
16 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Configure SSL certificates for all domains in Nginx Proxy Manager
* Uses browser automation to configure proxy hosts and Let's Encrypt certificates
*/
import { chromium } from 'playwright';
import { readFileSync } from 'fs';
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, '../..');
// Load environment variables
config({ path: join(PROJECT_ROOT, '.env') });
// Configuration
const NPM_URL = process.env.NPM_URL || 'http://192.168.11.26:81';
const NPM_EMAIL = process.env.NPM_EMAIL || process.env.NGINX_EMAIL || 'nsatoshi2007@hotmail.com';
const NPM_USERNAME = process.env.NPM_USERNAME || 'nsatoshi2007';
const NPM_PASSWORD = process.env.NPM_PASSWORD || process.env.NGINX_PASSWORD || 'L@ker$2010';
const HEADLESS = process.env.HEADLESS !== 'false';
const PAUSE_MODE = process.env.PAUSE_MODE === 'true';
// All domains to configure
const DOMAINS = [
// sankofa.nexus zone
{ domain: 'sankofa.nexus', target: 'http://192.168.11.140:80', description: 'Sankofa Nexus' },
{ domain: 'www.sankofa.nexus', target: 'http://192.168.11.140:80', description: 'Sankofa Nexus WWW' },
{ domain: 'phoenix.sankofa.nexus', target: 'http://192.168.11.140:80', description: 'Phoenix Sankofa' },
{ domain: 'www.phoenix.sankofa.nexus', target: 'http://192.168.11.140:80', description: 'Phoenix Sankofa WWW' },
{ domain: 'the-order.sankofa.nexus', target: 'http://192.168.11.140:80', description: 'The Order' },
// d-bis.org zone
{ domain: 'explorer.d-bis.org', target: 'http://192.168.11.140:4000', description: 'Blockscout Explorer (Direct Route)' },
{ domain: 'rpc-http-pub.d-bis.org', target: 'https://192.168.11.252:443', description: 'RPC HTTP Public' },
{ domain: 'rpc-ws-pub.d-bis.org', target: 'https://192.168.11.252:443', description: 'RPC WebSocket Public' },
{ domain: 'rpc-http-prv.d-bis.org', target: 'https://192.168.11.251:443', description: 'RPC HTTP Private' },
{ domain: 'rpc-ws-prv.d-bis.org', target: 'https://192.168.11.251:443', description: 'RPC WebSocket Private' },
{ domain: 'dbis-admin.d-bis.org', target: 'http://192.168.11.130:80', description: 'DBIS Admin' },
{ domain: 'dbis-api.d-bis.org', target: 'http://192.168.11.155:3000', description: 'DBIS API' },
{ domain: 'dbis-api-2.d-bis.org', target: 'http://192.168.11.156:3000', description: 'DBIS API 2' },
{ domain: 'secure.d-bis.org', target: 'http://192.168.11.130:80', description: 'DBIS Secure' },
// mim4u.org zone
// MIM4U - VMID 7810 (mim-web-1) @ 192.168.11.37 - Web Frontend serves main site and proxies /api/* to 7811
{ domain: 'mim4u.org', target: 'http://192.168.11.37:80', description: 'MIM4U' },
{ domain: 'www.mim4u.org', target: 'http://192.168.11.37:80', description: 'MIM4U WWW' },
{ domain: 'secure.mim4u.org', target: 'http://192.168.11.37:80', description: 'MIM4U Secure' },
{ domain: 'training.mim4u.org', target: 'http://192.168.11.37:80', description: 'MIM4U Training' },
// defi-oracle.io zone
{ domain: 'rpc.public-0138.defi-oracle.io', target: 'https://192.168.11.252:443', description: 'ThirdWeb RPC' },
];
// Helper functions
const log = {
info: (msg) => console.log(`[INFO] ${msg}`),
success: (msg) => console.log(`[✓] ${msg}`),
warn: (msg) => console.log(`[⚠] ${msg}`),
error: (msg) => console.log(`[✗] ${msg}`),
};
const pause = async (page, message) => {
if (PAUSE_MODE) {
log.info(`⏸ PAUSE: ${message}`);
log.info('Press Enter to continue...');
await new Promise(resolve => {
process.stdin.once('data', () => resolve());
});
}
};
async function login(page) {
log.info('Logging in to Nginx Proxy Manager...');
try {
// Try with more lenient wait strategy
await page.goto(NPM_URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.waitForTimeout(2000); // Give page time to fully load
await pause(page, 'At login page');
// Take screenshot for debugging
await page.screenshot({ path: '/tmp/npm-login-page.png' }).catch(() => {});
log.info('Screenshot saved to /tmp/npm-login-page.png');
// Wait for login form - try multiple selectors
try {
await page.waitForSelector('input[type="email"], input[name="email"], input[type="text"], input[placeholder*="email" i]', { timeout: 15000 });
} catch (e) {
log.warn('Login form not found with standard selectors, trying alternative...');
await page.waitForSelector('input', { timeout: 10000 });
}
// Find email input - try multiple strategies
let emailInput = await page.$('input[type="email"]');
if (!emailInput) emailInput = await page.$('input[name="email"]');
if (!emailInput) emailInput = await page.$('input[placeholder*="email" i]');
if (!emailInput) emailInput = await page.$('input[type="text"]');
if (!emailInput) {
// Get all inputs and try the first one
const inputs = await page.$$('input');
if (inputs.length > 0) emailInput = inputs[0];
}
if (emailInput) {
await emailInput.click();
await emailInput.fill(NPM_EMAIL);
log.info(`Filled email: ${NPM_EMAIL}`);
} else {
log.error('Could not find email input field');
return false;
}
// Find password input
const passwordInput = await page.$('input[type="password"]');
if (passwordInput) {
await passwordInput.click();
await passwordInput.fill(''); // Clear first
await page.waitForTimeout(300);
await passwordInput.fill(NPM_PASSWORD);
await page.waitForTimeout(300);
// Trigger events to ensure validation
await passwordInput.evaluate(el => {
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
el.dispatchEvent(new Event('blur', { bubbles: true }));
});
log.info('Filled password');
} else {
log.error('Could not find password input field');
return false;
}
await pause(page, 'Credentials filled, ready to submit');
// Find and click login button - try multiple strategies
let loginButton = await page.$('button[type="submit"]');
if (!loginButton) loginButton = await page.$('button:has-text("Sign In")');
if (!loginButton) loginButton = await page.$('button:has-text("Login")');
if (!loginButton) loginButton = await page.$('button:has-text("Log in")');
if (!loginButton) {
// Try to find any button and click it
const buttons = await page.$$('button');
if (buttons.length > 0) loginButton = buttons[buttons.length - 1]; // Usually submit is last
}
if (loginButton) {
await loginButton.click();
log.info('Clicked login button');
} else {
// Try pressing Enter
log.info('Login button not found, pressing Enter');
await page.keyboard.press('Enter');
}
// Wait a moment for any error messages to appear
await page.waitForTimeout(2000);
// Check for error messages before waiting for navigation
const errorSelectors = [
'.error',
'.alert-danger',
'.alert-error',
'[role="alert"]',
'.text-danger',
'.invalid-feedback',
'[class*="error"]',
'[class*="invalid"]'
];
for (const selector of errorSelectors) {
const errorElement = await page.$(selector);
if (errorElement) {
const errorText = await errorElement.textContent();
if (errorText && errorText.trim().length > 0) {
log.error(`Login error detected: ${errorText.trim()}`);
await page.screenshot({ path: '/tmp/npm-login-error.png' }).catch(() => {});
return false;
}
}
}
// Wait for navigation or dashboard - with better error handling
try {
await page.waitForURL(/dashboard|hosts|proxy|login/, { timeout: 20000 });
const currentUrl = page.url();
if (currentUrl.includes('login')) {
// Check for error message again after navigation attempt
let errorFound = false;
for (const selector of errorSelectors) {
const errorElement = await page.$(selector);
if (errorElement) {
const errorText = await errorElement.textContent();
if (errorText && errorText.trim().length > 0) {
log.error(`Login failed: ${errorText.trim()}`);
errorFound = true;
break;
}
}
}
if (!errorFound) {
// Check page content for error messages
const pageText = await page.textContent('body');
if (pageText && (pageText.includes('Invalid') || pageText.includes('incorrect') || pageText.includes('error'))) {
log.error('Login failed - error message detected in page content');
errorFound = true;
}
}
if (!errorFound) {
log.error('Login failed - still on login page (no error message found)');
}
await page.screenshot({ path: '/tmp/npm-login-error.png' }).catch(() => {});
return false;
}
log.success('Logged in successfully');
await pause(page, 'After login');
return true;
} catch (e) {
log.error(`Login timeout: ${e.message}`);
// Check for errors one more time
const pageText = await page.textContent('body').catch(() => '');
if (pageText && (pageText.includes('Invalid') || pageText.includes('incorrect'))) {
log.error('Login failed - invalid credentials detected');
}
await page.screenshot({ path: '/tmp/npm-login-timeout.png' }).catch(() => {});
return false;
}
} catch (error) {
log.error(`Login error: ${error.message}`);
await page.screenshot({ path: '/tmp/npm-login-error.png' }).catch(() => {});
return false;
}
}
async function configureProxyHost(page, domainConfig) {
const { domain, target, description } = domainConfig;
log.info(`Configuring proxy host for ${domain}...`);
try {
// Navigate to Proxy Hosts
await page.goto(`${NPM_URL}/#/proxy-hosts`, { waitUntil: 'networkidle' });
await pause(page, `At proxy hosts page for ${domain}`);
// Click Add Proxy Host button
const addButton = await page.$('button:has-text("Add Proxy Host")') ||
await page.$('a:has-text("Add Proxy Host")') ||
await page.$('button.btn-primary');
if (!addButton) {
log.warn(`Could not find Add Proxy Host button for ${domain}`);
return false;
}
await addButton.click();
await page.waitForTimeout(1000);
await pause(page, `Add Proxy Host form opened for ${domain}`);
// Fill in domain name
const domainInput = await page.$('input[name="domain_names"]') ||
await page.$('input[placeholder*="domain"]') ||
await page.$('input[type="text"]');
if (domainInput) {
await domainInput.fill(domain);
}
// Configure forwarding
const schemeSelect = await page.$('select[name="forward_scheme"]') ||
await page.$('select');
if (schemeSelect) {
const scheme = target.startsWith('https') ? 'https' : 'http';
await schemeSelect.selectOption(scheme);
}
const hostInput = await page.$('input[name="forward_hostname"]') ||
await page.$('input[name="forward_host"]');
if (hostInput) {
const host = new URL(target).hostname;
await hostInput.fill(host);
}
const portInput = await page.$('input[name="forward_port"]');
if (portInput) {
const port = new URL(target).port || (target.startsWith('https') ? '443' : '80');
await portInput.fill(port);
}
await pause(page, `Form filled for ${domain}`);
// Configure SSL
const sslTab = await page.$('a:has-text("SSL")') ||
await page.$('button:has-text("SSL")');
if (sslTab) {
await sslTab.click();
await page.waitForTimeout(500);
// Request Let's Encrypt certificate
const requestCertButton = await page.$('button:has-text("Request a new SSL Certificate")') ||
await page.$('button:has-text("Request SSL")');
if (requestCertButton) {
await requestCertButton.click();
await page.waitForTimeout(500);
// Enable Force SSL
const forceSSL = await page.$('input[type="checkbox"][name*="force"]') ||
await page.$('input[type="checkbox"]');
if (forceSSL) {
await forceSSL.check();
}
// Enable HTTP/2
const http2 = await page.$('input[type="checkbox"][name*="http2"]');
if (http2) {
await http2.check();
}
// Enable HSTS
const hsts = await page.$('input[type="checkbox"][name*="hsts"]');
if (hsts) {
await hsts.check();
}
await pause(page, `SSL configured for ${domain}`);
}
}
// Save
const saveButton = await page.$('button:has-text("Save")') ||
await page.$('button.btn-primary:has-text("Save")');
if (saveButton) {
await saveButton.click();
await page.waitForTimeout(2000);
log.success(`Proxy host configured for ${domain}`);
return true;
} else {
log.warn(`Could not find Save button for ${domain}`);
return false;
}
} catch (error) {
log.error(`Error configuring ${domain}: ${error.message}`);
return false;
}
}
async function main() {
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('🔒 Nginx Proxy Manager SSL Configuration');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('');
log.info(`NPM URL: ${NPM_URL}`);
log.info(`Email: ${NPM_EMAIL}`);
log.info(`Domains to configure: ${DOMAINS.length}`);
console.log('');
const browser = await chromium.launch({
headless: HEADLESS,
ignoreHTTPSErrors: true,
});
const context = await browser.newContext({
ignoreHTTPSErrors: true,
});
const page = await context.newPage();
try {
// Login
const loggedIn = await login(page);
if (!loggedIn) {
log.error('Failed to login. Please check credentials.');
process.exit(1);
}
// Configure each domain
let successCount = 0;
let failCount = 0;
for (const domainConfig of DOMAINS) {
const success = await configureProxyHost(page, domainConfig);
if (success) {
successCount++;
} else {
failCount++;
}
// Small delay between domains
await page.waitForTimeout(1000);
}
console.log('');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
log.info(`Configuration complete: ${successCount} succeeded, ${failCount} failed`);
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
} catch (error) {
log.error(`Fatal error: ${error.message}`);
console.error(error);
process.exit(1);
} finally {
await browser.close();
}
}
main().catch(console.error);