easy-devops 0.1.4 → 0.1.5

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.
@@ -247,91 +247,109 @@ async function installCertbot() {
247
247
  return { success: exitCode === 0 };
248
248
  }
249
249
 
250
- // ── Windows: winget → Chocolatey → direct EFF installer ──────────────────────
250
+ // ── Shared helpers ────────────────────────────────────────────────────────────
251
+
252
+ // Check multiple possible certbot locations — some methods install to different paths
253
+ async function verifyCertbot() {
254
+ const whereResult = await run('where.exe certbot 2>$null');
255
+ if (whereResult.success && whereResult.stdout.trim()) return true;
256
+ const paths = [
257
+ CERTBOT_WIN_EXE,
258
+ 'C:\\Program Files (x86)\\Certbot\\bin\\certbot.exe',
259
+ 'C:\\Certbot\\bin\\certbot.exe',
260
+ ];
261
+ for (const p of paths) {
262
+ const r = await run(`Test-Path '${p}'`);
263
+ if (r.stdout.trim().toLowerCase() === 'true') return true;
264
+ }
265
+ return false;
266
+ }
267
+
268
+ // Download a file trying Invoke-WebRequest (TLS 1.2 forced) then curl.exe
269
+ const hasCurl = (await run('where.exe curl.exe 2>$null')).success;
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 },
274
+ );
275
+ if (iwr.success) return true;
276
+ 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;
279
+ }
280
+ return false;
281
+ }
251
282
 
252
- // 1. Try winget (Windows 10/11 desktop; not on Windows Server by default)
283
+ // ── Method 1: winget ──────────────────────────────────────────────────────────
253
284
  const wingetCheck = await run('where.exe winget 2>$null');
254
- if (wingetCheck.success && wingetCheck.stdout.trim().length > 0) {
255
- console.log(chalk.gray('\n Installing via winget (EFF.Certbot) ...\n'));
285
+ if (wingetCheck.success && wingetCheck.stdout.trim()) {
286
+ console.log(chalk.gray('\n [1/4] Trying winget ...\n'));
256
287
  const exitCode = await runLive(
257
288
  'winget install -e --id EFF.Certbot --accept-package-agreements --accept-source-agreements',
258
289
  { timeout: 180000 },
259
290
  );
260
- if (exitCode === 0) {
261
- const check = await run(`Test-Path "${CERTBOT_WIN_EXE}"`);
262
- return { success: check.stdout.trim().toLowerCase() === 'true' };
263
- }
264
- console.log(chalk.yellow(' winget failed, trying Chocolatey...\n'));
291
+ if (exitCode === 0 || await verifyCertbot()) return { success: true };
292
+ console.log(chalk.yellow(' winget did not install certbot, trying next method...\n'));
265
293
  }
266
294
 
267
- // 2. Try Chocolatey (common on Windows Server)
295
+ // ── Method 2: Chocolatey ──────────────────────────────────────────────────────
268
296
  const chocoCheck = await run('where.exe choco 2>$null');
269
- if (chocoCheck.success && chocoCheck.stdout.trim().length > 0) {
270
- console.log(chalk.gray('\n Installing via Chocolatey ...\n'));
297
+ if (chocoCheck.success && chocoCheck.stdout.trim()) {
298
+ console.log(chalk.gray('\n [2/4] Trying Chocolatey ...\n'));
271
299
  const exitCode = await runLive('choco install certbot -y', { timeout: 180000 });
272
- if (exitCode === 0) {
273
- const check = await run(`Test-Path "${CERTBOT_WIN_EXE}"`);
274
- return { success: check.stdout.trim().toLowerCase() === 'true' };
275
- }
276
- console.log(chalk.yellow(' Chocolatey failed, trying direct download...\n'));
300
+ if (exitCode === 0 || await verifyCertbot()) return { success: true };
301
+ console.log(chalk.yellow(' Chocolatey did not install certbot, trying next method...\n'));
277
302
  }
278
303
 
279
- // 3. Direct download try multiple sources × multiple download methods
280
- // PowerShell 5.1 on Windows Server defaults to TLS 1.0; GitHub requires TLS 1.2+.
281
- // Force TLS 1.2 before every Invoke-WebRequest call.
282
- // Also try curl.exe (built into Windows Server 2019+) as a second method.
304
+ // ── Method 3: Official NSIS installer (GitHub dl.eff.org) ──────────────────
283
305
  const INSTALLER_FILENAME = 'certbot-beta-installer-win_amd64_signed.exe';
284
- const INSTALLER_DEST = '$env:TEMP\\certbot-installer.exe';
306
+ const INSTALLER_DEST = `$env:TEMP\\${INSTALLER_FILENAME}`;
285
307
  const downloadSources = [
286
308
  `https://github.com/certbot/certbot/releases/latest/download/${INSTALLER_FILENAME}`,
287
309
  `https://dl.eff.org/${INSTALLER_FILENAME}`,
288
310
  ];
289
311
 
290
- const hasCurl = (await run('where.exe curl.exe 2>$null')).success;
291
-
292
312
  let downloaded = false;
293
313
  for (const url of downloadSources) {
294
314
  const label = new URL(url).hostname;
295
- console.log(chalk.gray(`\n Downloading certbot installer from ${label} ...\n`));
296
-
297
- // Method A: Invoke-WebRequest with TLS 1.2 forced
298
- const iwr = await run(
299
- `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri '${url}' -OutFile "${INSTALLER_DEST}" -UseBasicParsing -TimeoutSec 120`,
300
- { timeout: 130000 },
301
- );
302
- if (iwr.success) { downloaded = true; break; }
303
-
304
- // Method B: curl.exe (handles TLS independently of PowerShell/.NET settings)
305
- if (hasCurl) {
306
- const curlResult = await run(
307
- `curl.exe -L --silent --show-error --max-time 120 -o "${INSTALLER_DEST}" "${url}"`,
308
- { timeout: 130000 },
309
- );
310
- if (curlResult.success) { downloaded = true; break; }
311
- }
312
-
315
+ console.log(chalk.gray(`\n [3/4] Downloading certbot installer from ${label} ...\n`));
316
+ if (await downloadFile(url, INSTALLER_DEST)) { downloaded = true; break; }
313
317
  console.log(chalk.yellow(` Failed from ${label}`));
314
318
  }
315
319
 
316
- if (!downloaded) {
317
- console.log(chalk.red('\n Download failed from all sources.'));
318
- console.log(chalk.gray(' Install manually: https://certbot.eff.org/instructions?ws=other&os=windows\n'));
319
- return { success: false };
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'));
320
332
  }
321
333
 
322
- console.log(chalk.gray(' Running installer silently ...\n'));
323
-
324
- await run(
325
- `Start-Process -FilePath "$env:TEMP\\certbot-installer.exe" -ArgumentList '/S' -Wait -NoNewWindow`,
326
- { timeout: 120000 },
327
- );
328
-
329
- // Cleanup installer
330
- await run(`Remove-Item -Force "$env:TEMP\\certbot-installer.exe" -ErrorAction SilentlyContinue`);
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 };
347
+ }
348
+ }
331
349
 
332
- // Verify
333
- const check = await run(`Test-Path "${CERTBOT_WIN_EXE}"`);
334
- return { success: check.stdout.trim().toLowerCase() === 'true' };
350
+ // All methods exhausted
351
+ console.log(chalk.gray('\n Manual install: https://certbot.eff.org/instructions?ws=other&os=windows\n'));
352
+ return { success: false };
335
353
  }
336
354
 
337
355
  // ─── showSslManager ───────────────────────────────────────────────────────────
@@ -21,7 +21,7 @@ import path from 'path';
21
21
  import { fileURLToPath } from 'url';
22
22
  import { createRequire } from 'module';
23
23
  import { run } from '../../core/shell.js';
24
- import { dbGet, dbSet } from '../../core/db.js';
24
+ import { dbGet, dbSet, closeDb } from '../../core/db.js';
25
25
  import { loadConfig } from '../../core/config.js';
26
26
  import { getDashboardStatus, startDashboard, stopDashboard } from './dashboard.js';
27
27
 
@@ -92,7 +92,10 @@ async function performUpdate(latestVersion) {
92
92
  sp.succeed('Dashboard stopped');
93
93
  }
94
94
 
95
- // Step 3 — install new version
95
+ // Step 3 — close the SQLite connection so npm can rename the db file (EBUSY on Windows)
96
+ closeDb();
97
+
98
+ // Step 4 — install new version
96
99
  const sp = ora(`Installing easy-devops@${latestVersion}...`).start();
97
100
  const result = await run(`npm install -g easy-devops@${latestVersion}`, { timeout: 120000 });
98
101
 
@@ -105,7 +108,7 @@ async function performUpdate(latestVersion) {
105
108
 
106
109
  sp.succeed(`Updated to v${latestVersion}`);
107
110
 
108
- // Step 4 — restart dashboard if it was running before
111
+ // Step 5 — restart dashboard if it was running before
109
112
  const saved = dbGet('update-pre-dashboard');
110
113
  dbSet('update-pre-dashboard', null);
111
114
 
package/core/db.js CHANGED
@@ -28,3 +28,14 @@ export { db };
28
28
  export const dbGet = (key) => db.get(key);
29
29
  export const dbSet = (key, value) => db.set(key, value);
30
30
  export const dbDelete = (key) => db.delete(key);
31
+
32
+ /**
33
+ * Closes the underlying SQLite connection.
34
+ * Call this before any operation that needs to rename or replace the db file
35
+ * (e.g. npm install -g), otherwise the open file handle causes EBUSY on Windows.
36
+ */
37
+ export function closeDb() {
38
+ try {
39
+ db.driver?.db?.close?.();
40
+ } catch { /* ignore */ }
41
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-devops",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
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",