easy-devops 0.2.1 → 0.2.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/README.md CHANGED
@@ -21,6 +21,7 @@ A unified DevOps management tool with interactive CLI and web dashboard for mana
21
21
 
22
22
  - **Node.js 18+** (with npm)
23
23
  - **Linux** (Debian/Ubuntu) or **Windows**
24
+ - ⚠️ **Windows users: PowerShell must be run as Administrator** (required for installing packages via winget, managing services, and SSL certificates)
24
25
  - Optional: Nginx, Certbot, nvm (installed separately or via the tool)
25
26
 
26
27
  ## Installation
@@ -49,6 +50,8 @@ wget -qO- https://raw.githubusercontent.com/omar00050/Easy-DevOps/main/install.s
49
50
 
50
51
  #### Windows (PowerShell)
51
52
 
53
+ > ⚠️ **Important:** Run PowerShell **as Administrator**. Right-click PowerShell → "Run as Administrator".
54
+
52
55
  ```powershell
53
56
  Invoke-WebRequest -Uri "https://raw.githubusercontent.com/omar00050/Easy-DevOps/main/install.ps1" -OutFile "install.ps1"; ./install.ps1
54
57
  ```
@@ -175,6 +178,20 @@ Manage Let's Encrypt SSL certificates using Certbot.
175
178
 
176
179
  > **Note:** Renewing a certificate temporarily stops Nginx to free port 80, then restarts it automatically.
177
180
 
181
+ #### Windows Package Manager (winget)
182
+
183
+ Easy DevOps uses **winget** (Windows Package Manager) to install certbot and other packages on Windows. If winget is not installed, Easy DevOps will automatically prompt to install it using the [asheroto/winget-install](https://github.com/asheroto/winget-install) script.
184
+
185
+ **Installing winget manually:**
186
+
187
+ If you prefer to install winget separately, you can:
188
+
189
+ 1. **Install from Microsoft Store:** Search for "App Installer" in the Microsoft Store
190
+ 2. **Use the winget-install script:** [https://github.com/asheroto/winget-install](https://github.com/asheroto/winget-install)
191
+ 3. **Official Microsoft documentation:** [https://learn.microsoft.com/en-us/windows/package-manager/winget/](https://learn.microsoft.com/en-us/windows/package-manager/winget/)
192
+
193
+ > **Note:** The embedded `winget-install.ps1` script in this project is sourced from [asheroto/winget-install](https://github.com/asheroto/winget-install) — a community-maintained, reliable installer for winget on Windows Server and systems without the Microsoft Store.
194
+
178
195
  ---
179
196
 
180
197
  ### Web Dashboard
@@ -187,7 +204,15 @@ Start the web dashboard:
187
204
  npm run dashboard
188
205
  ```
189
206
 
190
- Access at `http://localhost:3000` (or configured port).
207
+ Access at `http://localhost:6443` (or configured port).
208
+
209
+ #### First-Time Login
210
+
211
+ Default credentials:
212
+ - **Username:** `admin`
213
+ - **Password:** Set in Settings menu or check your configuration
214
+
215
+ > **Tip:** From the Dashboard menu, select "How to use" for a quick guide on getting started.
191
216
 
192
217
  #### Dashboard Pages
193
218
 
@@ -245,7 +270,8 @@ easy-devops/
245
270
  │ ├── config.js # Configuration loader
246
271
  │ ├── db.js # SQLite database (good.db)
247
272
  │ ├── detector.js # System environment detection
248
- └── shell.js # Cross-platform shell executor
273
+ ├── shell.js # Cross-platform shell executor
274
+ │ └── nginx-conf-generator.js # Nginx config file generator
249
275
  ├── dashboard/
250
276
  │ ├── server.js # Express + Socket.io server
251
277
  │ ├── routes/ # API endpoints
@@ -255,7 +281,10 @@ easy-devops/
255
281
  ├── data/
256
282
  │ └── easy-devops.sqlite
257
283
  └── lib/
258
- └── installer/ # Bootstrap scripts
284
+ └── installer/
285
+ ├── install.ps1 # Windows bootstrap installer
286
+ ├── install.sh # Linux/macOS bootstrap installer
287
+ └── winget-install.ps1 # Windows Package Manager installer
259
288
  ```
260
289
 
261
290
  ---
@@ -139,6 +139,26 @@ function openBrowser(url) {
139
139
  run(cmd).catch(() => {});
140
140
  }
141
141
 
142
+ // ─── Usage Info ──────────────────────────────────────────────────────────────
143
+
144
+ function showUsageInfo() {
145
+ console.log(chalk.cyan('\n How to use the Dashboard:'));
146
+ console.log(chalk.gray(' ─'.repeat(40)));
147
+ console.log(' 1. Start the dashboard from this menu');
148
+ console.log(' 2. Open it in your browser (or visit the URL shown)');
149
+ console.log(' 3. Log in with the admin password from config');
150
+ console.log(' 4. Use the dashboard to:');
151
+ console.log(chalk.gray(' • Manage domains → create nginx configs'));
152
+ console.log(chalk.gray(' • View SSL certificates'));
153
+ console.log(chalk.gray(' • Control nginx (start/stop/reload)'));
154
+ console.log(chalk.gray(' • Edit nginx configuration files'));
155
+ console.log();
156
+ console.log(chalk.yellow(' Default login:'));
157
+ console.log(chalk.gray(' Username: admin'));
158
+ console.log(chalk.gray(' Password: (check your config or set one)'));
159
+ console.log();
160
+ }
161
+
142
162
  // ─── Menu ─────────────────────────────────────────────────────────────────────
143
163
 
144
164
  export default async function dashboardMenu() {
@@ -160,8 +180,8 @@ export default async function dashboardMenu() {
160
180
  console.log();
161
181
 
162
182
  const choices = status.running
163
- ? ['Open in browser', 'Stop dashboard', new inquirer.Separator(), '← Back']
164
- : ['Start dashboard', new inquirer.Separator(), '← Back'];
183
+ ? ['Open in browser', 'Stop dashboard', 'How to use', new inquirer.Separator(), '← Back']
184
+ : ['Start dashboard', 'How to use', new inquirer.Separator(), '← Back'];
165
185
 
166
186
  let choice;
167
187
  try {
@@ -4,31 +4,37 @@
4
4
  * Check for updates and upgrade easy-devops in place.
5
5
  *
6
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
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. Close database connection
13
+ * 6. Spawn an external update script that runs npm install
14
+ * after this process exits (to avoid EBUSY on Windows)
15
+ * 7. This process exits; update script runs
16
+ * 8. On next launch, recoverIfNeeded restarts dashboard if needed
15
17
  */
16
18
 
17
19
  import chalk from 'chalk';
18
20
  import inquirer from 'inquirer';
19
21
  import ora from 'ora';
20
22
  import path from 'path';
23
+ import fs from 'fs';
21
24
  import { fileURLToPath } from 'url';
22
25
  import { createRequire } from 'module';
26
+ import { spawn } from 'child_process';
23
27
  import { run } from '../../core/shell.js';
24
- import { dbGet, dbSet, closeDb, initDb } from '../../core/db.js';
28
+ import { dbGet, dbSet, closeDb } from '../../core/db.js';
25
29
  import { loadConfig } from '../../core/config.js';
26
- import { getDashboardStatus, startDashboard, stopDashboard } from './dashboard.js';
30
+ import { getDashboardStatus, stopDashboard } from './dashboard.js';
27
31
 
28
32
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
29
- const require = createRequire(import.meta.url);
33
+ const require = createRequire(import.meta.url);
30
34
  const { version: currentVersion } = require('../../package.json');
31
35
 
36
+ const isWindows = process.platform === 'win32';
37
+
32
38
  // ─── Version helpers ──────────────────────────────────────────────────────────
33
39
 
34
40
  async function fetchLatestVersion() {
@@ -54,18 +60,20 @@ async function recoverIfNeeded() {
54
60
  const saved = dbGet('update-pre-dashboard');
55
61
  if (!saved?.wasRunning) return;
56
62
 
57
- console.log(chalk.yellow('\n A previous update left the dashboard stopped.'));
63
+ console.log(chalk.yellow('\n A previous update left the dashboard stopped.'));
58
64
  const { restart } = await inquirer.prompt([{
59
- type: 'confirm',
60
- name: 'restart',
65
+ type: 'confirm',
66
+ name: 'restart',
61
67
  message: 'Restart the dashboard now?',
62
68
  default: true,
63
69
  }]);
64
70
 
65
71
  if (restart) {
72
+ // Dynamic import to avoid circular dependency and ensure fresh state
73
+ const { startDashboard } = await import('./dashboard.js');
66
74
  const port = saved.port || loadConfig().dashboardPort;
67
- const sp = ora(`Starting dashboard on port ${port}...`).start();
68
- const res = await startDashboard(port);
75
+ const sp = ora(`Starting dashboard on port ${port}...`).start();
76
+ const res = await startDashboard(port);
69
77
  res.success
70
78
  ? sp.succeed(`Dashboard restarted on port ${port}`)
71
79
  : sp.fail('Could not restart dashboard — use the Dashboard menu');
@@ -76,54 +84,141 @@ async function recoverIfNeeded() {
76
84
 
77
85
  // ─── Perform update ───────────────────────────────────────────────────────────
78
86
 
87
+ /**
88
+ * Creates and spawns an external update script that runs after this process exits.
89
+ * This is necessary on Windows because better-sqlite3's native module stays locked
90
+ * as long as any Node.js process has the database open.
91
+ */
79
92
  async function performUpdate(latestVersion) {
80
93
  // Step 1 — snapshot dashboard state and persist it
81
94
  const status = await getDashboardStatus();
82
95
  dbSet('update-pre-dashboard', {
83
96
  wasRunning: status.running,
84
- pid: status.pid,
85
- port: status.port,
97
+ pid: status.pid,
98
+ port: status.port,
86
99
  });
87
100
 
88
- // Step 2 — stop dashboard if running
101
+ // Step 2 — stop dashboard if running (it has its own DB connection)
89
102
  if (status.running) {
90
103
  const sp = ora('Stopping dashboard...').start();
91
104
  await stopDashboard(status.pid);
92
105
  sp.succeed('Dashboard stopped');
106
+ // Give the dashboard process a moment to fully terminate
107
+ await new Promise(r => setTimeout(r, 2000));
93
108
  }
94
109
 
95
- // Step 3 — close the SQLite connection so npm can rename the db file (EBUSY on Windows)
110
+ // Step 3 — close this process's database connection
96
111
  closeDb();
97
112
 
98
- // Step 4 — install new version
99
- const sp = ora(`Installing easy-devops@${latestVersion}...`).start();
100
- const result = await run(`npm install -g easy-devops@${latestVersion}`, { timeout: 120000 });
113
+ // Step 4 — create an external update script and spawn it
114
+ // The script will run npm install after this process exits
115
+ const updateScriptPS = `
116
+ $ProgressPreference = 'SilentlyContinue'
117
+ Write-Host ""
118
+ Write-Host "Installing easy-devops@${latestVersion}..." -ForegroundColor Cyan
119
+ Write-Host ""
120
+ npm install -g easy-devops@${latestVersion}
121
+ if ($LASTEXITCODE -eq 0) {
122
+ Write-Host ""
123
+ Write-Host "Successfully updated to v${latestVersion}" -ForegroundColor Green
124
+ Write-Host "Run 'easy-devops' to start the new version." -ForegroundColor Gray
125
+ } else {
126
+ Write-Host ""
127
+ Write-Host "Update failed. Please try again or update manually:" -ForegroundColor Red
128
+ Write-Host " npm install -g easy-devops@${latestVersion}" -ForegroundColor Yellow
129
+ }
130
+ Write-Host ""
131
+ Write-Host "Press any key to close this window..." -ForegroundColor Gray
132
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
133
+ `;
101
134
 
102
- if (!result.success) {
103
- sp.fail('Update failed');
104
- console.log(chalk.red('\n' + (result.stderr || result.stdout) + '\n'));
105
- // Leave 'update-pre-dashboard' in DB so recovery runs on next launch
106
- return false;
107
- }
135
+ const updateScriptBash = `#!/bin/bash
136
+ echo ""
137
+ echo -e "\\033[36mInstalling easy-devops@${latestVersion}...\\033[0m"
138
+ echo ""
139
+ npm install -g easy-devops@${latestVersion}
140
+ if [ $? -eq 0 ]; then
141
+ echo ""
142
+ echo -e "\\033[32mSuccessfully updated to v${latestVersion}\\033[0m"
143
+ echo -e "\\033[90mRun 'easy-devops' to start the new version.\\033[0m"
144
+ else
145
+ echo ""
146
+ echo -e "\\033[31mUpdate failed. Please try again or update manually:\\033[0m"
147
+ echo -e "\\033[33m npm install -g easy-devops@${latestVersion}\\033[0m"
148
+ fi
149
+ echo ""
150
+ read -p "Press Enter to close this window..."
151
+ `;
108
152
 
109
- sp.succeed(`Updated to v${latestVersion}`);
153
+ console.log(chalk.cyan('\n Starting external update process...'));
154
+ console.log(chalk.gray(' A new window will open to complete the update.'));
155
+ console.log(chalk.gray(' This window will close after the update starts.\n'));
110
156
 
111
- // Re-initialize the database connection after npm replaced the module
112
- initDb();
157
+ if (isWindows) {
158
+ // Write script to temp file and run in new PowerShell window
159
+ const tempDir = process.env.TEMP || 'C:\\Windows\\Temp';
160
+ const tempScript = path.join(tempDir, 'easy-devops-update.ps1');
161
+ fs.writeFileSync(tempScript, updateScriptPS, 'utf8');
113
162
 
114
- // Step 5 restart dashboard if it was running before
115
- const saved = dbGet('update-pre-dashboard');
116
- dbSet('update-pre-dashboard', null);
163
+ // Run in a new window, this process will exit
164
+ spawn('powershell.exe', [
165
+ '-NoExit',
166
+ '-ExecutionPolicy', 'Bypass',
167
+ '-File', tempScript
168
+ ], {
169
+ detached: true,
170
+ stdio: 'ignore',
171
+ windowsHide: false
172
+ }).unref();
117
173
 
118
- if (saved?.wasRunning) {
119
- const port = saved.port || loadConfig().dashboardPort;
120
- const restSp = ora(`Restarting dashboard on port ${port}...`).start();
121
- const res = await startDashboard(port);
122
- res.success
123
- ? restSp.succeed(`Dashboard restarted on port ${port}`)
124
- : restSp.fail('Could not restart dashboard — use the Dashboard menu');
174
+ // Clean up hint
175
+ console.log(chalk.gray(` Update script: ${tempScript}`));
176
+ } else {
177
+ // On Linux/Mac, run in a new terminal window if possible
178
+ const tempScript = '/tmp/easy-devops-update.sh';
179
+ fs.writeFileSync(tempScript, updateScriptBash, 'utf8');
180
+ fs.chmodSync(tempScript, '755');
181
+
182
+ // Try common terminal emulators
183
+ const terminals = [
184
+ ['gnome-terminal', '--', 'bash', '-c', updateScriptBash],
185
+ ['xterm', '-e', 'bash', '-c', updateScriptBash],
186
+ ['konsole', '-e', 'bash', '-c', updateScriptBash],
187
+ ];
188
+ let launched = false;
189
+
190
+ for (const [cmd, ...args] of terminals) {
191
+ try {
192
+ spawn(cmd, args, {
193
+ detached: true,
194
+ stdio: 'ignore'
195
+ }).unref();
196
+ launched = true;
197
+ break;
198
+ } catch { /* try next */ }
199
+ }
200
+
201
+ if (!launched) {
202
+ // Fallback: just run npm directly in this terminal
203
+ console.log(chalk.yellow('\nRunning update in this window...'));
204
+ const result = await run(`npm install -g easy-devops@${latestVersion}`, { timeout: 120000 });
205
+ if (result.success) {
206
+ console.log(chalk.green(`\n Successfully updated to v${latestVersion}`));
207
+ } else {
208
+ console.log(chalk.red('\n Update failed:'));
209
+ console.log(result.stderr || result.stdout);
210
+ }
211
+ return result.success;
212
+ }
125
213
  }
126
214
 
215
+ // Give a moment for the external process to start
216
+ await new Promise(r => setTimeout(r, 1000));
217
+
218
+ console.log(chalk.green('\n Update process launched.'));
219
+ console.log(chalk.gray(' Complete the update in the new window, then run: easy-devops'));
220
+ console.log(chalk.gray(' If the dashboard was running, it will be restarted on next launch.\n'));
221
+
127
222
  return true;
128
223
  }
129
224
 
@@ -137,12 +232,12 @@ export default async function updateMenu() {
137
232
  const latestVersion = await fetchLatestVersion();
138
233
  spinner.stop();
139
234
 
140
- console.log(chalk.bold('\n Check for Updates'));
141
- console.log(chalk.gray(' ' + '─'.repeat(40)));
142
- console.log(` Current version : ${chalk.cyan('v' + currentVersion)}`);
235
+ console.log(chalk.bold('\n Check for Updates'));
236
+ console.log(chalk.gray(' ' + '─'.repeat(40)));
237
+ console.log(` Current version : ${chalk.cyan('v' + currentVersion)}`);
143
238
 
144
239
  if (!latestVersion) {
145
- console.log(chalk.yellow(' Could not reach npm registry. Check your internet connection.\n'));
240
+ console.log(chalk.yellow(' Could not reach npm registry. Check your internet connection.\n'));
146
241
  await inquirer.prompt([{ type: 'input', name: '_', message: 'Press Enter to go back...' }]);
147
242
  return;
148
243
  }
@@ -150,9 +245,9 @@ export default async function updateMenu() {
150
245
  const updateAvailable = isNewer(latestVersion, currentVersion);
151
246
 
152
247
  if (updateAvailable) {
153
- console.log(` Latest version : ${chalk.green('v' + latestVersion)} ${chalk.yellow('← update available')}\n`);
248
+ console.log(` Latest version : ${chalk.green('v' + latestVersion)} ${chalk.yellow('← update available')}\n`);
154
249
  } else {
155
- console.log(` Latest version : ${chalk.green('v' + latestVersion)} ${chalk.gray('✓ up to date')}\n`);
250
+ console.log(` Latest version : ${chalk.green('v' + latestVersion)} ${chalk.gray('✓ up to date')}\n`);
156
251
  }
157
252
 
158
253
  const choices = updateAvailable
@@ -162,8 +257,8 @@ export default async function updateMenu() {
162
257
  let choice;
163
258
  try {
164
259
  ({ choice } = await inquirer.prompt([{
165
- type: 'list',
166
- name: 'choice',
260
+ type: 'list',
261
+ name: 'choice',
167
262
  message: 'Select an option:',
168
263
  choices,
169
264
  }]));
@@ -173,12 +268,9 @@ export default async function updateMenu() {
173
268
  }
174
269
 
175
270
  if (choice === `Update to v${latestVersion}`) {
176
- const success = await performUpdate(latestVersion);
177
- if (success) {
178
- console.log(chalk.gray('\n Restart easy-devops to use the new version.\n'));
179
- }
271
+ await performUpdate(latestVersion);
180
272
  try {
181
- await inquirer.prompt([{ type: 'input', name: '_', message: 'Press Enter to continue...' }]);
273
+ await inquirer.prompt([{ type: 'input', name: '_', message: 'Press Enter to exit...' }]);
182
274
  } catch { /* ExitPromptError */ }
183
275
  }
184
276
  }
@@ -257,7 +257,8 @@ export function buildConf(domain, nginxDir, certbotDir) {
257
257
 
258
258
  // Logging
259
259
  if (advanced?.accessLog) {
260
- mainBlock.push(` access_log /var/log/nginx/${name}.access.log;`);
260
+ const logDir = isWindows ? `${nginxDir.replace(/\\/g, '/')}/logs` : '/var/log/nginx';
261
+ mainBlock.push(` access_log ${logDir}/${name}.access.log;`);
261
262
  }
262
263
 
263
264
  // ─── Location Block ─────────────────────────────────────────────────────────
@@ -301,10 +302,10 @@ export function buildConf(domain, nginxDir, certbotDir) {
301
302
  mainBlock.push(` error_page 500 502 503 504 /50x.html;`);
302
303
  }
303
304
  if (security.custom404) {
304
- mainBlock.push(` location = /404.html { root /usr/share/nginx/html; internal; }`);
305
+ mainBlock.push(` location = /404.html { root ${isWindows ? nginxDir.replace(/\\/g, '/') + '/html' : '/usr/share/nginx/html'}; internal; }`);
305
306
  }
306
307
  if (security.custom50x) {
307
- mainBlock.push(` location = /50x.html { root /usr/share/nginx/html; internal; }`);
308
+ mainBlock.push(` location = /50x.html { root ${isWindows ? nginxDir.replace(/\\/g, '/') + '/html' : '/usr/share/nginx/html'}; internal; }`);
308
309
  }
309
310
  }
310
311
 
@@ -141,17 +141,26 @@ export async function start() {
141
141
  throw new NginxNotFoundError('nginx binary not found');
142
142
  }
143
143
 
144
- // Test config before starting
144
+ // Ensure required directories exist on Windows
145
+ if (process.platform === 'win32') {
146
+ await fs.mkdir(path.join(nginxDir, 'logs'), { recursive: true });
147
+ await fs.mkdir(path.join(nginxDir, 'temp'), { recursive: true });
148
+ }
149
+
150
+ // Test config before starting with explicit config path on Windows
145
151
  await ensureNginxInclude(nginxDir);
146
- const testResult = await run(`${nginxExe} -t`);
152
+ const testCmd = process.platform === 'win32'
153
+ ? `${nginxExe} -c "${path.join(nginxDir, 'conf', 'nginx.conf')}" -t`
154
+ : `${nginxExe} -t`;
155
+ const testResult = await run(testCmd);
147
156
  if (!testResult.success) {
148
157
  return { success: false, output: combineOutput(testResult) };
149
158
  }
150
159
 
151
- // Start nginx
160
+ // Start nginx with explicit config path on Windows
152
161
  if (process.platform === 'win32') {
153
- // On Windows, nginx runs in foreground so we need Start-Process
154
- const startCmd = `Start-Process -FilePath "${path.join(nginxDir, 'nginx.exe')}" -WorkingDirectory "${nginxDir}" -WindowStyle Hidden`;
162
+ const confPath = path.join(nginxDir, 'conf', 'nginx.conf');
163
+ const startCmd = `Start-Process -FilePath "${path.join(nginxDir, 'nginx.exe')}" -ArgumentList '-c','"${confPath}"' -WorkingDirectory "${nginxDir}" -WindowStyle Hidden`;
155
164
  await run(startCmd, { timeout: 10000 });
156
165
  } else {
157
166
  const result = await run('nginx', { cwd: nginxDir, timeout: 15000 });
@@ -216,7 +225,11 @@ export async function test() {
216
225
  }
217
226
 
218
227
  await ensureNginxInclude(nginxDir);
219
- const result = await run(`${nginxExe} -t`);
228
+ // Use explicit -c flag on Windows to avoid path issues
229
+ const testCmd = process.platform === 'win32'
230
+ ? `${nginxExe} -c "${path.join(nginxDir, 'conf', 'nginx.conf')}" -t`
231
+ : `${nginxExe} -t`;
232
+ const result = await run(testCmd);
220
233
  return { success: result.success, output: combineOutput(result) };
221
234
  }
222
235
 
@@ -18,7 +18,12 @@ function getNginxExe(nginxDir) {
18
18
 
19
19
  function nginxTestCmd(nginxDir) {
20
20
  const exe = getNginxExe(nginxDir);
21
- return isWindows ? `& "${exe}" -t` : 'nginx -t';
21
+ // Use explicit -c flag on Windows to avoid path issues
22
+ if (isWindows) {
23
+ const confPath = `${nginxDir}\\conf\\nginx.conf`;
24
+ return `& "${exe}" -c "${confPath}" -t`;
25
+ }
26
+ return 'nginx -t';
22
27
  }
23
28
 
24
29
  function nginxReloadCmd(nginxDir) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-devops",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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",