easy-devops 0.1.5 → 0.1.6

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.
@@ -249,7 +249,6 @@ async function installCertbot() {
249
249
 
250
250
  // ── Shared helpers ────────────────────────────────────────────────────────────
251
251
 
252
- // Check multiple possible certbot locations — some methods install to different paths
253
252
  async function verifyCertbot() {
254
253
  const whereResult = await run('where.exe certbot 2>$null');
255
254
  if (whereResult.success && whereResult.stdout.trim()) return true;
@@ -265,43 +264,105 @@ async function installCertbot() {
265
264
  return false;
266
265
  }
267
266
 
268
- // Download a file trying Invoke-WebRequest (TLS 1.2 forced) then curl.exe
267
+ // Try every possible download mechanism one may work even when others fail
269
268
  const hasCurl = (await run('where.exe curl.exe 2>$null')).success;
269
+
270
270
  async function downloadFile(url, dest) {
271
- const iwr = await run(
272
- `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri '${url}' -OutFile '${dest}' -UseBasicParsing -TimeoutSec 120`,
273
- { timeout: 130000 },
271
+ // 1. Invoke-WebRequest with TLS 1.2 forced
272
+ let r = await run(
273
+ `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri '${url}' -OutFile '${dest}' -UseBasicParsing -TimeoutSec 60`,
274
+ { timeout: 70000 },
275
+ );
276
+ if (r.success) return true;
277
+
278
+ // 2. System.Net.WebClient (different .NET code path)
279
+ r = await run(
280
+ `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; (New-Object System.Net.WebClient).DownloadFile('${url}','${dest}')`,
281
+ { timeout: 70000 },
282
+ );
283
+ if (r.success) return true;
284
+
285
+ // 3. BITS (Background Intelligent Transfer Service)
286
+ r = await run(
287
+ `Start-BitsTransfer -Source '${url}' -Destination '${dest}' -ErrorAction Stop`,
288
+ { timeout: 70000 },
274
289
  );
275
- if (iwr.success) return true;
290
+ if (r.success) return true;
291
+
292
+ // 4. curl.exe (independent TLS stack)
276
293
  if (hasCurl) {
277
- const curl = await run(`curl.exe -L --silent --show-error --max-time 120 -o '${dest}' '${url}'`, { timeout: 130000 });
278
- if (curl.success) return true;
294
+ r = await run(
295
+ `curl.exe -L --ssl-no-revoke --silent --show-error --max-time 60 -o '${dest}' '${url}'`,
296
+ { timeout: 70000 },
297
+ );
298
+ if (r.success) return true;
279
299
  }
300
+
301
+ // 5. System.Net.Http.HttpClient (most modern .NET stack)
302
+ r = await run(
303
+ `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $hc=[System.Net.Http.HttpClient]::new(); $hc.DefaultRequestHeaders.Add('User-Agent','Mozilla/5.0'); $bytes=$hc.GetByteArrayAsync('${url}').GetAwaiter().GetResult(); [System.IO.File]::WriteAllBytes('${dest}',$bytes)`,
304
+ { timeout: 70000 },
305
+ );
306
+ if (r.success) return true;
307
+
280
308
  return false;
281
309
  }
282
310
 
283
- // ── Method 1: winget ──────────────────────────────────────────────────────────
284
- const wingetCheck = await run('where.exe winget 2>$null');
285
- if (wingetCheck.success && wingetCheck.stdout.trim()) {
286
- console.log(chalk.gray('\n [1/4] Trying winget ...\n'));
311
+ async function runNsisInstaller(exePath) {
312
+ await run(
313
+ `$p = Start-Process -FilePath '${exePath}' -ArgumentList '/S' -PassThru -Wait; $p.ExitCode`,
314
+ { timeout: 120000 },
315
+ );
316
+ await new Promise(res => setTimeout(res, 4000));
317
+ return verifyCertbot();
318
+ }
319
+
320
+ let methodNum = 0;
321
+ function step(label) {
322
+ methodNum++;
323
+ console.log(chalk.gray(`\n [${methodNum}] ${label}\n`));
324
+ }
325
+
326
+ // ── Method 1: pip / pip3 (PyPI CDN — completely different from GitHub/EFF) ────
327
+ for (const pip of ['pip', 'pip3']) {
328
+ const check = await run(`where.exe ${pip} 2>$null`);
329
+ if (check.success && check.stdout.trim()) {
330
+ step(`Trying ${pip} install certbot ...`);
331
+ const exitCode = await runLive(`${pip} install certbot`, { timeout: 180000 });
332
+ if (exitCode === 0 || await verifyCertbot()) return { success: true };
333
+ console.log(chalk.yellow(` ${pip} did not install certbot, trying next...\n`));
334
+ break;
335
+ }
336
+ }
337
+
338
+ // ── Method 2: winget ──────────────────────────────────────────────────────────
339
+ if ((await run('where.exe winget 2>$null')).success) {
340
+ step('Trying winget ...');
287
341
  const exitCode = await runLive(
288
342
  'winget install -e --id EFF.Certbot --accept-package-agreements --accept-source-agreements',
289
343
  { timeout: 180000 },
290
344
  );
291
345
  if (exitCode === 0 || await verifyCertbot()) return { success: true };
292
- console.log(chalk.yellow(' winget did not install certbot, trying next method...\n'));
346
+ console.log(chalk.yellow(' winget did not install certbot, trying next...\n'));
293
347
  }
294
348
 
295
- // ── Method 2: Chocolatey ──────────────────────────────────────────────────────
296
- const chocoCheck = await run('where.exe choco 2>$null');
297
- if (chocoCheck.success && chocoCheck.stdout.trim()) {
298
- console.log(chalk.gray('\n [2/4] Trying Chocolatey ...\n'));
349
+ // ── Method 3: Chocolatey ──────────────────────────────────────────────────────
350
+ if ((await run('where.exe choco 2>$null')).success) {
351
+ step('Trying Chocolatey ...');
299
352
  const exitCode = await runLive('choco install certbot -y', { timeout: 180000 });
300
353
  if (exitCode === 0 || await verifyCertbot()) return { success: true };
301
- console.log(chalk.yellow(' Chocolatey did not install certbot, trying next method...\n'));
354
+ console.log(chalk.yellow(' Chocolatey did not install certbot, trying next...\n'));
355
+ }
356
+
357
+ // ── Method 4: Scoop ───────────────────────────────────────────────────────────
358
+ if ((await run('where.exe scoop 2>$null')).success) {
359
+ step('Trying Scoop ...');
360
+ const exitCode = await runLive('scoop install certbot', { timeout: 180000 });
361
+ if (exitCode === 0 || await verifyCertbot()) return { success: true };
362
+ console.log(chalk.yellow(' Scoop did not install certbot, trying next...\n'));
302
363
  }
303
364
 
304
- // ── Method 3: Official NSIS installer (GitHub dl.eff.org) ──────────────────
365
+ // ── Method 5: Direct installer download (multiple sources × multiple methods)
305
366
  const INSTALLER_FILENAME = 'certbot-beta-installer-win_amd64_signed.exe';
306
367
  const INSTALLER_DEST = `$env:TEMP\\${INSTALLER_FILENAME}`;
307
368
  const downloadSources = [
@@ -309,46 +370,58 @@ async function installCertbot() {
309
370
  `https://dl.eff.org/${INSTALLER_FILENAME}`,
310
371
  ];
311
372
 
312
- let downloaded = false;
313
373
  for (const url of downloadSources) {
314
374
  const label = new URL(url).hostname;
315
- console.log(chalk.gray(`\n [3/4] Downloading certbot installer from ${label} ...\n`));
316
- if (await downloadFile(url, INSTALLER_DEST)) { downloaded = true; break; }
317
- console.log(chalk.yellow(` Failed from ${label}`));
375
+ step(`Downloading installer from ${label} ...`);
376
+ if (await downloadFile(url, INSTALLER_DEST)) {
377
+ console.log(chalk.gray(' Running installer silently ...\n'));
378
+ const ok = await runNsisInstaller(INSTALLER_DEST);
379
+ await run(`Remove-Item -Force '${INSTALLER_DEST}' -ErrorAction SilentlyContinue`);
380
+ if (ok) return { success: true };
381
+ console.log(chalk.yellow(' Installer ran but certbot not detected, trying next...\n'));
382
+ } else {
383
+ console.log(chalk.yellow(` Could not download from ${label}`));
384
+ }
318
385
  }
319
386
 
320
- if (downloaded) {
321
- console.log(chalk.gray(' Running installer silently ...\n'));
322
- // Use Start-Process -PassThru -Wait so PowerShell waits for the NSIS process to fully exit
323
- await run(
324
- `$p = Start-Process -FilePath "${INSTALLER_DEST}" -ArgumentList '/S' -PassThru -Wait; $p.ExitCode`,
325
- { timeout: 120000 },
326
- );
327
- await run(`Remove-Item -Force "${INSTALLER_DEST}" -ErrorAction SilentlyContinue`);
328
- // Allow a few seconds for the installer to finish writing files
329
- await new Promise(r => setTimeout(r, 4000));
330
- if (await verifyCertbot()) return { success: true };
331
- console.log(chalk.yellow(' Installer ran but certbot not found, trying pip...\n'));
332
- }
387
+ // ── Method 6: Local file (user manually copies the installer to the server) ───
388
+ console.log(chalk.yellow('\n All automatic methods failed.'));
389
+ console.log(chalk.gray(' If you have the certbot installer on this machine, enter its path below.'));
390
+ console.log(chalk.gray(` (Download it on another PC: https://certbot.eff.org/instructions?ws=other&os=windows)\n`));
333
391
 
334
- // ── Method 4: pip (Python must be installed) ──────────────────────────────────
335
- const pipCheck = await run('where.exe pip 2>$null');
336
- if (pipCheck.success && pipCheck.stdout.trim()) {
337
- console.log(chalk.gray('\n [4/4] Trying pip install certbot ...\n'));
338
- const exitCode = await runLive('pip install certbot', { timeout: 180000 });
339
- if (exitCode === 0 || await verifyCertbot()) return { success: true };
340
- } else {
341
- // try pip3 as well
342
- const pip3Check = await run('where.exe pip3 2>$null');
343
- if (pip3Check.success && pip3Check.stdout.trim()) {
344
- console.log(chalk.gray('\n [4/4] Trying pip3 install certbot ...\n'));
345
- const exitCode = await runLive('pip3 install certbot', { timeout: 180000 });
346
- if (exitCode === 0 || await verifyCertbot()) return { success: true };
392
+ let localChoice;
393
+ try {
394
+ ({ localChoice } = await inquirer.prompt([{
395
+ type: 'list',
396
+ name: 'localChoice',
397
+ message: 'What would you like to do?',
398
+ choices: ['Specify local installer path', 'Cancel'],
399
+ }]));
400
+ } catch { return { success: false }; }
401
+
402
+ if (localChoice === 'Specify local installer path') {
403
+ let localPath;
404
+ try {
405
+ ({ localPath } = await inquirer.prompt([{
406
+ type: 'input',
407
+ name: 'localPath',
408
+ message: 'Full path to certbot installer (.exe):',
409
+ validate: v => v.trim().length > 0 || 'Required',
410
+ }]));
411
+ } catch { return { success: false }; }
412
+
413
+ const exists = await run(`Test-Path '${localPath.trim()}'`);
414
+ if (exists.stdout.trim().toLowerCase() !== 'true') {
415
+ console.log(chalk.red(` File not found: ${localPath}\n`));
416
+ return { success: false };
347
417
  }
418
+
419
+ step('Running local installer silently ...');
420
+ const ok = await runNsisInstaller(localPath.trim());
421
+ if (ok) return { success: true };
422
+ console.log(chalk.red(' Installer ran but certbot was not detected.\n'));
348
423
  }
349
424
 
350
- // All methods exhausted
351
- console.log(chalk.gray('\n Manual install: https://certbot.eff.org/instructions?ws=other&os=windows\n'));
352
425
  return { success: false };
353
426
  }
354
427
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-devops",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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",