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.
- package/cli/managers/ssl-manager.js +76 -58
- package/cli/menus/update.js +6 -3
- package/core/db.js +11 -0
- package/package.json +1 -1
|
@@ -247,91 +247,109 @@ 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
|
+
}
|
|
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
|
|
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
|
-
//
|
|
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 =
|
|
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 (
|
|
317
|
-
console.log(chalk.
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
{ timeout:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
//
|
|
333
|
-
|
|
334
|
-
return { success:
|
|
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 ───────────────────────────────────────────────────────────
|
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