easy-devops 0.1.7 → 0.1.9
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 +187 -17
- package/package.json +1 -1
|
@@ -310,51 +310,106 @@ 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
|
+
// 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
|
+
// Method 1: Invoke-WebRequest with all TLS versions
|
|
319
323
|
let r = await run(
|
|
320
|
-
|
|
324
|
+
`${tlsSetup}; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri '${safeUrl}' -OutFile '${safeDest}' -UseBasicParsing -TimeoutSec 120`,
|
|
321
325
|
{ timeout: 130000 },
|
|
322
326
|
);
|
|
323
327
|
if (r.success) return true;
|
|
328
|
+
if (showErrors && r.stderr) lastDownloadError = `Invoke-WebRequest: ${r.stderr}`;
|
|
324
329
|
|
|
325
|
-
// Method 2: WebClient
|
|
330
|
+
// Method 2: WebClient with TLS
|
|
326
331
|
r = await run(
|
|
327
|
-
|
|
332
|
+
`${tlsSetup}; (New-Object System.Net.WebClient).DownloadFile('${safeUrl}','${safeDest}')`,
|
|
328
333
|
{ timeout: 130000 },
|
|
329
334
|
);
|
|
330
335
|
if (r.success) return true;
|
|
336
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `WebClient: ${r.stderr}`;
|
|
331
337
|
|
|
332
|
-
// Method 3: BITS
|
|
338
|
+
// Method 3: BITS Transfer
|
|
333
339
|
r = await run(
|
|
334
340
|
`Import-Module BitsTransfer -ErrorAction SilentlyContinue; Start-BitsTransfer -Source '${safeUrl}' -Destination '${safeDest}' -ErrorAction Stop`,
|
|
335
341
|
{ timeout: 130000 },
|
|
336
342
|
);
|
|
337
343
|
if (r.success) return true;
|
|
344
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `BITS: ${r.stderr}`;
|
|
338
345
|
|
|
339
|
-
// Method 4: curl.exe
|
|
346
|
+
// Method 4: curl.exe (works on Windows 10+)
|
|
340
347
|
if (hasCurl) {
|
|
341
348
|
r = await run(
|
|
342
|
-
`curl.exe -L --ssl-no-revoke --
|
|
349
|
+
`curl.exe -L --ssl-no-revoke --max-time 120 -o '${safeDest}' '${safeUrl}'`,
|
|
343
350
|
{ timeout: 130000 },
|
|
344
351
|
);
|
|
345
352
|
if (r.success) return true;
|
|
353
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `curl: ${r.stderr}`;
|
|
346
354
|
}
|
|
347
355
|
|
|
348
|
-
// Method 5: HttpClient
|
|
356
|
+
// Method 5: HttpClient with custom handler
|
|
357
|
+
r = await run(
|
|
358
|
+
`${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)`,
|
|
359
|
+
{ timeout: 130000 },
|
|
360
|
+
);
|
|
361
|
+
if (r.success) return true;
|
|
362
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `HttpClient: ${r.stderr}`;
|
|
363
|
+
|
|
364
|
+
// Method 6: Certutil (built-in Windows tool)
|
|
349
365
|
r = await run(
|
|
350
|
-
`
|
|
366
|
+
`certutil.exe -urlcache -split -f "${safeUrl}" "${safeDest}"`,
|
|
351
367
|
{ timeout: 130000 },
|
|
352
368
|
);
|
|
353
369
|
if (r.success) return true;
|
|
370
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `certutil: ${r.stderr}`;
|
|
371
|
+
|
|
372
|
+
// Method 7: PowerShell with DisableKeepAlive
|
|
373
|
+
r = await run(
|
|
374
|
+
`${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)`,
|
|
375
|
+
{ timeout: 130000 },
|
|
376
|
+
);
|
|
377
|
+
if (r.success) return true;
|
|
378
|
+
if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `WebRequest: ${r.stderr}`;
|
|
354
379
|
|
|
355
380
|
return false;
|
|
356
381
|
}
|
|
357
382
|
|
|
383
|
+
// Test network connectivity to common endpoints
|
|
384
|
+
async function testNetworkConnectivity() {
|
|
385
|
+
console.log(chalk.cyan('\n Testing network connectivity...'));
|
|
386
|
+
const tests = [
|
|
387
|
+
{ name: 'GitHub', url: 'https://github.com' },
|
|
388
|
+
{ name: 'Microsoft', url: 'https://microsoft.com' },
|
|
389
|
+
{ name: 'EFF', url: 'https://eff.org' },
|
|
390
|
+
];
|
|
391
|
+
const results = [];
|
|
392
|
+
|
|
393
|
+
for (const test of tests) {
|
|
394
|
+
const r = await run(`curl.exe -I --ssl-no-revoke --max-time 10 "${test.url}"`, { timeout: 15000 });
|
|
395
|
+
const success = r.success || r.stdout.includes('HTTP') || r.stdout.includes('200');
|
|
396
|
+
results.push({ name: test.name, success });
|
|
397
|
+
console.log(chalk.gray(` ${test.name}: ${success ? chalk.green('✓ Connected') : chalk.red('✗ Failed')}`));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const allFailed = results.every(r => !r.success);
|
|
401
|
+
if (allFailed) {
|
|
402
|
+
console.log(chalk.yellow('\n ⚠ All external connections failed.'));
|
|
403
|
+
console.log(chalk.gray(' This could indicate:'));
|
|
404
|
+
console.log(chalk.gray(' • Firewall blocking outbound connections'));
|
|
405
|
+
console.log(chalk.gray(' • Proxy server required'));
|
|
406
|
+
console.log(chalk.gray(' • DNS resolution issues'));
|
|
407
|
+
console.log(chalk.gray(' • Antivirus blocking downloads'));
|
|
408
|
+
}
|
|
409
|
+
console.log();
|
|
410
|
+
return results;
|
|
411
|
+
}
|
|
412
|
+
|
|
358
413
|
async function runNsisInstaller(exePath) {
|
|
359
414
|
await run(
|
|
360
415
|
`$p = Start-Process -FilePath '${exePath}' -ArgumentList '/S' -PassThru -Wait; $p.ExitCode`,
|
|
@@ -370,9 +425,93 @@ async function installCertbot() {
|
|
|
370
425
|
console.log(chalk.gray(`\n [${methodNum}] ${label}\n`));
|
|
371
426
|
}
|
|
372
427
|
|
|
428
|
+
// ── Check winget availability and offer to install if missing ─────────────────
|
|
429
|
+
let wingetAvailable = false;
|
|
430
|
+
const wingetCheck = await run('where.exe winget 2>$null');
|
|
431
|
+
wingetAvailable = wingetCheck.success && wingetCheck.stdout.trim();
|
|
432
|
+
|
|
433
|
+
if (!wingetAvailable) {
|
|
434
|
+
console.log(chalk.yellow('\n ⚠ winget is not installed on this system.'));
|
|
435
|
+
console.log(chalk.gray(' winget (Windows Package Manager) provides the easiest installation method.'));
|
|
436
|
+
|
|
437
|
+
let installWinget;
|
|
438
|
+
try {
|
|
439
|
+
({ installWinget } = await inquirer.prompt([{
|
|
440
|
+
type: 'confirm',
|
|
441
|
+
name: 'installWinget',
|
|
442
|
+
message: 'Would you like to install winget (App Installer) from Microsoft Store?',
|
|
443
|
+
default: true,
|
|
444
|
+
}]));
|
|
445
|
+
} catch { /* user cancelled */ }
|
|
446
|
+
|
|
447
|
+
if (installWinget) {
|
|
448
|
+
console.log(chalk.cyan('\n Opening Microsoft Store to install App Installer...'));
|
|
449
|
+
console.log(chalk.gray(' Please complete the installation, then run this command again.\n'));
|
|
450
|
+
|
|
451
|
+
// Try to open Microsoft Store
|
|
452
|
+
const storeOpened = await run(
|
|
453
|
+
'Start-Process "ms-windows-store://pdp/?ProductId=9NBLGGH4NNS1"',
|
|
454
|
+
{ timeout: 10000 }
|
|
455
|
+
).catch(() => ({ success: false }));
|
|
456
|
+
|
|
457
|
+
if (storeOpened.success) {
|
|
458
|
+
console.log(chalk.green(' Microsoft Store opened successfully.'));
|
|
459
|
+
console.log(chalk.gray(' After installing App Installer, winget will be available.\n'));
|
|
460
|
+
} else {
|
|
461
|
+
console.log(chalk.yellow(' Could not open Microsoft Store directly.'));
|
|
462
|
+
console.log(chalk.gray(' Please manually install "App Installer" from:'));
|
|
463
|
+
console.log(chalk.cyan(' https://apps.microsoft.com/store/detail/app-installer/9NBLGGH4NNS1\n'));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Optional: try direct download of App Installer
|
|
467
|
+
let tryDownload;
|
|
468
|
+
try {
|
|
469
|
+
({ tryDownload } = await inquirer.prompt([{
|
|
470
|
+
type: 'confirm',
|
|
471
|
+
name: 'tryDownload',
|
|
472
|
+
message: 'Would you like to try downloading App Installer directly?',
|
|
473
|
+
default: false,
|
|
474
|
+
}]));
|
|
475
|
+
} catch { tryDownload = false; }
|
|
476
|
+
|
|
477
|
+
if (tryDownload) {
|
|
478
|
+
const appInstallerUrl = 'https://aka.ms/getwinget';
|
|
479
|
+
const appInstallerDest = '$env:TEMP\\AppInstaller.msixbundle';
|
|
480
|
+
|
|
481
|
+
console.log(chalk.gray('\n Downloading App Installer...'));
|
|
482
|
+
const downloaded = await downloadFile(appInstallerUrl, appInstallerDest);
|
|
483
|
+
|
|
484
|
+
if (downloaded) {
|
|
485
|
+
console.log(chalk.gray(' Running App Installer...'));
|
|
486
|
+
await run(`Start-Process -FilePath '${appInstallerDest}' -Wait`, { timeout: 300000 });
|
|
487
|
+
|
|
488
|
+
// Re-check for winget
|
|
489
|
+
const recheck = await run('where.exe winget 2>$null');
|
|
490
|
+
if (recheck.success && recheck.stdout.trim()) {
|
|
491
|
+
console.log(chalk.green('\n ✓ winget installed successfully!\n'));
|
|
492
|
+
wingetAvailable = true;
|
|
493
|
+
} else {
|
|
494
|
+
console.log(chalk.yellow('\n App Installer ran but winget is not yet available.'));
|
|
495
|
+
console.log(chalk.gray(' You may need to restart your terminal or sign out/in.\n'));
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
console.log(chalk.yellow('\n Could not download App Installer automatically.\n'));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// If still no winget, continue with other methods
|
|
503
|
+
if (!wingetAvailable) {
|
|
504
|
+
console.log(chalk.gray(' Continuing with alternative installation methods...\n'));
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
console.log(chalk.gray(' Skipping winget installation. Using alternative methods...\n'));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
373
511
|
// ── Method 1: winget (win-acme - has better success rate) ─────────────────────
|
|
374
|
-
if (
|
|
512
|
+
if (wingetAvailable) {
|
|
375
513
|
step('Trying winget (win-acme) ...');
|
|
514
|
+
console.log(chalk.gray(' Running: winget install win-acme.win-acme\n'));
|
|
376
515
|
const exitCode = await runLive(
|
|
377
516
|
'winget install -e --id win-acme.win-acme --accept-package-agreements --accept-source-agreements',
|
|
378
517
|
{ timeout: 180000 },
|
|
@@ -382,8 +521,9 @@ async function installCertbot() {
|
|
|
382
521
|
}
|
|
383
522
|
|
|
384
523
|
// ── Method 2: winget (certbot EFF) ────────────────────────────────────────────
|
|
385
|
-
if (
|
|
524
|
+
if (wingetAvailable) {
|
|
386
525
|
step('Trying winget (EFF.Certbot) ...');
|
|
526
|
+
console.log(chalk.gray(' Running: winget install EFF.Certbot\n'));
|
|
387
527
|
const exitCode = await runLive(
|
|
388
528
|
'winget install -e --id EFF.Certbot --accept-package-agreements --accept-source-agreements',
|
|
389
529
|
{ timeout: 180000 },
|
|
@@ -432,6 +572,9 @@ async function installCertbot() {
|
|
|
432
572
|
// ── Method 7: Direct download win-acme (ZIP - smaller, no installer) ──────────
|
|
433
573
|
const WINACME_DEST = 'C:\\Program Files\\win-acme';
|
|
434
574
|
|
|
575
|
+
// Test network before attempting downloads
|
|
576
|
+
await testNetworkConnectivity();
|
|
577
|
+
|
|
435
578
|
step('Downloading win-acme from GitHub ...');
|
|
436
579
|
const winAcmeUrls = [
|
|
437
580
|
'https://github.com/win-acme/win-acme/releases/latest/download/win-acme.zip',
|
|
@@ -443,7 +586,8 @@ async function installCertbot() {
|
|
|
443
586
|
console.log(chalk.gray(` Downloading from ${hostname} ...`));
|
|
444
587
|
|
|
445
588
|
const zipDest = `$env:TEMP\\win-acme.zip`;
|
|
446
|
-
|
|
589
|
+
lastDownloadError = '';
|
|
590
|
+
if (await downloadFile(url, zipDest, true)) {
|
|
447
591
|
console.log(chalk.gray(' Extracting win-acme ...\n'));
|
|
448
592
|
await run(`New-Item -ItemType Directory -Force -Path '${WINACME_DEST}'`);
|
|
449
593
|
await run(`Expand-Archive -Path '${zipDest}' -DestinationPath '${WINACME_DEST}' -Force`);
|
|
@@ -456,6 +600,9 @@ async function installCertbot() {
|
|
|
456
600
|
console.log(chalk.yellow(' Extraction succeeded but verification failed, trying next...\n'));
|
|
457
601
|
} else {
|
|
458
602
|
console.log(chalk.yellow(` Could not download from ${hostname}`));
|
|
603
|
+
if (lastDownloadError) {
|
|
604
|
+
console.log(chalk.gray(` Error: ${lastDownloadError.substring(0, 200)}\n`));
|
|
605
|
+
}
|
|
459
606
|
}
|
|
460
607
|
}
|
|
461
608
|
|
|
@@ -471,7 +618,8 @@ async function installCertbot() {
|
|
|
471
618
|
const hostname = new URL(url).hostname;
|
|
472
619
|
step(`Downloading certbot installer from ${hostname} ...`);
|
|
473
620
|
|
|
474
|
-
|
|
621
|
+
lastDownloadError = '';
|
|
622
|
+
if (await downloadFile(url, INSTALLER_DEST, true)) {
|
|
475
623
|
console.log(chalk.gray(' Running installer silently ...\n'));
|
|
476
624
|
const ok = await runNsisInstaller(INSTALLER_DEST);
|
|
477
625
|
await run(`Remove-Item -Force '${INSTALLER_DEST}' -ErrorAction SilentlyContinue`);
|
|
@@ -479,14 +627,17 @@ async function installCertbot() {
|
|
|
479
627
|
console.log(chalk.yellow(' Installer ran but certbot not detected, trying next...\n'));
|
|
480
628
|
} else {
|
|
481
629
|
console.log(chalk.yellow(` Could not download from ${hostname}`));
|
|
630
|
+
if (lastDownloadError) {
|
|
631
|
+
console.log(chalk.gray(` Error: ${lastDownloadError.substring(0, 200)}\n`));
|
|
632
|
+
}
|
|
482
633
|
}
|
|
483
634
|
}
|
|
484
635
|
|
|
485
636
|
// ── Method 9: Manual installer path ───────────────────────────────────────────
|
|
486
637
|
console.log(chalk.yellow('\n All automatic methods failed.'));
|
|
487
638
|
console.log(chalk.gray(' You can manually download one of these on another PC:'));
|
|
488
|
-
console.log(chalk.gray('
|
|
489
|
-
console.log(chalk.gray('
|
|
639
|
+
console.log(chalk.gray(' • certbot: https://certbot.eff.org/instructions?ws=other&os=windows'));
|
|
640
|
+
console.log(chalk.gray(' • win-acme: https://github.com/win-acme/win-acme/releases'));
|
|
490
641
|
console.log(chalk.gray(' Then transfer to this server and use "Specify local installer" below.\n'));
|
|
491
642
|
|
|
492
643
|
let localChoice;
|
|
@@ -495,10 +646,29 @@ async function installCertbot() {
|
|
|
495
646
|
type: 'list',
|
|
496
647
|
name: 'localChoice',
|
|
497
648
|
message: 'What would you like to do?',
|
|
498
|
-
choices: [
|
|
649
|
+
choices: [
|
|
650
|
+
'Open download page in browser',
|
|
651
|
+
'Specify local installer path',
|
|
652
|
+
'Cancel'
|
|
653
|
+
],
|
|
499
654
|
}]));
|
|
500
655
|
} catch { return { success: false }; }
|
|
501
656
|
|
|
657
|
+
if (localChoice === 'Open download page in browser') {
|
|
658
|
+
console.log(chalk.cyan('\n Opening download pages in browser...'));
|
|
659
|
+
console.log(chalk.gray(' Download either win-acme.zip or certbot installer, then re-run this tool.\n'));
|
|
660
|
+
|
|
661
|
+
// Open win-acme releases
|
|
662
|
+
await run('Start-Process "https://github.com/win-acme/win-acme/releases/latest"');
|
|
663
|
+
// Also open EFF certbot page
|
|
664
|
+
await run('Start-Process "https://certbot.eff.org/instructions?ws=other&os=windows"');
|
|
665
|
+
|
|
666
|
+
console.log(chalk.green('✓ Browser opened. Download the installer, then run:'));
|
|
667
|
+
console.log(chalk.cyan(' easy-devops → SSL Manager → Install certbot → Specify local installer path\n'));
|
|
668
|
+
|
|
669
|
+
return { success: false };
|
|
670
|
+
}
|
|
671
|
+
|
|
502
672
|
if (localChoice === 'Specify local installer path') {
|
|
503
673
|
let localPath;
|
|
504
674
|
try {
|
package/package.json
CHANGED