easy-devops 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/managers/ssl-manager.js +117 -19
- package/package.json +1 -1
|
@@ -310,51 +310,119 @@ async function installCertbot() {
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
const hasCurl = (await run('where.exe curl.exe 2>$null')).success;
|
|
313
|
+
let lastDownloadError = '';
|
|
313
314
|
|
|
314
|
-
async function downloadFile(url, dest) {
|
|
315
|
+
async function downloadFile(url, dest, showErrors = false) {
|
|
315
316
|
const safeUrl = url.replace(/'/g, "''");
|
|
316
317
|
const safeDest = dest.replace(/'/g, "''");
|
|
317
318
|
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
// Enable all TLS versions (1.0, 1.1, 1.2, 1.3) for maximum compatibility
|
|
320
|
+
const tlsSetup = `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13`;
|
|
321
|
+
|
|
322
|
+
// Helper to safely run a command and catch EPERM errors
|
|
323
|
+
const safeRun = async (cmd, opts) => {
|
|
324
|
+
try {
|
|
325
|
+
return await run(cmd, opts);
|
|
326
|
+
} catch (err) {
|
|
327
|
+
// Re-throw EPERM so caller can handle Windows Defender blocks
|
|
328
|
+
if (err.code === 'EPERM' || err.message?.includes('EPERM')) {
|
|
329
|
+
throw err;
|
|
330
|
+
}
|
|
331
|
+
return { success: false, stdout: '', stderr: err.message, exitCode: null };
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Method 1: Invoke-WebRequest with all TLS versions
|
|
336
|
+
let r = await safeRun(
|
|
337
|
+
`${tlsSetup}; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri '${safeUrl}' -OutFile '${safeDest}' -UseBasicParsing -TimeoutSec 120`,
|
|
321
338
|
{ timeout: 130000 },
|
|
322
339
|
);
|
|
323
340
|
if (r.success) return true;
|
|
341
|
+
if (showErrors && r.stderr) lastDownloadError = `Invoke-WebRequest: ${r.stderr}`;
|
|
324
342
|
|
|
325
|
-
// Method 2: WebClient
|
|
326
|
-
r = await
|
|
327
|
-
|
|
343
|
+
// Method 2: WebClient with TLS
|
|
344
|
+
r = await safeRun(
|
|
345
|
+
`${tlsSetup}; (New-Object System.Net.WebClient).DownloadFile('${safeUrl}','${safeDest}')`,
|
|
328
346
|
{ timeout: 130000 },
|
|
329
347
|
);
|
|
330
348
|
if (r.success) return true;
|
|
349
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `WebClient: ${r.stderr}`;
|
|
331
350
|
|
|
332
|
-
// Method 3: BITS
|
|
333
|
-
r = await
|
|
351
|
+
// Method 3: BITS Transfer
|
|
352
|
+
r = await safeRun(
|
|
334
353
|
`Import-Module BitsTransfer -ErrorAction SilentlyContinue; Start-BitsTransfer -Source '${safeUrl}' -Destination '${safeDest}' -ErrorAction Stop`,
|
|
335
354
|
{ timeout: 130000 },
|
|
336
355
|
);
|
|
337
356
|
if (r.success) return true;
|
|
357
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `BITS: ${r.stderr}`;
|
|
338
358
|
|
|
339
|
-
// Method 4: curl.exe
|
|
359
|
+
// Method 4: curl.exe (works on Windows 10+)
|
|
340
360
|
if (hasCurl) {
|
|
341
|
-
r = await
|
|
342
|
-
`curl.exe -L --ssl-no-revoke --
|
|
361
|
+
r = await safeRun(
|
|
362
|
+
`curl.exe -L --ssl-no-revoke --max-time 120 -o '${safeDest}' '${safeUrl}'`,
|
|
343
363
|
{ timeout: 130000 },
|
|
344
364
|
);
|
|
345
365
|
if (r.success) return true;
|
|
366
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `curl: ${r.stderr}`;
|
|
346
367
|
}
|
|
347
368
|
|
|
348
|
-
// Method 5: HttpClient
|
|
349
|
-
r = await
|
|
350
|
-
|
|
369
|
+
// Method 5: HttpClient with custom handler
|
|
370
|
+
r = await safeRun(
|
|
371
|
+
`${tlsSetup}; $handler=[System.Net.Http.HttpClientHandler]::new(); $handler.ServerCertificateCustomValidationCallback={$true}; $handler.AllowAutoRedirect=$true; $hc=[System.Net.Http.HttpClient]::new($handler); $hc.DefaultRequestHeaders.Add('User-Agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64)'); $hc.Timeout=[TimeSpan]::FromSeconds(120); $bytes=$hc.GetByteArrayAsync('${safeUrl}').GetAwaiter().GetResult(); [System.IO.File]::WriteAllBytes('${safeDest}',$bytes)`,
|
|
372
|
+
{ timeout: 130000 },
|
|
373
|
+
);
|
|
374
|
+
if (r.success) return true;
|
|
375
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `HttpClient: ${r.stderr}`;
|
|
376
|
+
|
|
377
|
+
// Method 6: Certutil (built-in Windows tool)
|
|
378
|
+
r = await safeRun(
|
|
379
|
+
`certutil.exe -urlcache -split -f "${safeUrl}" "${safeDest}"`,
|
|
380
|
+
{ timeout: 130000 },
|
|
381
|
+
);
|
|
382
|
+
if (r.success) return true;
|
|
383
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `certutil: ${r.stderr}`;
|
|
384
|
+
|
|
385
|
+
// Method 7: PowerShell with DisableKeepAlive
|
|
386
|
+
r = await safeRun(
|
|
387
|
+
`${tlsSetup}; $req=[System.Net.WebRequest]::Create('${safeUrl}'); $req.Method='GET'; $req.KeepAlive=$false; $req.UserAgent='Mozilla/5.0'; $resp=$req.GetResponse(); $stream=$resp.GetResponseStream(); $reader=[System.IO.BinaryReader]::new($stream); $bytes=$reader.ReadBytes($resp.ContentLength); $reader.Close(); [System.IO.File]::WriteAllBytes('${safeDest}',$bytes)`,
|
|
351
388
|
{ timeout: 130000 },
|
|
352
389
|
);
|
|
353
390
|
if (r.success) return true;
|
|
391
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `WebRequest: ${r.stderr}`;
|
|
354
392
|
|
|
355
393
|
return false;
|
|
356
394
|
}
|
|
357
395
|
|
|
396
|
+
// Test network connectivity to common endpoints
|
|
397
|
+
async function testNetworkConnectivity() {
|
|
398
|
+
console.log(chalk.cyan('\n Testing network connectivity...'));
|
|
399
|
+
const tests = [
|
|
400
|
+
{ name: 'GitHub', url: 'https://github.com' },
|
|
401
|
+
{ name: 'Microsoft', url: 'https://microsoft.com' },
|
|
402
|
+
{ name: 'EFF', url: 'https://eff.org' },
|
|
403
|
+
];
|
|
404
|
+
const results = [];
|
|
405
|
+
|
|
406
|
+
for (const test of tests) {
|
|
407
|
+
const r = await run(`curl.exe -I --ssl-no-revoke --max-time 10 "${test.url}"`, { timeout: 15000 });
|
|
408
|
+
const success = r.success || r.stdout.includes('HTTP') || r.stdout.includes('200');
|
|
409
|
+
results.push({ name: test.name, success });
|
|
410
|
+
console.log(chalk.gray(` ${test.name}: ${success ? chalk.green('✓ Connected') : chalk.red('✗ Failed')}`));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const allFailed = results.every(r => !r.success);
|
|
414
|
+
if (allFailed) {
|
|
415
|
+
console.log(chalk.yellow('\n ⚠ All external connections failed.'));
|
|
416
|
+
console.log(chalk.gray(' This could indicate:'));
|
|
417
|
+
console.log(chalk.gray(' • Firewall blocking outbound connections'));
|
|
418
|
+
console.log(chalk.gray(' • Proxy server required'));
|
|
419
|
+
console.log(chalk.gray(' • DNS resolution issues'));
|
|
420
|
+
console.log(chalk.gray(' • Antivirus blocking downloads'));
|
|
421
|
+
}
|
|
422
|
+
console.log();
|
|
423
|
+
return results;
|
|
424
|
+
}
|
|
425
|
+
|
|
358
426
|
async function runNsisInstaller(exePath) {
|
|
359
427
|
await run(
|
|
360
428
|
`$p = Start-Process -FilePath '${exePath}' -ArgumentList '/S' -PassThru -Wait; $p.ExitCode`,
|
|
@@ -424,7 +492,7 @@ async function installCertbot() {
|
|
|
424
492
|
const appInstallerDest = '$env:TEMP\\AppInstaller.msixbundle';
|
|
425
493
|
|
|
426
494
|
console.log(chalk.gray('\n Downloading App Installer...'));
|
|
427
|
-
|
|
495
|
+
let downloaded = false; try { downloaded = await downloadFile(appInstallerUrl, appInstallerDest); } catch (err) { if (err.code === 'EPERM' || err.message?.includes('EPERM')) { console.log(chalk.red('\n ? Windows Defender blocked the download.')); console.log(chalk.gray(' Add an exclusion in Windows Defender or download manually.')); console.log(chalk.gray(' Download URL: https://aka.ms/getwinget\n')); } else { console.log(chalk.yellow('\n Download failed: ' + err.message + '\n')); } }
|
|
428
496
|
|
|
429
497
|
if (downloaded) {
|
|
430
498
|
console.log(chalk.gray(' Running App Installer...'));
|
|
@@ -517,6 +585,9 @@ async function installCertbot() {
|
|
|
517
585
|
// ── Method 7: Direct download win-acme (ZIP - smaller, no installer) ──────────
|
|
518
586
|
const WINACME_DEST = 'C:\\Program Files\\win-acme';
|
|
519
587
|
|
|
588
|
+
// Test network before attempting downloads
|
|
589
|
+
try { await testNetworkConnectivity(); } catch (err) { console.log(chalk.yellow('Network test failed: ' + err.message)); }
|
|
590
|
+
|
|
520
591
|
step('Downloading win-acme from GitHub ...');
|
|
521
592
|
const winAcmeUrls = [
|
|
522
593
|
'https://github.com/win-acme/win-acme/releases/latest/download/win-acme.zip',
|
|
@@ -528,7 +599,8 @@ async function installCertbot() {
|
|
|
528
599
|
console.log(chalk.gray(` Downloading from ${hostname} ...`));
|
|
529
600
|
|
|
530
601
|
const zipDest = `$env:TEMP\\win-acme.zip`;
|
|
531
|
-
|
|
602
|
+
lastDownloadError = '';
|
|
603
|
+
let dlOk = false; try { dlOk = await downloadFile(url, zipDest, true); } catch (err) { if (err.code === 'EPERM' || err.message?.includes('EPERM')) { console.log(chalk.red('Windows Defender blocked this download.')); console.log(chalk.gray('Use the manual installer option or add a Windows Defender exclusion.')); } } if (dlOk) {
|
|
532
604
|
console.log(chalk.gray(' Extracting win-acme ...\n'));
|
|
533
605
|
await run(`New-Item -ItemType Directory -Force -Path '${WINACME_DEST}'`);
|
|
534
606
|
await run(`Expand-Archive -Path '${zipDest}' -DestinationPath '${WINACME_DEST}' -Force`);
|
|
@@ -541,6 +613,9 @@ async function installCertbot() {
|
|
|
541
613
|
console.log(chalk.yellow(' Extraction succeeded but verification failed, trying next...\n'));
|
|
542
614
|
} else {
|
|
543
615
|
console.log(chalk.yellow(` Could not download from ${hostname}`));
|
|
616
|
+
if (lastDownloadError) {
|
|
617
|
+
console.log(chalk.gray(` Error: ${lastDownloadError.substring(0, 200)}\n`));
|
|
618
|
+
}
|
|
544
619
|
}
|
|
545
620
|
}
|
|
546
621
|
|
|
@@ -556,7 +631,8 @@ async function installCertbot() {
|
|
|
556
631
|
const hostname = new URL(url).hostname;
|
|
557
632
|
step(`Downloading certbot installer from ${hostname} ...`);
|
|
558
633
|
|
|
559
|
-
|
|
634
|
+
lastDownloadError = '';
|
|
635
|
+
let dlOk = false; try { dlOk = await downloadFile(url, INSTALLER_DEST, true); } catch (err) { if (err.code === 'EPERM' || err.message?.includes('EPERM')) { console.log(chalk.red('Windows Defender blocked this download.')); console.log(chalk.gray('Use the manual installer option or add a Windows Defender exclusion.')); } } if (dlOk) {
|
|
560
636
|
console.log(chalk.gray(' Running installer silently ...\n'));
|
|
561
637
|
const ok = await runNsisInstaller(INSTALLER_DEST);
|
|
562
638
|
await run(`Remove-Item -Force '${INSTALLER_DEST}' -ErrorAction SilentlyContinue`);
|
|
@@ -564,6 +640,9 @@ async function installCertbot() {
|
|
|
564
640
|
console.log(chalk.yellow(' Installer ran but certbot not detected, trying next...\n'));
|
|
565
641
|
} else {
|
|
566
642
|
console.log(chalk.yellow(` Could not download from ${hostname}`));
|
|
643
|
+
if (lastDownloadError) {
|
|
644
|
+
console.log(chalk.gray(` Error: ${lastDownloadError.substring(0, 200)}\n`));
|
|
645
|
+
}
|
|
567
646
|
}
|
|
568
647
|
}
|
|
569
648
|
|
|
@@ -580,10 +659,29 @@ async function installCertbot() {
|
|
|
580
659
|
type: 'list',
|
|
581
660
|
name: 'localChoice',
|
|
582
661
|
message: 'What would you like to do?',
|
|
583
|
-
choices: [
|
|
662
|
+
choices: [
|
|
663
|
+
'Open download page in browser',
|
|
664
|
+
'Specify local installer path',
|
|
665
|
+
'Cancel'
|
|
666
|
+
],
|
|
584
667
|
}]));
|
|
585
668
|
} catch { return { success: false }; }
|
|
586
669
|
|
|
670
|
+
if (localChoice === 'Open download page in browser') {
|
|
671
|
+
console.log(chalk.cyan('\n Opening download pages in browser...'));
|
|
672
|
+
console.log(chalk.gray(' Download either win-acme.zip or certbot installer, then re-run this tool.\n'));
|
|
673
|
+
|
|
674
|
+
// Open win-acme releases
|
|
675
|
+
await run('Start-Process "https://github.com/win-acme/win-acme/releases/latest"');
|
|
676
|
+
// Also open EFF certbot page
|
|
677
|
+
await run('Start-Process "https://certbot.eff.org/instructions?ws=other&os=windows"');
|
|
678
|
+
|
|
679
|
+
console.log(chalk.green('✓ Browser opened. Download the installer, then run:'));
|
|
680
|
+
console.log(chalk.cyan(' easy-devops → SSL Manager → Install certbot → Specify local installer path\n'));
|
|
681
|
+
|
|
682
|
+
return { success: false };
|
|
683
|
+
}
|
|
684
|
+
|
|
587
685
|
if (localChoice === 'Specify local installer path') {
|
|
588
686
|
let localPath;
|
|
589
687
|
try {
|
package/package.json
CHANGED