easy-devops 0.1.4 → 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.
- package/cli/managers/ssl-manager.js +151 -60
- package/cli/menus/update.js +6 -3
- package/core/db.js +11 -0
- package/package.json +1 -1
|
@@ -247,91 +247,182 @@ async function installCertbot() {
|
|
|
247
247
|
return { success: exitCode === 0 };
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
// ──
|
|
250
|
+
// ── Shared helpers ────────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
async function verifyCertbot() {
|
|
253
|
+
const whereResult = await run('where.exe certbot 2>$null');
|
|
254
|
+
if (whereResult.success && whereResult.stdout.trim()) return true;
|
|
255
|
+
const paths = [
|
|
256
|
+
CERTBOT_WIN_EXE,
|
|
257
|
+
'C:\\Program Files (x86)\\Certbot\\bin\\certbot.exe',
|
|
258
|
+
'C:\\Certbot\\bin\\certbot.exe',
|
|
259
|
+
];
|
|
260
|
+
for (const p of paths) {
|
|
261
|
+
const r = await run(`Test-Path '${p}'`);
|
|
262
|
+
if (r.stdout.trim().toLowerCase() === 'true') return true;
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Try every possible download mechanism — one may work even when others fail
|
|
268
|
+
const hasCurl = (await run('where.exe curl.exe 2>$null')).success;
|
|
269
|
+
|
|
270
|
+
async function downloadFile(url, dest) {
|
|
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 },
|
|
289
|
+
);
|
|
290
|
+
if (r.success) return true;
|
|
291
|
+
|
|
292
|
+
// 4. curl.exe (independent TLS stack)
|
|
293
|
+
if (hasCurl) {
|
|
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;
|
|
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
|
+
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
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
|
+
}
|
|
251
319
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
console.log(chalk.gray(
|
|
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 ...');
|
|
256
341
|
const exitCode = await runLive(
|
|
257
342
|
'winget install -e --id EFF.Certbot --accept-package-agreements --accept-source-agreements',
|
|
258
343
|
{ timeout: 180000 },
|
|
259
344
|
);
|
|
260
|
-
if (exitCode === 0) {
|
|
261
|
-
|
|
262
|
-
return { success: check.stdout.trim().toLowerCase() === 'true' };
|
|
263
|
-
}
|
|
264
|
-
console.log(chalk.yellow(' winget failed, trying Chocolatey...\n'));
|
|
345
|
+
if (exitCode === 0 || await verifyCertbot()) return { success: true };
|
|
346
|
+
console.log(chalk.yellow(' winget did not install certbot, trying next...\n'));
|
|
265
347
|
}
|
|
266
348
|
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
console.log(chalk.gray('\n Installing via Chocolatey ...\n'));
|
|
349
|
+
// ── Method 3: Chocolatey ──────────────────────────────────────────────────────
|
|
350
|
+
if ((await run('where.exe choco 2>$null')).success) {
|
|
351
|
+
step('Trying Chocolatey ...');
|
|
271
352
|
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'));
|
|
353
|
+
if (exitCode === 0 || await verifyCertbot()) return { success: true };
|
|
354
|
+
console.log(chalk.yellow(' Chocolatey did not install certbot, trying next...\n'));
|
|
277
355
|
}
|
|
278
356
|
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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'));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ── Method 5: Direct installer download (multiple sources × multiple methods) ─
|
|
283
366
|
const INSTALLER_FILENAME = 'certbot-beta-installer-win_amd64_signed.exe';
|
|
284
|
-
const INSTALLER_DEST =
|
|
367
|
+
const INSTALLER_DEST = `$env:TEMP\\${INSTALLER_FILENAME}`;
|
|
285
368
|
const downloadSources = [
|
|
286
369
|
`https://github.com/certbot/certbot/releases/latest/download/${INSTALLER_FILENAME}`,
|
|
287
370
|
`https://dl.eff.org/${INSTALLER_FILENAME}`,
|
|
288
371
|
];
|
|
289
372
|
|
|
290
|
-
const hasCurl = (await run('where.exe curl.exe 2>$null')).success;
|
|
291
|
-
|
|
292
|
-
let downloaded = false;
|
|
293
373
|
for (const url of downloadSources) {
|
|
294
374
|
const label = new URL(url).hostname;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
`
|
|
300
|
-
{
|
|
301
|
-
|
|
302
|
-
|
|
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; }
|
|
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}`));
|
|
311
384
|
}
|
|
312
|
-
|
|
313
|
-
console.log(chalk.yellow(` Failed from ${label}`));
|
|
314
385
|
}
|
|
315
386
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
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`));
|
|
321
391
|
|
|
322
|
-
|
|
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 }; }
|
|
323
412
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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 };
|
|
417
|
+
}
|
|
328
418
|
|
|
329
|
-
|
|
330
|
-
|
|
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'));
|
|
423
|
+
}
|
|
331
424
|
|
|
332
|
-
|
|
333
|
-
const check = await run(`Test-Path "${CERTBOT_WIN_EXE}"`);
|
|
334
|
-
return { success: check.stdout.trim().toLowerCase() === 'true' };
|
|
425
|
+
return { success: false };
|
|
335
426
|
}
|
|
336
427
|
|
|
337
428
|
// ─── 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