Ditzzy AF commited on
Commit
2a7c094
·
1 Parent(s): ad126ea

fix: Update default port and remove unused code in app.js

Browse files

- Changed default port from 3000 to 7860.
- Removed unused platform detection variables and SystemMonitor properties.
- Simplified SystemMonitor methods by removing unnecessary whitespace and comments.
- Updated SystemMonitor.getSystemInfo to use Promise.all for parallel execution of CPU, memory, and disk usage checks.

Files changed (1) hide show
  1. app.js +75 -395
app.js CHANGED
@@ -26,20 +26,15 @@ const io = new IOServer(httpServer, {
26
  }
27
  });
28
 
29
- const PORT = process.env.PORT || 3000;
30
  const SESSION_SECRET = process.env.SESSION_SECRET || "devsecret";
31
  const PASSWORD_HASH = process.env.WEBTERM_PASSWORD_HASH;
32
 
33
- // Platform detection
34
  const isWindows = process.platform === "win32";
35
- const isLinux = process.platform === "linux";
36
- const isMacOS = process.platform === "darwin";
37
 
38
  // === System Utilities ===
39
  class SystemMonitor {
40
  constructor() {
41
- this.cpuHistory = [];
42
- this.networkHistory = [];
43
  this.previousNetworkStats = null;
44
  }
45
 
@@ -59,7 +54,6 @@ class SystemMonitor {
59
  cpuAverage() {
60
  const cpus = os.cpus();
61
  let user = 0, nice = 0, sys = 0, idle = 0, irq = 0;
62
-
63
  for (let cpu of cpus) {
64
  user += cpu.times.user;
65
  nice += cpu.times.nice || 0;
@@ -67,7 +61,6 @@ class SystemMonitor {
67
  irq += cpu.times.irq || 0;
68
  idle += cpu.times.idle;
69
  }
70
-
71
  return {
72
  idle: idle / cpus.length,
73
  total: (user + nice + sys + idle + irq) / cpus.length
@@ -78,11 +71,10 @@ class SystemMonitor {
78
  const totalMem = os.totalmem();
79
  const freeMem = os.freemem();
80
  const usedMem = totalMem - freeMem;
81
-
82
  return {
83
- total: Math.round(totalMem / 1024 / 1024), // MB
84
- used: Math.round(usedMem / 1024 / 1024), // MB
85
- free: Math.round(freeMem / 1024 / 1024), // MB
86
  percentage: Math.round((usedMem / totalMem) * 100)
87
  };
88
  }
@@ -92,28 +84,23 @@ class SystemMonitor {
92
  if (isWindows) {
93
  const { stdout } = await execAsync('wmic logicaldisk get size,freespace,caption /format:csv');
94
  const lines = stdout.split('\n').filter(line => line.includes(':'));
95
-
96
  if (lines.length > 0) {
97
  const parts = lines[0].split(',');
98
  if (parts.length >= 3) {
99
  const freeSpace = parseInt(parts[1]) || 0;
100
  const totalSpace = parseInt(parts[2]) || 1;
101
  const usedSpace = totalSpace - freeSpace;
102
- const percentage = Math.round((usedSpace / totalSpace) * 100);
103
-
104
  return {
105
- total: Math.round(totalSpace / 1024 / 1024 / 1024), // GB
106
- used: Math.round(usedSpace / 1024 / 1024 / 1024), // GB
107
- free: Math.round(freeSpace / 1024 / 1024 / 1024), // GB
108
- percentage: percentage
109
  };
110
  }
111
  }
112
  } else {
113
- // Linux/macOS
114
  const { stdout } = await execAsync('df -h / | tail -1');
115
  const parts = stdout.trim().split(/\s+/);
116
-
117
  if (parts.length >= 5) {
118
  const percentage = parseInt(parts[4].replace('%', '')) || 0;
119
  return {
@@ -124,208 +111,66 @@ class SystemMonitor {
124
  };
125
  }
126
  }
127
- } catch (error) {
128
- console.warn('Disk usage detection failed:', error.message);
129
  }
130
-
131
  return { total: 0, used: 0, free: 0, percentage: 0 };
132
  }
133
 
134
- async getProcessList() {
135
- try {
136
- let processes = [];
137
-
138
- if (isWindows) {
139
- const { stdout } = await execAsync(
140
- 'powershell "Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 Name, CPU | ConvertTo-Csv -NoTypeInformation"'
141
- );
142
-
143
- const lines = stdout.split('\n').slice(1).filter(line => line.trim());
144
- processes = lines.map(line => {
145
- const [name, cpu] = line.replace(/"/g, '').split(',');
146
- return {
147
- name: name || 'Unknown',
148
- cpu: parseFloat(cpu) || 0
149
- };
150
- }).filter(p => p.name !== 'Unknown').slice(0, 8);
151
-
152
- } else {
153
- // Linux/macOS - using ps command
154
- const { stdout } = await execAsync('ps aux --sort=-%cpu | head -9 | tail -8');
155
- const lines = stdout.split('\n').filter(line => line.trim());
156
-
157
- processes = lines.map(line => {
158
- const parts = line.trim().split(/\s+/);
159
- return {
160
- name: parts[10] ? path.basename(parts[10]) : 'unknown',
161
- cpu: parseFloat(parts[2]) || 0
162
- };
163
- });
164
- }
165
-
166
- return processes;
167
- } catch (error) {
168
- console.warn('Process list detection failed:', error.message);
169
- return [];
170
- }
171
- }
172
-
173
- async getNetworkStats() {
174
- try {
175
- if (isWindows) {
176
- // Windows network stats - simplified for now
177
- const { stdout } = await execAsync('powershell "Get-Counter \\"\\Network Interface(*)\\Bytes Total/sec\\" -SampleInterval 1 -MaxSamples 1 | Select-Object -ExpandProperty CounterSamples | Where-Object {$_.InstanceName -ne \\"isatap*\\" -and $_.InstanceName -ne \\"Teredo*\\" -and $_.InstanceName -ne \\"_Total\\""} | Measure-Object CookedValue -Sum | Select-Object -ExpandProperty Sum"');
178
- const totalBytes = parseFloat(stdout.trim()) || 0;
179
- return { rx: totalBytes / 2, tx: totalBytes / 2 }; // Rough estimate
180
- } else {
181
- // Linux - read from /proc/net/dev
182
- const data = await fs.readFile('/proc/net/dev', 'utf8');
183
- const lines = data.split('\n');
184
- let totalRx = 0, totalTx = 0;
185
-
186
- for (let line of lines) {
187
- if (line.includes(':') && !line.includes('lo:')) {
188
- const parts = line.split(':')[1].trim().split(/\s+/);
189
- totalRx += parseInt(parts[0]) || 0;
190
- totalTx += parseInt(parts[8]) || 0;
191
- }
192
- }
193
-
194
- // Calculate speed if we have previous data
195
- if (this.previousNetworkStats) {
196
- const rxSpeed = Math.max(0, totalRx - this.previousNetworkStats.rx);
197
- const txSpeed = Math.max(0, totalTx - this.previousNetworkStats.tx);
198
- this.previousNetworkStats = { rx: totalRx, tx: totalTx };
199
- return { rx: rxSpeed, tx: txSpeed };
200
- } else {
201
- this.previousNetworkStats = { rx: totalRx, tx: totalTx };
202
- return { rx: 0, tx: 0 };
203
- }
204
- }
205
- } catch (error) {
206
- console.warn('Network stats detection failed:', error.message);
207
- return { rx: 0, tx: 0 };
208
- }
209
- }
210
-
211
  formatUptime(seconds) {
212
  const days = Math.floor(seconds / 86400);
213
  const hours = Math.floor((seconds % 86400) / 3600);
214
  const minutes = Math.floor((seconds % 3600) / 60);
215
-
216
  if (days > 0) return `${days}d ${hours}h ${minutes}m`;
217
  if (hours > 0) return `${hours}h ${minutes}m`;
218
  return `${minutes}m`;
219
  }
220
 
221
  async getSystemInfo() {
222
- try {
223
- const [cpuUsage, memInfo, diskInfo, processes, networkStats] = await Promise.all([
224
- this.getCPUUsage(),
225
- this.getMemoryInfo(),
226
- this.getDiskUsage(),
227
- this.getProcessList(),
228
- this.getNetworkStats()
229
- ]);
230
-
231
- const cpuInfo = os.cpus()[0];
232
- const uptime = os.uptime();
233
-
234
- return {
235
- // System info
236
- platform: os.platform(),
237
- release: os.release(),
238
- arch: os.arch(),
239
- hostname: os.hostname(),
240
- uptime: this.formatUptime(uptime),
241
-
242
- // CPU info
243
- cpu_model: cpuInfo.model,
244
- cpu_cores: os.cpus().length,
245
- cpu_usage: cpuUsage,
246
-
247
- // Memory info
248
- total_mem: memInfo.total,
249
- used_mem: memInfo.used,
250
- free_mem: memInfo.free,
251
- mem_usage: memInfo.percentage,
252
-
253
- // Disk info
254
- disk_total: diskInfo.total,
255
- disk_used: diskInfo.used,
256
- disk_free: diskInfo.free,
257
- disk_usage: diskInfo.percentage,
258
-
259
- // Network info (speeds in bytes per second)
260
- network_rx: networkStats.rx,
261
- network_tx: networkStats.tx,
262
-
263
- // Process list
264
- processes: processes,
265
-
266
- // Load average (Linux/macOS only)
267
- loadavg: isWindows ? [0, 0, 0] : os.loadavg(),
268
-
269
- // User info
270
- user_info: os.userInfo(),
271
-
272
- timestamp: Date.now()
273
- };
274
- } catch (error) {
275
- console.error('System info error:', error);
276
- return null;
277
- }
278
  }
279
  }
280
 
281
  const systemMonitor = new SystemMonitor();
282
 
283
  // === Middleware ===
284
- app.use(helmet({
285
- contentSecurityPolicy: {
286
- directives: {
287
- defaultSrc: ["'self'"],
288
- scriptSrc: [
289
- "'self'",
290
- "'unsafe-inline'",
291
- "https://cdn.jsdelivr.net"
292
- ],
293
- styleSrc: [
294
- "'self'",
295
- "'unsafe-inline'",
296
- "https://cdn.jsdelivr.net",
297
- "https://fonts.googleapis.com"
298
- ],
299
- fontSrc: [
300
- "'self'",
301
- "https://fonts.googleapis.com",
302
- "https://fonts.gstatic.com"
303
- ],
304
- imgSrc: ["'self'", "data:"],
305
- connectSrc: ["'self'", "ws:", "wss:"]
306
- }
307
- },
308
- crossOriginEmbedderPolicy: false
309
- }));
310
-
311
  app.use(express.urlencoded({ extended: false }));
312
  app.use(express.json());
313
  app.use(express.static(path.join(__dirname, "public")));
314
  app.set("view engine", "ejs");
315
  app.set("views", path.join(__dirname, "views"));
316
 
317
- // === Session ===
318
  app.use(
319
  session({
320
  secret: SESSION_SECRET,
321
  resave: false,
322
  saveUninitialized: false,
323
- cookie: {
324
- httpOnly: true,
325
- sameSite: "lax",
326
- secure: process.env.NODE_ENV === "production",
327
- maxAge: 24 * 60 * 60 * 1000 // 24 hours
328
- },
329
  })
330
  );
331
 
@@ -336,29 +181,17 @@ function requireAuth(req, res, next) {
336
  }
337
 
338
  // === Routes ===
339
- app.get("/login", (req, res) => {
340
- if (req.session?.authed) return res.redirect("/");
341
- res.render("login", { error: null });
342
- });
343
 
344
  app.post("/login", async (req, res) => {
345
  const { password } = req.body;
346
- try {
347
- if (!PASSWORD_HASH) {
348
- console.error("PASSWORD_HASH not set in environment variables");
349
- return res.render("login", { error: "Server configuration error" });
350
- }
351
-
352
- const isValid = await bcrypt.compare(password || "", PASSWORD_HASH);
353
- if (isValid) {
354
- req.session.authed = true;
355
- return res.redirect("/");
356
- } else {
357
- return res.render("login", { error: "Invalid password" });
358
- }
359
- } catch (error) {
360
- console.error("Login error:", error);
361
- return res.render("login", { error: "Authentication error" });
362
  }
363
  });
364
 
@@ -366,25 +199,18 @@ app.get("/logout", (req, res) => {
366
  req.session.destroy(() => res.redirect("/login"));
367
  });
368
 
369
- app.get("/", requireAuth, (req, res) => {
370
- res.render("terminal");
371
- });
372
 
373
  app.get("/sysinfo", requireAuth, async (req, res) => {
374
  try {
375
- const systemInfo = await systemMonitor.getSystemInfo();
376
- if (systemInfo) {
377
- res.json(systemInfo);
378
- } else {
379
- res.status(500).json({ error: "Failed to fetch system information" });
380
- }
381
- } catch (error) {
382
- console.error("System info endpoint error:", error);
383
- res.status(500).json({ error: "Internal server error" });
384
  }
385
  });
386
 
387
- // === Terminal Class for better process management ===
388
  class Terminal {
389
  constructor(socket) {
390
  this.socket = socket;
@@ -394,221 +220,75 @@ class Terminal {
394
 
395
  async initialize() {
396
  try {
397
- // Try to use node-pty first
398
  const nodePty = await import("node-pty");
399
  return this.initWithPty(nodePty);
400
- } catch (error) {
401
- console.warn("node-pty not available, using fallback:", error.message);
402
  return this.initWithSpawn();
403
  }
404
  }
405
 
406
  initWithPty(nodePty) {
407
  const shell = this.getShell();
408
- const env = { ...process.env };
409
-
410
- // Set better environment for cross-platform compatibility
411
- if (isWindows) {
412
- env.TERM = 'windows-ansi';
413
- } else {
414
- env.TERM = 'xterm-256color';
415
- env.COLORTERM = 'truecolor';
416
- }
417
-
418
  this.process = nodePty.spawn(shell, [], {
419
  name: isWindows ? 'windows-ansi' : 'xterm-256color',
420
  cols: 80,
421
  rows: 24,
422
- cwd: this.getInitialDirectory(),
423
  env: env,
424
- useConpty: isWindows, // Use ConPTY on Windows 10+
425
  });
426
 
427
- this.process.onData(data => {
428
- if (this.isActive) {
429
- this.socket.emit("terminal_output", data);
430
- }
431
- });
432
 
433
- this.process.onExit(() => {
434
- if (this.isActive) {
435
- this.socket.emit("terminal_output", "\r\n\x1b[91mTerminal session ended\x1b[0m\r\n");
436
- }
437
- });
438
 
439
- // Socket event handlers
440
- this.socket.on("terminal_input", (data) => {
441
- if (this.process && this.isActive) {
442
- this.process.write(data);
443
- }
444
- });
445
-
446
- this.socket.on("terminal_resize", ({ cols, rows }) => {
447
- if (this.process && this.isActive) {
448
- this.process.resize(cols || 80, rows || 24);
449
- }
450
- });
451
-
452
- console.log(`Terminal initialized with node-pty (${shell})`);
453
  return true;
454
  }
455
 
456
  initWithSpawn() {
457
  const shell = this.getShell();
458
- const args = this.getShellArgs();
459
-
460
- this.process = spawn(shell, args, {
461
- cwd: this.getInitialDirectory(),
462
- env: { ...process.env, TERM: isWindows ? 'windows-ansi' : 'xterm-256color' },
463
- stdio: ['pipe', 'pipe', 'pipe']
464
- });
465
 
466
- this.process.stdout.on("data", (data) => {
467
- if (this.isActive) {
468
- this.socket.emit("terminal_output", data.toString());
469
- }
470
- });
471
-
472
- this.process.stderr.on("data", (data) => {
473
- if (this.isActive) {
474
- this.socket.emit("terminal_output", data.toString());
475
- }
476
- });
477
 
478
- this.process.on("exit", (code) => {
479
- if (this.isActive) {
480
- this.socket.emit("terminal_output", `\r\n\x1b[91mProcess exited with code ${code}\x1b[0m\r\n`);
481
- }
482
- });
483
-
484
- this.socket.on("terminal_input", (data) => {
485
- if (this.process && this.process.stdin && this.isActive) {
486
- this.process.stdin.write(data);
487
- }
488
- });
489
-
490
- console.log(`Terminal initialized with spawn fallback (${shell})`);
491
  return true;
492
  }
493
 
494
  getShell() {
495
- if (isWindows) {
496
- return process.env.COMSPEC || "cmd.exe";
497
- } else {
498
- return process.env.SHELL || "/bin/bash";
499
- }
500
- }
501
-
502
- getShellArgs() {
503
- if (isWindows) {
504
- return [];
505
- } else {
506
- return ["-l"]; // Login shell
507
- }
508
- }
509
-
510
- getInitialDirectory() {
511
- if (isWindows) {
512
- return process.env.USERPROFILE || process.cwd();
513
- } else {
514
- return process.env.HOME || process.cwd();
515
- }
516
  }
517
 
518
  destroy() {
519
  this.isActive = false;
520
  if (this.process) {
521
- try {
522
- if (typeof this.process.kill === 'function') {
523
- this.process.kill();
524
- } else if (typeof this.process.destroy === 'function') {
525
- this.process.destroy();
526
- }
527
- } catch (error) {
528
- console.warn("Error destroying terminal process:", error.message);
529
- }
530
  }
531
  }
532
  }
533
 
534
- // === Socket.IO Connection Handler ===
535
  io.on("connection", async (socket) => {
536
- console.log(`Socket connected: ${socket.id} from ${socket.handshake.address}`);
537
-
538
- // Create terminal instance
539
  const terminal = new Terminal(socket);
540
- const initSuccess = await terminal.initialize();
541
 
542
- if (!initSuccess) {
543
- socket.emit("terminal_output", "\r\n\x1b[91mFailed to initialize terminal\x1b[0m\r\n");
544
- }
545
-
546
- // Send welcome message
547
- const welcomeMessage = `\x1b[32m╭─────────────────────────────────────────────────╮\x1b[0m\r\n` +
548
- `\x1b[32m│\x1b[0m \x1b[1mWeb Terminal v2.0\x1b[0m \x1b[32m│\x1b[0m\r\n` +
549
- `\x1b[32m│\x1b[0m Platform: ${process.platform} (${process.arch})${' '.repeat(Math.max(0, 15 - process.platform.length - process.arch.length))} \x1b[32m│\x1b[0m\r\n` +
550
- `\x1b[32m│\x1b[0m Shell: ${terminal.getShell()}${' '.repeat(Math.max(0, 25 - terminal.getShell().length))} \x1b[32m│\x1b[0m\r\n` +
551
- `\x1b[32m╰─────────────────────────────────────────────────╯\x1b[0m\r\n\r\n`;
552
-
553
- socket.emit("terminal_output", welcomeMessage);
554
-
555
- // Real-time system monitoring
556
- const systemInfoInterval = setInterval(async () => {
557
- try {
558
- const systemInfo = await systemMonitor.getSystemInfo();
559
- if (systemInfo && socket.connected) {
560
- socket.emit("system_info", systemInfo);
561
- }
562
- } catch (error) {
563
- console.error("System monitoring error:", error);
564
  }
565
  }, 2000);
566
 
567
- // Cleanup on disconnect
568
- socket.on("disconnect", () => {
569
- console.log(`Socket disconnected: ${socket.id}`);
570
- clearInterval(systemInfoInterval);
571
- terminal.destroy();
572
- });
573
-
574
- // Error handling
575
- socket.on("error", (error) => {
576
- console.error(`Socket error for ${socket.id}:`, error);
577
- });
578
- });
579
-
580
- // === Error Handling ===
581
- process.on('uncaughtException', (error) => {
582
- console.error('Uncaught Exception:', error);
583
- });
584
-
585
- process.on('unhandledRejection', (reason, promise) => {
586
- console.error('Unhandled Rejection at:', promise, 'reason:', reason);
587
- });
588
-
589
- // === Graceful Shutdown ===
590
- process.on('SIGTERM', () => {
591
- console.log('SIGTERM received, shutting down gracefully');
592
- httpServer.close(() => {
593
- console.log('Process terminated');
594
- process.exit(0);
595
- });
596
- });
597
-
598
- process.on('SIGINT', () => {
599
- console.log('SIGINT received, shutting down gracefully');
600
- httpServer.close(() => {
601
- console.log('Process terminated');
602
- process.exit(0);
603
- });
604
  });
605
 
606
  // === Start Server ===
607
  httpServer.listen(PORT, () => {
608
- console.log(`✅ Web Terminal Server running on http://localhost:${PORT}`);
609
- console.log(`📊 Platform: ${process.platform} (${process.arch})`);
610
- console.log(`🐚 Default Shell: ${isWindows ? process.env.COMSPEC || "cmd.exe" : process.env.SHELL || "/bin/bash"}`);
611
- console.log(`🔐 Authentication: ${PASSWORD_HASH ? 'Enabled' : 'Disabled (set WEBTERM_PASSWORD_HASH)'}`);
612
  });
613
-
614
- export default app;
 
26
  }
27
  });
28
 
29
+ const PORT = process.env.PORT || 7860;
30
  const SESSION_SECRET = process.env.SESSION_SECRET || "devsecret";
31
  const PASSWORD_HASH = process.env.WEBTERM_PASSWORD_HASH;
32
 
 
33
  const isWindows = process.platform === "win32";
 
 
34
 
35
  // === System Utilities ===
36
  class SystemMonitor {
37
  constructor() {
 
 
38
  this.previousNetworkStats = null;
39
  }
40
 
 
54
  cpuAverage() {
55
  const cpus = os.cpus();
56
  let user = 0, nice = 0, sys = 0, idle = 0, irq = 0;
 
57
  for (let cpu of cpus) {
58
  user += cpu.times.user;
59
  nice += cpu.times.nice || 0;
 
61
  irq += cpu.times.irq || 0;
62
  idle += cpu.times.idle;
63
  }
 
64
  return {
65
  idle: idle / cpus.length,
66
  total: (user + nice + sys + idle + irq) / cpus.length
 
71
  const totalMem = os.totalmem();
72
  const freeMem = os.freemem();
73
  const usedMem = totalMem - freeMem;
 
74
  return {
75
+ total: Math.round(totalMem / 1024 / 1024),
76
+ used: Math.round(usedMem / 1024 / 1024),
77
+ free: Math.round(freeMem / 1024 / 1024),
78
  percentage: Math.round((usedMem / totalMem) * 100)
79
  };
80
  }
 
84
  if (isWindows) {
85
  const { stdout } = await execAsync('wmic logicaldisk get size,freespace,caption /format:csv');
86
  const lines = stdout.split('\n').filter(line => line.includes(':'));
 
87
  if (lines.length > 0) {
88
  const parts = lines[0].split(',');
89
  if (parts.length >= 3) {
90
  const freeSpace = parseInt(parts[1]) || 0;
91
  const totalSpace = parseInt(parts[2]) || 1;
92
  const usedSpace = totalSpace - freeSpace;
 
 
93
  return {
94
+ total: Math.round(totalSpace / 1024 / 1024 / 1024),
95
+ used: Math.round(usedSpace / 1024 / 1024 / 1024),
96
+ free: Math.round(freeSpace / 1024 / 1024 / 1024),
97
+ percentage: Math.round((usedSpace / totalSpace) * 100)
98
  };
99
  }
100
  }
101
  } else {
 
102
  const { stdout } = await execAsync('df -h / | tail -1');
103
  const parts = stdout.trim().split(/\s+/);
 
104
  if (parts.length >= 5) {
105
  const percentage = parseInt(parts[4].replace('%', '')) || 0;
106
  return {
 
111
  };
112
  }
113
  }
114
+ } catch {
115
+ return { total: 0, used: 0, free: 0, percentage: 0 };
116
  }
 
117
  return { total: 0, used: 0, free: 0, percentage: 0 };
118
  }
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  formatUptime(seconds) {
121
  const days = Math.floor(seconds / 86400);
122
  const hours = Math.floor((seconds % 86400) / 3600);
123
  const minutes = Math.floor((seconds % 3600) / 60);
 
124
  if (days > 0) return `${days}d ${hours}h ${minutes}m`;
125
  if (hours > 0) return `${hours}h ${minutes}m`;
126
  return `${minutes}m`;
127
  }
128
 
129
  async getSystemInfo() {
130
+ const [cpuUsage, memInfo, diskInfo] = await Promise.all([
131
+ this.getCPUUsage(),
132
+ this.getMemoryInfo(),
133
+ this.getDiskUsage()
134
+ ]);
135
+ const cpuInfo = os.cpus()[0];
136
+ return {
137
+ platform: os.platform(),
138
+ release: os.release(),
139
+ arch: os.arch(),
140
+ hostname: os.hostname(),
141
+ uptime: this.formatUptime(os.uptime()),
142
+ cpu_model: cpuInfo.model,
143
+ cpu_cores: os.cpus().length,
144
+ cpu_usage: cpuUsage,
145
+ total_mem: memInfo.total,
146
+ used_mem: memInfo.used,
147
+ free_mem: memInfo.free,
148
+ mem_usage: memInfo.percentage,
149
+ disk_total: diskInfo.total,
150
+ disk_used: diskInfo.used,
151
+ disk_free: diskInfo.free,
152
+ disk_usage: diskInfo.percentage,
153
+ timestamp: Date.now()
154
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  }
156
  }
157
 
158
  const systemMonitor = new SystemMonitor();
159
 
160
  // === Middleware ===
161
+ app.use(helmet());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  app.use(express.urlencoded({ extended: false }));
163
  app.use(express.json());
164
  app.use(express.static(path.join(__dirname, "public")));
165
  app.set("view engine", "ejs");
166
  app.set("views", path.join(__dirname, "views"));
167
 
 
168
  app.use(
169
  session({
170
  secret: SESSION_SECRET,
171
  resave: false,
172
  saveUninitialized: false,
173
+ cookie: { httpOnly: true, sameSite: "lax", secure: false, maxAge: 24*60*60*1000 }
 
 
 
 
 
174
  })
175
  );
176
 
 
181
  }
182
 
183
  // === Routes ===
184
+ app.get("/login", (req, res) => res.render("login", { error: null }));
 
 
 
185
 
186
  app.post("/login", async (req, res) => {
187
  const { password } = req.body;
188
+ if (!PASSWORD_HASH) return res.render("login", { error: "Server not configured" });
189
+ const valid = await bcrypt.compare(password || "", PASSWORD_HASH);
190
+ if (valid) {
191
+ req.session.authed = true;
192
+ return res.redirect("/");
193
+ } else {
194
+ return res.render("login", { error: "Invalid password" });
 
 
 
 
 
 
 
 
 
195
  }
196
  });
197
 
 
199
  req.session.destroy(() => res.redirect("/login"));
200
  });
201
 
202
+ app.get("/", requireAuth, (req, res) => res.render("terminal"));
 
 
203
 
204
  app.get("/sysinfo", requireAuth, async (req, res) => {
205
  try {
206
+ const info = await systemMonitor.getSystemInfo();
207
+ res.json(info);
208
+ } catch {
209
+ res.status(500).json({ error: "Failed to fetch system info" });
 
 
 
 
 
210
  }
211
  });
212
 
213
+ // === Terminal ===
214
  class Terminal {
215
  constructor(socket) {
216
  this.socket = socket;
 
220
 
221
  async initialize() {
222
  try {
 
223
  const nodePty = await import("node-pty");
224
  return this.initWithPty(nodePty);
225
+ } catch {
 
226
  return this.initWithSpawn();
227
  }
228
  }
229
 
230
  initWithPty(nodePty) {
231
  const shell = this.getShell();
232
+ const env = { ...process.env, TERM: isWindows ? 'windows-ansi' : 'xterm-256color', COLORTERM: 'truecolor' };
 
 
 
 
 
 
 
 
 
233
  this.process = nodePty.spawn(shell, [], {
234
  name: isWindows ? 'windows-ansi' : 'xterm-256color',
235
  cols: 80,
236
  rows: 24,
237
+ cwd: '/app', // PAKSA writable directory
238
  env: env,
239
+ useConpty: isWindows
240
  });
241
 
242
+ this.process.onData(data => this.isActive && this.socket.emit("terminal_output", data));
243
+ this.process.onExit(() => this.isActive && this.socket.emit("terminal_output", "\r\n\x1b[91mTerminal session ended\x1b[0m\r\n"));
 
 
 
244
 
245
+ this.socket.on("terminal_input", data => this.process.write(data));
246
+ this.socket.on("terminal_resize", ({ cols, rows }) => this.process.resize(cols || 80, rows || 24));
 
 
 
247
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  return true;
249
  }
250
 
251
  initWithSpawn() {
252
  const shell = this.getShell();
253
+ const args = isWindows ? [] : ["-l"];
254
+ this.process = spawn(shell, args, { cwd: '/app', env: process.env, stdio: ['pipe', 'pipe', 'pipe'] });
 
 
 
 
 
255
 
256
+ this.process.stdout.on("data", data => this.isActive && this.socket.emit("terminal_output", data.toString()));
257
+ this.process.stderr.on("data", data => this.isActive && this.socket.emit("terminal_output", data.toString()));
258
+ this.process.on("exit", code => this.isActive && this.socket.emit("terminal_output", `\r\n\x1b[91mProcess exited with code ${code}\x1b[0m\r\n`));
 
 
 
 
 
 
 
 
259
 
260
+ this.socket.on("terminal_input", data => this.process.stdin.write(data));
 
 
 
 
 
 
 
 
 
 
 
 
261
  return true;
262
  }
263
 
264
  getShell() {
265
+ return isWindows ? process.env.COMSPEC || "cmd.exe" : process.env.SHELL || "/bin/bash";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  }
267
 
268
  destroy() {
269
  this.isActive = false;
270
  if (this.process) {
271
+ try { this.process.kill?.(); } catch {}
 
 
 
 
 
 
 
 
272
  }
273
  }
274
  }
275
 
276
+ // === Socket.IO ===
277
  io.on("connection", async (socket) => {
 
 
 
278
  const terminal = new Terminal(socket);
279
+ await terminal.initialize();
280
 
281
+ const systemInterval = setInterval(async () => {
282
+ if (socket.connected) {
283
+ const info = await systemMonitor.getSystemInfo();
284
+ socket.emit("system_info", info);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  }
286
  }, 2000);
287
 
288
+ socket.on("disconnect", () => { clearInterval(systemInterval); terminal.destroy(); });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  });
290
 
291
  // === Start Server ===
292
  httpServer.listen(PORT, () => {
293
+ console.log(`✅ Web Terminal running on port ${PORT}`);
 
 
 
294
  });