easy-devops 0.1.8 → 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.
@@ -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
- // Method 1: Invoke-WebRequest with TLS 1.2
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
- `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri '${safeUrl}' -OutFile '${safeDest}' -UseBasicParsing -TimeoutSec 120`,
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
- `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; (New-Object System.Net.WebClient).DownloadFile('${safeUrl}','${safeDest}')`,
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 --silent --show-error --max-time 120 -o '${safeDest}' '${safeUrl}'`,
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)
365
+ r = await run(
366
+ `certutil.exe -urlcache -split -f "${safeUrl}" "${safeDest}"`,
367
+ { timeout: 130000 },
368
+ );
369
+ if (r.success) return true;
370
+ if (showErrors && r.stderr && !lastDownloadError) lastDownloadError = `certutil: ${r.stderr}`;
371
+
372
+ // Method 7: PowerShell with DisableKeepAlive
349
373
  r = await run(
350
- `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $hc=[System.Net.Http.HttpClient]::new(); $hc.DefaultRequestHeaders.Add('User-Agent','Mozilla/5.0'); $hc.Timeout=[TimeSpan]::FromSeconds(120); $bytes=$hc.GetByteArrayAsync('${safeUrl}').GetAwaiter().GetResult(); [System.IO.File]::WriteAllBytes('${safeDest}',$bytes)`,
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)`,
351
375
  { timeout: 130000 },
352
376
  );
353
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`,
@@ -517,6 +572,9 @@ async function installCertbot() {
517
572
  // ── Method 7: Direct download win-acme (ZIP - smaller, no installer) ──────────
518
573
  const WINACME_DEST = 'C:\\Program Files\\win-acme';
519
574
 
575
+ // Test network before attempting downloads
576
+ await testNetworkConnectivity();
577
+
520
578
  step('Downloading win-acme from GitHub ...');
521
579
  const winAcmeUrls = [
522
580
  'https://github.com/win-acme/win-acme/releases/latest/download/win-acme.zip',
@@ -528,7 +586,8 @@ async function installCertbot() {
528
586
  console.log(chalk.gray(` Downloading from ${hostname} ...`));
529
587
 
530
588
  const zipDest = `$env:TEMP\\win-acme.zip`;
531
- if (await downloadFile(url, zipDest)) {
589
+ lastDownloadError = '';
590
+ if (await downloadFile(url, zipDest, true)) {
532
591
  console.log(chalk.gray(' Extracting win-acme ...\n'));
533
592
  await run(`New-Item -ItemType Directory -Force -Path '${WINACME_DEST}'`);
534
593
  await run(`Expand-Archive -Path '${zipDest}' -DestinationPath '${WINACME_DEST}' -Force`);
@@ -541,6 +600,9 @@ async function installCertbot() {
541
600
  console.log(chalk.yellow(' Extraction succeeded but verification failed, trying next...\n'));
542
601
  } else {
543
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
+ }
544
606
  }
545
607
  }
546
608
 
@@ -556,7 +618,8 @@ async function installCertbot() {
556
618
  const hostname = new URL(url).hostname;
557
619
  step(`Downloading certbot installer from ${hostname} ...`);
558
620
 
559
- if (await downloadFile(url, INSTALLER_DEST)) {
621
+ lastDownloadError = '';
622
+ if (await downloadFile(url, INSTALLER_DEST, true)) {
560
623
  console.log(chalk.gray(' Running installer silently ...\n'));
561
624
  const ok = await runNsisInstaller(INSTALLER_DEST);
562
625
  await run(`Remove-Item -Force '${INSTALLER_DEST}' -ErrorAction SilentlyContinue`);
@@ -564,6 +627,9 @@ async function installCertbot() {
564
627
  console.log(chalk.yellow(' Installer ran but certbot not detected, trying next...\n'));
565
628
  } else {
566
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
+ }
567
633
  }
568
634
  }
569
635
 
@@ -580,10 +646,29 @@ async function installCertbot() {
580
646
  type: 'list',
581
647
  name: 'localChoice',
582
648
  message: 'What would you like to do?',
583
- choices: ['Specify local installer path', 'Cancel'],
649
+ choices: [
650
+ 'Open download page in browser',
651
+ 'Specify local installer path',
652
+ 'Cancel'
653
+ ],
584
654
  }]));
585
655
  } catch { return { success: false }; }
586
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
+
587
672
  if (localChoice === 'Specify local installer path') {
588
673
  let localPath;
589
674
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-devops",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "A unified DevOps management tool with CLI and web dashboard for managing Nginx, SSL certificates, and Node.js on Linux and Windows servers.",
5
5
  "keywords": [
6
6
  "devops",