easy-devops 0.1.3 → 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.
- package/cli/managers/nginx-manager.js +19 -4
- package/cli/managers/ssl-manager.js +76 -46
- package/cli/menus/update.js +6 -3
- package/core/db.js +11 -0
- package/package.json +1 -1
|
@@ -268,7 +268,7 @@ async function installNginx() {
|
|
|
268
268
|
let nginxVersion = FALLBACK_VERSION;
|
|
269
269
|
|
|
270
270
|
const fetchVersionResult = await run(
|
|
271
|
-
`try { $p=(Invoke-WebRequest -Uri 'https://nginx.org/en/download.html' -UseBasicParsing -TimeoutSec 15).Content; if($p -match 'nginx-(\\d+\\.\\d+\\.\\d+)\\.zip'){$Matches[1]}else{''} } catch { '' }`,
|
|
271
|
+
`[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; try { $p=(Invoke-WebRequest -Uri 'https://nginx.org/en/download.html' -UseBasicParsing -TimeoutSec 15).Content; if($p -match 'nginx-(\\d+\\.\\d+\\.\\d+)\\.zip'){$Matches[1]}else{''} } catch { '' }`,
|
|
272
272
|
{ timeout: 20000 },
|
|
273
273
|
);
|
|
274
274
|
const fetched = (fetchVersionResult.stdout || '').trim();
|
|
@@ -276,15 +276,30 @@ async function installNginx() {
|
|
|
276
276
|
|
|
277
277
|
const { nginxDir } = loadConfig();
|
|
278
278
|
const zipUrl = `https://nginx.org/download/nginx-${nginxVersion}.zip`;
|
|
279
|
+
const zipDest = `$env:TEMP\\nginx-${nginxVersion}.zip`;
|
|
279
280
|
|
|
280
281
|
spinner.text = `Downloading nginx ${nginxVersion}…`;
|
|
281
282
|
|
|
282
|
-
|
|
283
|
-
|
|
283
|
+
// Try Invoke-WebRequest with TLS 1.2 forced, fall back to curl.exe
|
|
284
|
+
let downloadOk = false;
|
|
285
|
+
let downloadResult = await run(
|
|
286
|
+
`[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri '${zipUrl}' -OutFile "${zipDest}" -UseBasicParsing -TimeoutSec 120`,
|
|
284
287
|
{ timeout: 130000 },
|
|
285
288
|
);
|
|
289
|
+
if (downloadResult.success) {
|
|
290
|
+
downloadOk = true;
|
|
291
|
+
} else {
|
|
292
|
+
const hasCurl = (await run('where.exe curl.exe 2>$null')).success;
|
|
293
|
+
if (hasCurl) {
|
|
294
|
+
downloadResult = await run(
|
|
295
|
+
`curl.exe -L --silent --show-error --max-time 120 -o "${zipDest}" "${zipUrl}"`,
|
|
296
|
+
{ timeout: 130000 },
|
|
297
|
+
);
|
|
298
|
+
if (downloadResult.success) downloadOk = true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
286
301
|
|
|
287
|
-
if (!
|
|
302
|
+
if (!downloadOk) {
|
|
288
303
|
spinner.fail('Download failed');
|
|
289
304
|
console.log(chalk.red(downloadResult.stderr || downloadResult.stdout));
|
|
290
305
|
console.log(chalk.gray(`\n Download nginx manually from: https://nginx.org/en/download.html\n`));
|
|
@@ -247,37 +247,63 @@ async function installCertbot() {
|
|
|
247
247
|
return { success: exitCode === 0 };
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
// ──
|
|
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
|
+
}
|
|
251
267
|
|
|
252
|
-
//
|
|
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
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── Method 1: winget ──────────────────────────────────────────────────────────
|
|
253
284
|
const wingetCheck = await run('where.exe winget 2>$null');
|
|
254
|
-
if (wingetCheck.success && wingetCheck.stdout.trim()
|
|
255
|
-
console.log(chalk.gray('\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
|
-
|
|
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
|
|
295
|
+
// ── Method 2: Chocolatey ──────────────────────────────────────────────────────
|
|
268
296
|
const chocoCheck = await run('where.exe choco 2>$null');
|
|
269
|
-
if (chocoCheck.success && chocoCheck.stdout.trim()
|
|
270
|
-
console.log(chalk.gray('\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
|
-
|
|
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
|
|
304
|
+
// ── Method 3: Official NSIS installer (GitHub → dl.eff.org) ──────────────────
|
|
280
305
|
const INSTALLER_FILENAME = 'certbot-beta-installer-win_amd64_signed.exe';
|
|
306
|
+
const INSTALLER_DEST = `$env:TEMP\\${INSTALLER_FILENAME}`;
|
|
281
307
|
const downloadSources = [
|
|
282
308
|
`https://github.com/certbot/certbot/releases/latest/download/${INSTALLER_FILENAME}`,
|
|
283
309
|
`https://dl.eff.org/${INSTALLER_FILENAME}`,
|
|
@@ -286,40 +312,44 @@ async function installCertbot() {
|
|
|
286
312
|
let downloaded = false;
|
|
287
313
|
for (const url of downloadSources) {
|
|
288
314
|
const label = new URL(url).hostname;
|
|
289
|
-
console.log(chalk.gray(`\n Downloading certbot installer from ${label} ...\n`));
|
|
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}`));
|
|
318
|
+
}
|
|
290
319
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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 },
|
|
294
326
|
);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
console.log(chalk.yellow(` Failed from ${label}: ${(downloadResult.stderr || downloadResult.stdout).split('\n')[0].trim()}`));
|
|
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'));
|
|
302
332
|
}
|
|
303
333
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
+
}
|
|
308
348
|
}
|
|
309
349
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
`Start-Process -FilePath "$env:TEMP\\certbot-installer.exe" -ArgumentList '/S' -Wait -NoNewWindow`,
|
|
314
|
-
{ timeout: 120000 },
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
// Cleanup installer
|
|
318
|
-
await run(`Remove-Item -Force "$env:TEMP\\certbot-installer.exe" -ErrorAction SilentlyContinue`);
|
|
319
|
-
|
|
320
|
-
// Verify
|
|
321
|
-
const check = await run(`Test-Path "${CERTBOT_WIN_EXE}"`);
|
|
322
|
-
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 };
|
|
323
353
|
}
|
|
324
354
|
|
|
325
355
|
// ─── showSslManager ───────────────────────────────────────────────────────────
|
package/cli/menus/update.js
CHANGED
|
@@ -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 —
|
|
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
|
|
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