Files
proxmox/scripts/nginx-proxy-manager/request-npmplus-7-certs-dns-ui.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

183 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Request SSL certificates for the 7 NPMplus proxy hosts that have no cert.
* Uses browser automation: for each host, edit → SSL → Request new certificate
* → DNS Challenge → Cloudflare → (first credential) → email + agree → submit.
*
* Run from repo root: node scripts/nginx-proxy-manager/request-npmplus-7-certs-dns-ui.js
* Requires: .env with NPM_URL, NPM_EMAIL, NPM_PASSWORD. HEADLESS=false to watch.
*/
import { chromium } from 'playwright';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { config } from 'dotenv';
import https from 'https';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const PROJECT_ROOT = join(__dirname, '../..');
config({ path: join(PROJECT_ROOT, '.env') });
const NPM_URL = process.env.NPM_URL || 'https://192.168.11.167:81';
const NPM_EMAIL = process.env.NPM_EMAIL || 'admin@example.org';
const NPM_PASSWORD = process.env.NPM_PASSWORD;
const LETSENCRYPT_EMAIL = process.env.SSL_EMAIL || process.env.NPM_EMAIL || NPM_EMAIL;
const HEADLESS = process.env.HEADLESS !== 'false';
const PAUSE_MODE = process.env.PAUSE_MODE === 'true';
// Host IDs for the 7 proxy hosts without a certificate (from list-npmplus-proxy-hosts-cert-status.sh)
// Set FIRST_ONLY=1 to process only host 22 (for testing)
const ALL_HOST_IDS = [22, 26, 24, 27, 28, 29, 25];
const HOST_IDS_WITHOUT_CERT = process.env.FIRST_ONLY === '1' || process.env.FIRST_ONLY === 'true' ? ALL_HOST_IDS.slice(0, 1) : ALL_HOST_IDS;
if (!NPM_PASSWORD) {
console.error('❌ NPM_PASSWORD is required. Set it in .env or export NPM_PASSWORD=...');
process.exit(1);
}
function log(msg, type = 'info') {
const icons = { success: '✅', error: '❌', warning: '⚠️', info: '📋' };
console.log(`${icons[type] || '📋'} ${msg}`);
}
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');
await page.waitForSelector('input[type="email"], input[name="email"], input[placeholder*="email" i]', { timeout: 10000 });
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);
const passwordInput = await page.$('input[type="password"]');
if (passwordInput) await passwordInput.fill(NPM_PASSWORD);
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();
else await page.keyboard.press('Enter');
await page.waitForTimeout(3000);
const url = page.url();
if (url.includes('login') && !url.includes('proxy')) {
const body = await page.textContent('body').catch(() => '');
if (!body.includes('Proxy Hosts') && !body.includes('dashboard')) {
log('Login may have failed still on login page', 'warning');
await page.screenshot({ path: join(PROJECT_ROOT, 'npmplus-login-check.png') }).catch(() => {});
}
}
log('Logged in', 'success');
return true;
}
async function requestCertForHostId(page, hostId) {
log(`Requesting cert for host ID ${hostId}...`);
try {
// NPM edit URL: open edit form directly by host ID
await page.goto(`${NPM_URL}/#/proxy-hosts/edit/${hostId}`, { waitUntil: 'networkidle' });
await page.waitForTimeout(3000);
// SSL tab: NPM edit form usually has Details | SSL | Advanced
await page.getByText('SSL').first().click();
await page.waitForTimeout(1500);
// "Request a new SSL Certificate" / "Get a new certificate"
const requestBtn = page.getByRole('button', { name: /request.*(new )?ssl certificate|get.*certificate/i }).or(
page.locator('button:has-text("Request"), button:has-text("Get a new"), a:has-text("Request")').first()
);
await requestBtn.click();
await page.waitForTimeout(1500);
// DNS Challenge: click option/label for "DNS Challenge" or "Use a DNS Challenge"
const dnsOption = page.getByText(/use a dns challenge|dns challenge/i).first();
await dnsOption.click();
await page.waitForTimeout(800);
// DNS Provider: Cloudflare (dropdown or first Cloudflare option)
const cloudflareOption = page.getByText('Cloudflare').first();
await cloudflareOption.click();
await page.waitForTimeout(800);
// Credential: usually first in dropdown if only one Cloudflare credential
const credSelect = page.locator('select').filter({ has: page.locator('option') }).first();
if (await credSelect.count() > 0) {
await credSelect.selectOption({ index: 1 });
}
await page.waitForTimeout(500);
// Email for Let's Encrypt
const emailField = page.locator('input[type="email"], input[name*="email" i]').first();
await emailField.fill(LETSENCRYPT_EMAIL);
// Agree to ToS
const agree = page.locator('input[type="checkbox"]').filter({ has: page.locator('..') }).first();
if (await agree.count() > 0 && !(await agree.isChecked())) await agree.check();
await pause(page, `Ready to submit cert request for host ${hostId}`);
// Submit
const submitBtn = page.getByRole('button', { name: /save|submit|request|get certificate/i }).first();
await submitBtn.click();
await page.waitForTimeout(5000);
// Check for success or error
const body = await page.textContent('body').catch(() => '');
if (body.includes('error') && body.toLowerCase().includes('internal')) {
log(`Request for host ${hostId} may have failed (Internal Error). Check NPM UI.`, 'warning');
return false;
}
log(`Submitted cert request for host ${hostId}`, 'success');
return true;
} catch (e) {
log(`Error for host ${hostId}: ${e.message}`, 'error');
await page.screenshot({ path: join(PROJECT_ROOT, `npmplus-cert-error-${hostId}.png`) }).catch(() => {});
return false;
}
}
async function main() {
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('🔒 NPMplus Request 7 certificates (DNS Cloudflare)');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('');
log(`NPM: ${NPM_URL}`);
log(`Host IDs: ${HOST_IDS_WITHOUT_CERT.join(', ')}`);
console.log('');
const browser = await chromium.launch({ headless: HEADLESS, ignoreHTTPSErrors: true });
const context = await browser.newContext({ ignoreHTTPSErrors: true });
const page = await context.newPage();
try {
const ok = await login(page);
if (!ok) {
log('Login failed', 'error');
process.exit(1);
}
let success = 0;
for (const hostId of HOST_IDS_WITHOUT_CERT) {
const ok = await requestCertForHostId(page, hostId);
if (ok) success++;
await page.waitForTimeout(2000);
}
console.log('');
log(`Done. Submitted requests for ${success}/${HOST_IDS_WITHOUT_CERT.length} hosts. Check NPM SSL Certificates and Hosts to confirm.`, 'success');
log('Run: ./scripts/list-npmplus-proxy-hosts-cert-status.sh', 'info');
} finally {
await browser.close();
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});