easy-devops 0.1.2 → 0.1.3
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/index.js +3 -0
- package/cli/managers/ssl-manager.js +25 -9
- package/cli/menus/dashboard.js +3 -3
- package/cli/menus/update.js +178 -0
- package/package.json +1 -1
package/cli/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import sslMenu from './menus/ssl.js';
|
|
|
11
11
|
import domainsMenu from './menus/domains.js';
|
|
12
12
|
import dashboardMenu from './menus/dashboard.js';
|
|
13
13
|
import settingsMenu from './menus/settings.js';
|
|
14
|
+
import updateMenu from './menus/update.js';
|
|
14
15
|
|
|
15
16
|
const require = createRequire(import.meta.url);
|
|
16
17
|
const { version } = require('../package.json');
|
|
@@ -44,6 +45,7 @@ async function showMainMenu() {
|
|
|
44
45
|
'🔗 Domain Manager',
|
|
45
46
|
'🎛️ Open Dashboard',
|
|
46
47
|
'⚙️ Settings',
|
|
48
|
+
'🔄 Check for Updates',
|
|
47
49
|
'✖ Exit',
|
|
48
50
|
],
|
|
49
51
|
}]);
|
|
@@ -58,6 +60,7 @@ async function dispatch(choice) {
|
|
|
58
60
|
case '🔗 Domain Manager': await domainsMenu(); break;
|
|
59
61
|
case '🎛️ Open Dashboard': await dashboardMenu(); break;
|
|
60
62
|
case '⚙️ Settings': await settingsMenu(); break;
|
|
63
|
+
case '🔄 Check for Updates': await updateMenu(); break;
|
|
61
64
|
case '✖ Exit': process.exit(0);
|
|
62
65
|
}
|
|
63
66
|
}
|
|
@@ -276,18 +276,34 @@ async function installCertbot() {
|
|
|
276
276
|
console.log(chalk.yellow(' Chocolatey failed, trying direct download...\n'));
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
// 3. Direct download —
|
|
280
|
-
const
|
|
279
|
+
// 3. Direct download — try multiple sources in order
|
|
280
|
+
const INSTALLER_FILENAME = 'certbot-beta-installer-win_amd64_signed.exe';
|
|
281
|
+
const downloadSources = [
|
|
282
|
+
`https://github.com/certbot/certbot/releases/latest/download/${INSTALLER_FILENAME}`,
|
|
283
|
+
`https://dl.eff.org/${INSTALLER_FILENAME}`,
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
let downloaded = false;
|
|
287
|
+
for (const url of downloadSources) {
|
|
288
|
+
const label = new URL(url).hostname;
|
|
289
|
+
console.log(chalk.gray(`\n Downloading certbot installer from ${label} ...\n`));
|
|
290
|
+
|
|
291
|
+
const downloadResult = await run(
|
|
292
|
+
`$ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri '${url}' -OutFile "$env:TEMP\\certbot-installer.exe" -UseBasicParsing -TimeoutSec 120`,
|
|
293
|
+
{ timeout: 130000 },
|
|
294
|
+
);
|
|
281
295
|
|
|
282
|
-
|
|
296
|
+
if (downloadResult.success) {
|
|
297
|
+
downloaded = true;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
283
300
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
{ timeout: 130000 },
|
|
287
|
-
);
|
|
301
|
+
console.log(chalk.yellow(` Failed from ${label}: ${(downloadResult.stderr || downloadResult.stdout).split('\n')[0].trim()}`));
|
|
302
|
+
}
|
|
288
303
|
|
|
289
|
-
if (!
|
|
290
|
-
console.log(chalk.red(' Download failed
|
|
304
|
+
if (!downloaded) {
|
|
305
|
+
console.log(chalk.red('\n Download failed from all sources.'));
|
|
306
|
+
console.log(chalk.gray(' Install manually: https://certbot.eff.org/instructions?ws=other&os=windows\n'));
|
|
291
307
|
return { success: false };
|
|
292
308
|
}
|
|
293
309
|
|
package/cli/menus/dashboard.js
CHANGED
|
@@ -69,7 +69,7 @@ function isPidAlive(pid) {
|
|
|
69
69
|
|
|
70
70
|
// ─── Status ───────────────────────────────────────────────────────────────────
|
|
71
71
|
|
|
72
|
-
async function getDashboardStatus() {
|
|
72
|
+
export async function getDashboardStatus() {
|
|
73
73
|
const { dashboardPort } = loadConfig();
|
|
74
74
|
const storedPid = dbGet('dashboard-pid');
|
|
75
75
|
const storedPort = dbGet('dashboard-port') ?? dashboardPort;
|
|
@@ -91,7 +91,7 @@ async function getDashboardStatus() {
|
|
|
91
91
|
|
|
92
92
|
// ─── Start ────────────────────────────────────────────────────────────────────
|
|
93
93
|
|
|
94
|
-
async function startDashboard(port) {
|
|
94
|
+
export async function startDashboard(port) {
|
|
95
95
|
await fs.mkdir(path.dirname(LOG_PATH), { recursive: true });
|
|
96
96
|
|
|
97
97
|
// openSync gives a real fd immediately — required by spawn's stdio option
|
|
@@ -118,7 +118,7 @@ async function startDashboard(port) {
|
|
|
118
118
|
|
|
119
119
|
// ─── Stop ─────────────────────────────────────────────────────────────────────
|
|
120
120
|
|
|
121
|
-
async function stopDashboard(pid) {
|
|
121
|
+
export async function stopDashboard(pid) {
|
|
122
122
|
if (!pid) return { success: false };
|
|
123
123
|
try {
|
|
124
124
|
if (isWindows) {
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/menus/update.js
|
|
3
|
+
*
|
|
4
|
+
* Check for updates and upgrade easy-devops in place.
|
|
5
|
+
*
|
|
6
|
+
* Flow:
|
|
7
|
+
* 1. Fetch latest version from npm registry
|
|
8
|
+
* 2. If an update is available, offer to install it
|
|
9
|
+
* 3. Before installing: record dashboard running state in DB
|
|
10
|
+
* (key: 'update-pre-dashboard') so it survives a crash mid-update
|
|
11
|
+
* 4. Stop dashboard if it was running
|
|
12
|
+
* 5. Run: npm install -g easy-devops@<latest>
|
|
13
|
+
* 6. Re-start dashboard if it was running before
|
|
14
|
+
* 7. Clear the saved state key
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import chalk from 'chalk';
|
|
18
|
+
import inquirer from 'inquirer';
|
|
19
|
+
import ora from 'ora';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
import { createRequire } from 'module';
|
|
23
|
+
import { run } from '../../core/shell.js';
|
|
24
|
+
import { dbGet, dbSet } from '../../core/db.js';
|
|
25
|
+
import { loadConfig } from '../../core/config.js';
|
|
26
|
+
import { getDashboardStatus, startDashboard, stopDashboard } from './dashboard.js';
|
|
27
|
+
|
|
28
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const require = createRequire(import.meta.url);
|
|
30
|
+
const { version: currentVersion } = require('../../package.json');
|
|
31
|
+
|
|
32
|
+
// ─── Version helpers ──────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
async function fetchLatestVersion() {
|
|
35
|
+
const result = await run('npm view easy-devops version', { timeout: 20000 });
|
|
36
|
+
if (result.success && result.stdout.trim()) return result.stdout.trim();
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isNewer(latest, current) {
|
|
41
|
+
const parse = v => v.replace(/^v/, '').split('.').map(Number);
|
|
42
|
+
const [la, lb, lc] = parse(latest);
|
|
43
|
+
const [ca, cb, cc] = parse(current);
|
|
44
|
+
if (la !== ca) return la > ca;
|
|
45
|
+
if (lb !== cb) return lb > cb;
|
|
46
|
+
return lc > cc;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Recover interrupted update ───────────────────────────────────────────────
|
|
50
|
+
// If a previous update crashed after stopping the dashboard but before restarting
|
|
51
|
+
// it, the key 'update-pre-dashboard' is still set. Offer to restart it.
|
|
52
|
+
|
|
53
|
+
async function recoverIfNeeded() {
|
|
54
|
+
const saved = dbGet('update-pre-dashboard');
|
|
55
|
+
if (!saved?.wasRunning) return;
|
|
56
|
+
|
|
57
|
+
console.log(chalk.yellow('\n A previous update left the dashboard stopped.'));
|
|
58
|
+
const { restart } = await inquirer.prompt([{
|
|
59
|
+
type: 'confirm',
|
|
60
|
+
name: 'restart',
|
|
61
|
+
message: 'Restart the dashboard now?',
|
|
62
|
+
default: true,
|
|
63
|
+
}]);
|
|
64
|
+
|
|
65
|
+
if (restart) {
|
|
66
|
+
const port = saved.port || loadConfig().dashboardPort;
|
|
67
|
+
const sp = ora(`Starting dashboard on port ${port}...`).start();
|
|
68
|
+
const res = await startDashboard(port);
|
|
69
|
+
res.success
|
|
70
|
+
? sp.succeed(`Dashboard restarted on port ${port}`)
|
|
71
|
+
: sp.fail('Could not restart dashboard — use the Dashboard menu');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
dbSet('update-pre-dashboard', null);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── Perform update ───────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
async function performUpdate(latestVersion) {
|
|
80
|
+
// Step 1 — snapshot dashboard state and persist it
|
|
81
|
+
const status = await getDashboardStatus();
|
|
82
|
+
dbSet('update-pre-dashboard', {
|
|
83
|
+
wasRunning: status.running,
|
|
84
|
+
pid: status.pid,
|
|
85
|
+
port: status.port,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Step 2 — stop dashboard if running
|
|
89
|
+
if (status.running) {
|
|
90
|
+
const sp = ora('Stopping dashboard...').start();
|
|
91
|
+
await stopDashboard(status.pid);
|
|
92
|
+
sp.succeed('Dashboard stopped');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Step 3 — install new version
|
|
96
|
+
const sp = ora(`Installing easy-devops@${latestVersion}...`).start();
|
|
97
|
+
const result = await run(`npm install -g easy-devops@${latestVersion}`, { timeout: 120000 });
|
|
98
|
+
|
|
99
|
+
if (!result.success) {
|
|
100
|
+
sp.fail('Update failed');
|
|
101
|
+
console.log(chalk.red('\n' + (result.stderr || result.stdout) + '\n'));
|
|
102
|
+
// Leave 'update-pre-dashboard' in DB so recovery runs on next launch
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
sp.succeed(`Updated to v${latestVersion}`);
|
|
107
|
+
|
|
108
|
+
// Step 4 — restart dashboard if it was running before
|
|
109
|
+
const saved = dbGet('update-pre-dashboard');
|
|
110
|
+
dbSet('update-pre-dashboard', null);
|
|
111
|
+
|
|
112
|
+
if (saved?.wasRunning) {
|
|
113
|
+
const port = saved.port || loadConfig().dashboardPort;
|
|
114
|
+
const restSp = ora(`Restarting dashboard on port ${port}...`).start();
|
|
115
|
+
const res = await startDashboard(port);
|
|
116
|
+
res.success
|
|
117
|
+
? restSp.succeed(`Dashboard restarted on port ${port}`)
|
|
118
|
+
: restSp.fail('Could not restart dashboard — use the Dashboard menu');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ─── Menu ─────────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
export default async function updateMenu() {
|
|
127
|
+
// Recover from a crashed previous update first
|
|
128
|
+
await recoverIfNeeded();
|
|
129
|
+
|
|
130
|
+
const spinner = ora('Checking for updates...').start();
|
|
131
|
+
const latestVersion = await fetchLatestVersion();
|
|
132
|
+
spinner.stop();
|
|
133
|
+
|
|
134
|
+
console.log(chalk.bold('\n Check for Updates'));
|
|
135
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
136
|
+
console.log(` Current version : ${chalk.cyan('v' + currentVersion)}`);
|
|
137
|
+
|
|
138
|
+
if (!latestVersion) {
|
|
139
|
+
console.log(chalk.yellow(' Could not reach npm registry. Check your internet connection.\n'));
|
|
140
|
+
await inquirer.prompt([{ type: 'input', name: '_', message: 'Press Enter to go back...' }]);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const updateAvailable = isNewer(latestVersion, currentVersion);
|
|
145
|
+
|
|
146
|
+
if (updateAvailable) {
|
|
147
|
+
console.log(` Latest version : ${chalk.green('v' + latestVersion)} ${chalk.yellow('← update available')}\n`);
|
|
148
|
+
} else {
|
|
149
|
+
console.log(` Latest version : ${chalk.green('v' + latestVersion)} ${chalk.gray('✓ up to date')}\n`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const choices = updateAvailable
|
|
153
|
+
? [`Update to v${latestVersion}`, new inquirer.Separator(), '← Back']
|
|
154
|
+
: ['← Back'];
|
|
155
|
+
|
|
156
|
+
let choice;
|
|
157
|
+
try {
|
|
158
|
+
({ choice } = await inquirer.prompt([{
|
|
159
|
+
type: 'list',
|
|
160
|
+
name: 'choice',
|
|
161
|
+
message: 'Select an option:',
|
|
162
|
+
choices,
|
|
163
|
+
}]));
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (err.name === 'ExitPromptError') return;
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (choice === `Update to v${latestVersion}`) {
|
|
170
|
+
const success = await performUpdate(latestVersion);
|
|
171
|
+
if (success) {
|
|
172
|
+
console.log(chalk.gray('\n Restart easy-devops to use the new version.\n'));
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
await inquirer.prompt([{ type: 'input', name: '_', message: 'Press Enter to continue...' }]);
|
|
176
|
+
} catch { /* ExitPromptError */ }
|
|
177
|
+
}
|
|
178
|
+
}
|
package/package.json
CHANGED