@woopsy/mcpanel 2.1.4 → 5.0.0
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 +24 -21
- package/dist/commands/commandRouter.js +41 -38
- package/dist/config/configManager.js +1 -1
- package/dist/index.js +112 -78
- package/dist/managers/playitManager.js +82 -6
- package/dist/utils/logger.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# MCPANEL
|
|
13
13
|
|
|
14
14
|
A terminal-based **single-server** Minecraft server manager with an Arch/neofetch-style
|
|
15
|
-
startup screen. Connect one server folder and control it with simple
|
|
15
|
+
startup screen. Connect one server folder and control it with simple commands —
|
|
16
16
|
start/stop, live logs in a separate window, backups, plugins, `server.properties` editing,
|
|
17
17
|
Java switching, and one-click [Playit.gg](https://playit.gg) tunnels so friends can join.
|
|
18
18
|
|
|
@@ -41,7 +41,7 @@ mcpanel>
|
|
|
41
41
|
|
|
42
42
|
- **Node.js 22+** — <https://nodejs.org>
|
|
43
43
|
- **Java** matching your Minecraft version (e.g. MC 26.x needs **Java 25**, MC 1.20–1.21 needs **Java 21**).
|
|
44
|
-
MCPANEL can list and switch between installed JVMs with
|
|
44
|
+
MCPANEL can list and switch between installed JVMs with `java`.
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
@@ -96,41 +96,44 @@ npx @woopsy/mcpanel
|
|
|
96
96
|
it to `/mnt/c/...` automatically.
|
|
97
97
|
3. MCPANEL detects the server type + Minecraft version, saves it, and drops you at the prompt.
|
|
98
98
|
|
|
99
|
-
To connect a different server later:
|
|
99
|
+
To connect a different server later: `sync <path>`.
|
|
100
100
|
|
|
101
101
|
---
|
|
102
102
|
|
|
103
103
|
## Commands
|
|
104
104
|
|
|
105
|
+
Commands are typed **without a leading slash** — just `start`, `tunnel java`, `playit`, etc.
|
|
106
|
+
|
|
105
107
|
| Command | What it does |
|
|
106
108
|
|---|---|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
121
|
-
|
|
109
|
+
| `start` · `stop` · `restart` | Control the server process |
|
|
110
|
+
| `console` | Interactive console (type commands sent to the server) |
|
|
111
|
+
| `log` | Open **live logs in a new terminal window** (`tail -f`) |
|
|
112
|
+
| `info` | Server path, type, version and status |
|
|
113
|
+
| `sync <path>` | Connect a different server folder |
|
|
114
|
+
| `properties` | Edit `server.properties` interactively |
|
|
115
|
+
| `java [path]` | Show/list installed JVMs, or set the one used to launch |
|
|
116
|
+
| `stats` | System + server CPU / RAM / disk usage |
|
|
117
|
+
| `folder` | Open the server folder in your file explorer |
|
|
118
|
+
| `backup create` · `list` · `restore <id>` | Manage ZIP backups |
|
|
119
|
+
| `plugins list` · `install <url>` · `remove <name>` | Manage plugins |
|
|
120
|
+
| `tunnel java` · `bedrock` · `status` · `log` · `stop` · `reset` | Playit.gg tunnels |
|
|
121
|
+
| `playit` | Stream live playit.gg relay logs (shortcut for `tunnel log`) |
|
|
122
|
+
| `config` · `clear` · `help` · `exit` | Utilities |
|
|
123
|
+
|
|
124
|
+
Type `help` inside MCPANEL for the full menu, and use **Tab** for autocompletion.
|
|
122
125
|
|
|
123
126
|
---
|
|
124
127
|
|
|
125
128
|
## Notes
|
|
126
129
|
|
|
127
130
|
- **Single server by design.** MCPANEL manages exactly one server (the one you sync).
|
|
128
|
-
- **Playit tunnel.** The first
|
|
131
|
+
- **Playit tunnel.** The first `tunnel` claims a free Playit agent in your browser once;
|
|
129
132
|
the binary is downloaded automatically. Your secret is stored locally in `config.json`
|
|
130
133
|
(which is git-ignored — don't commit it).
|
|
131
|
-
-
|
|
134
|
+
- **`log` in a new window.** On WSL it opens via the Windows console; on Linux it uses your
|
|
132
135
|
terminal emulator; on macOS it uses Terminal. If none is available it falls back to an
|
|
133
|
-
in-place read-only view (
|
|
136
|
+
in-place read-only view (`back` to exit).
|
|
134
137
|
|
|
135
138
|
---
|
|
136
139
|
|
|
@@ -65,43 +65,46 @@ class CommandRouter {
|
|
|
65
65
|
colors.bold(colors.cyan('\nMCPANEL Help Menu')),
|
|
66
66
|
colors.gray('──────────────────────────────────────────────'),
|
|
67
67
|
colors.bold(colors.green('Server Commands')),
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'
|
|
71
|
-
'
|
|
72
|
-
'
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
68
|
+
' start - Start the Minecraft server',
|
|
69
|
+
' stop - Stop the server gracefully',
|
|
70
|
+
' restart - Restart the server',
|
|
71
|
+
' console - Enter the interactive server console',
|
|
72
|
+
' log - Stream live server logs in this terminal (read-only)',
|
|
73
|
+
' info - Show server path, type, version and status',
|
|
74
|
+
' sync <path> - Connect a different server folder',
|
|
75
|
+
' properties - Edit server.properties interactively',
|
|
76
76
|
'',
|
|
77
77
|
colors.bold(colors.green('Tunnel Commands (Playit.gg)')),
|
|
78
|
-
'
|
|
79
|
-
'
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'
|
|
78
|
+
' setup - One-time Playit account claim (browser approval)',
|
|
79
|
+
' tunnel java - Auto-create & start a Java tunnel, returns address',
|
|
80
|
+
' tunnel bedrock - Auto-create & start a Bedrock tunnel, returns address',
|
|
81
|
+
' tunnel status - Check tunnel status, address and latency',
|
|
82
|
+
' tunnel log - Stream live playit relay logs in this terminal (read-only)',
|
|
83
|
+
' playit - Shortcut for tunnel log (live playit.gg logs)',
|
|
84
|
+
' tunnel stop - Stop the playit tunnel agent',
|
|
85
|
+
' tunnel reset - Clear saved agent secret (re-claim on next tunnel)',
|
|
84
86
|
'',
|
|
85
87
|
colors.bold(colors.green('Backup Commands')),
|
|
86
|
-
'
|
|
87
|
-
'
|
|
88
|
-
'
|
|
88
|
+
' backup create - Create a backup ZIP of the server',
|
|
89
|
+
' backup list - List all available backups',
|
|
90
|
+
' backup restore <id> - Restore the server from a backup ID',
|
|
89
91
|
'',
|
|
90
92
|
colors.bold(colors.green('Plugin Commands')),
|
|
91
|
-
'
|
|
92
|
-
'
|
|
93
|
-
'
|
|
93
|
+
' plugins list - List installed plugins',
|
|
94
|
+
' plugins install <url> - Download and install a plugin JAR',
|
|
95
|
+
' plugins remove <name> - Remove an installed plugin',
|
|
94
96
|
'',
|
|
95
97
|
colors.bold(colors.green('System Commands')),
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
'
|
|
100
|
-
'
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
'
|
|
104
|
-
'
|
|
98
|
+
' stats - System stats + CPU/RAM/disk of the server',
|
|
99
|
+
' java [path] - Show/list Java runtimes, or set the one used to launch',
|
|
100
|
+
' folder - Open the server folder in the file explorer',
|
|
101
|
+
' tray - Run in background, minimize console to system tray',
|
|
102
|
+
' background - Synonym for tray',
|
|
103
|
+
' clear - Clear the screen, scrollback and command history',
|
|
104
|
+
' update - Check npm for a newer version of MCPANEL',
|
|
105
|
+
' config - View active application config.json',
|
|
106
|
+
' help - Show this menu',
|
|
107
|
+
' exit - Close MCPANEL server manager',
|
|
105
108
|
colors.gray('──────────────────────────────────────────────\n')
|
|
106
109
|
].join('\n');
|
|
107
110
|
}
|
|
@@ -129,7 +132,7 @@ class CommandRouter {
|
|
|
129
132
|
executeInfo() {
|
|
130
133
|
const server = this.configManager.getServer();
|
|
131
134
|
if (!server) {
|
|
132
|
-
return colors.failure('No server connected. Use
|
|
135
|
+
return colors.failure('No server connected. Use sync <path> to connect one.');
|
|
133
136
|
}
|
|
134
137
|
const activeInfo = this.processManager.getActiveServer(server.name);
|
|
135
138
|
const statusStr = activeInfo
|
|
@@ -150,7 +153,7 @@ class CommandRouter {
|
|
|
150
153
|
async executeStart() {
|
|
151
154
|
const server = this.configManager.getServer();
|
|
152
155
|
if (!server) {
|
|
153
|
-
return colors.failure('No server connected. Use
|
|
156
|
+
return colors.failure('No server connected. Use sync <path> to connect one.');
|
|
154
157
|
}
|
|
155
158
|
if (this.processManager.getActiveServer(server.name)) {
|
|
156
159
|
return colors.warning(`Server "${server.name}" is already running.`);
|
|
@@ -169,7 +172,7 @@ class CommandRouter {
|
|
|
169
172
|
}
|
|
170
173
|
try {
|
|
171
174
|
await this.processManager.startServer(server.name, server.path, resolvedJar, server.ram, this.configManager.getConfig().defaultJavaPath);
|
|
172
|
-
return colors.success(`Server "${server.name}" started. Use
|
|
175
|
+
return colors.success(`Server "${server.name}" started. Use log to watch live logs or console to enter the console.`);
|
|
173
176
|
}
|
|
174
177
|
catch (err) {
|
|
175
178
|
return colors.failure(`Failed to start server: ${err.message}`);
|
|
@@ -436,7 +439,7 @@ class CommandRouter {
|
|
|
436
439
|
// If this says "Not saved", /tunnel will re-claim a new agent every run.
|
|
437
440
|
const hasSecret = !!this.playitManager.getSecret();
|
|
438
441
|
output.push('');
|
|
439
|
-
output.push(`Agent secret: ${hasSecret ? colors.green('Saved (will reuse this agent)') : colors.red('Not saved —
|
|
442
|
+
output.push(`Agent secret: ${hasSecret ? colors.green('Saved (will reuse this agent)') : colors.red('Not saved — tunnel will claim a new agent')}`);
|
|
440
443
|
output.push(colors.gray(`Config file: ${this.configManager.getConfigPath()}`));
|
|
441
444
|
output.push('');
|
|
442
445
|
return output.join('\n');
|
|
@@ -463,7 +466,7 @@ class CommandRouter {
|
|
|
463
466
|
lines.push(` ${colors.green(j.version.padEnd(10))} ${j.path}`);
|
|
464
467
|
}
|
|
465
468
|
lines.push('');
|
|
466
|
-
lines.push(colors.gray('Switch with:
|
|
469
|
+
lines.push(colors.gray('Switch with: java <path>'));
|
|
467
470
|
}
|
|
468
471
|
lines.push('');
|
|
469
472
|
return lines.join('\n');
|
|
@@ -474,7 +477,7 @@ class CommandRouter {
|
|
|
474
477
|
return colors.failure(`No working Java found at "${cleanPath}".`);
|
|
475
478
|
}
|
|
476
479
|
this.configManager.updateSettings({ defaultJavaPath: cleanPath });
|
|
477
|
-
return colors.success(`Java set to "${cleanPath}" (version ${info.version}). It will be used on the next
|
|
480
|
+
return colors.success(`Java set to "${cleanPath}" (version ${info.version}). It will be used on the next start.`);
|
|
478
481
|
}
|
|
479
482
|
/**
|
|
480
483
|
* Executes /update — checks npm for a newer version and prints how to update.
|
|
@@ -541,7 +544,7 @@ class CommandRouter {
|
|
|
541
544
|
}
|
|
542
545
|
catch (err) {
|
|
543
546
|
if (err.message && err.message.includes('NotAllowedWithReadOnly')) {
|
|
544
|
-
return colors.failure('The agent secret is read-only. Run
|
|
547
|
+
return colors.failure('The agent secret is read-only. Run tunnel reset and try again to re-claim it.');
|
|
545
548
|
}
|
|
546
549
|
return colors.failure(`Failed to create tunnel: ${err.message}`);
|
|
547
550
|
}
|
|
@@ -553,7 +556,7 @@ class CommandRouter {
|
|
|
553
556
|
*/
|
|
554
557
|
async executeSetup() {
|
|
555
558
|
if (this.playitManager.getSecret()) {
|
|
556
|
-
return colors.warning('Playit is already set up (agent secret saved). Run
|
|
559
|
+
return colors.warning('Playit is already set up (agent secret saved). Run tunnel reset first if you want to re-claim.');
|
|
557
560
|
}
|
|
558
561
|
try {
|
|
559
562
|
await this.playitManager.ensureSecret({
|
|
@@ -570,7 +573,7 @@ class CommandRouter {
|
|
|
570
573
|
},
|
|
571
574
|
onStatus: (msg) => console.log(colors.info(msg)),
|
|
572
575
|
});
|
|
573
|
-
return colors.success('Playit is set up! You can now run
|
|
576
|
+
return colors.success('Playit is set up! You can now run tunnel java or tunnel bedrock.');
|
|
574
577
|
}
|
|
575
578
|
catch (err) {
|
|
576
579
|
return colors.failure(`Setup failed: ${err.message}`);
|
|
@@ -226,7 +226,7 @@ class ConfigManager {
|
|
|
226
226
|
// a brand-new agent (and orphaning the old one) on the next launch.
|
|
227
227
|
if (!this.isPlayitSecretPersisted(secret)) {
|
|
228
228
|
throw new Error(`Could not persist the playit agent secret to ${CONFIG_PATH}. ` +
|
|
229
|
-
`Check that the folder exists and is writable, then run
|
|
229
|
+
`Check that the folder exists and is writable, then run tunnel again.`);
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
/** Confirms the agent secret is readable back from disk (not just in memory). */
|
package/dist/index.js
CHANGED
|
@@ -164,23 +164,24 @@ async function ensurePlayitSetup() {
|
|
|
164
164
|
// Master list of command templates — single source of truth for both
|
|
165
165
|
// tab-completion and "did you mean" suggestions.
|
|
166
166
|
const COMMAND_LIST = [
|
|
167
|
-
'
|
|
168
|
-
'
|
|
169
|
-
'
|
|
170
|
-
'
|
|
171
|
-
'
|
|
172
|
-
'
|
|
173
|
-
'
|
|
167
|
+
'help', 'start', 'stop', 'restart', 'console', 'log', 'info', 'sync',
|
|
168
|
+
'stats', 'folder', 'properties', 'java',
|
|
169
|
+
'backup create', 'backup list', 'backup restore',
|
|
170
|
+
'plugins list', 'plugins install', 'plugins remove',
|
|
171
|
+
'setup',
|
|
172
|
+
'tunnel java', 'tunnel bedrock', 'tunnel status', 'tunnel log', 'tunnel stop', 'tunnel reset',
|
|
173
|
+
'playit',
|
|
174
|
+
'config', 'clear', 'update', 'tray', 'background', 'exit'
|
|
174
175
|
];
|
|
175
176
|
// Subcommands offered once "<command> " has been typed.
|
|
176
177
|
const SUBCOMMANDS = {
|
|
177
|
-
'
|
|
178
|
-
'
|
|
179
|
-
'
|
|
178
|
+
'tunnel': ['java', 'bedrock', 'status', 'log', 'stop', 'reset'],
|
|
179
|
+
'backup': ['create', 'list', 'restore'],
|
|
180
|
+
'plugins': ['list', 'install', 'remove'],
|
|
180
181
|
};
|
|
181
182
|
/** Returns top-level commands that share a prefix with the typed token. */
|
|
182
183
|
function suggestCommands(token) {
|
|
183
|
-
if (!token
|
|
184
|
+
if (!token)
|
|
184
185
|
return [];
|
|
185
186
|
const tops = Array.from(new Set(COMMAND_LIST.map(c => c.split(' ')[0])));
|
|
186
187
|
return tops.filter(c => c.startsWith(token) && c !== token);
|
|
@@ -196,8 +197,8 @@ function completer(line) {
|
|
|
196
197
|
const parts = lineTrimmed.split(/\s+/);
|
|
197
198
|
const cmd = parts[0];
|
|
198
199
|
const arg = parts.slice(1).join(' ');
|
|
199
|
-
// Completing the command word itself (e.g. "
|
|
200
|
-
if (
|
|
200
|
+
// Completing the command word itself (e.g. "cl" -> "clear")
|
|
201
|
+
if (!line.includes(' ')) {
|
|
201
202
|
const hits = COMMAND_LIST.filter(c => c.startsWith(lineTrimmed));
|
|
202
203
|
return [hits.length ? hits : COMMAND_LIST, line];
|
|
203
204
|
}
|
|
@@ -230,7 +231,8 @@ function loadHistory() {
|
|
|
230
231
|
}
|
|
231
232
|
}
|
|
232
233
|
function saveHistoryLine(line) {
|
|
233
|
-
|
|
234
|
+
const t = line.trim();
|
|
235
|
+
if (!t || t === 'exit' || t === '/exit')
|
|
234
236
|
return;
|
|
235
237
|
try {
|
|
236
238
|
fs.appendFileSync(HISTORY_PATH, `${line.trim()}\n`, 'utf-8');
|
|
@@ -287,6 +289,17 @@ function exitLogView() {
|
|
|
287
289
|
console.log(colors.info('\nReturned to MCPANEL shell.'));
|
|
288
290
|
promptUser();
|
|
289
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Exits the in-place live tunnel-log view.
|
|
294
|
+
*/
|
|
295
|
+
function exitTunnelLogView() {
|
|
296
|
+
if (currentState !== 'TUNNEL_LOG_VIEW')
|
|
297
|
+
return;
|
|
298
|
+
playitManager.unregisterTunnelStream();
|
|
299
|
+
currentState = 'COMMAND';
|
|
300
|
+
console.log(colors.info('\nReturned to MCPANEL shell.'));
|
|
301
|
+
promptUser();
|
|
302
|
+
}
|
|
290
303
|
/**
|
|
291
304
|
* Prompt loop builder
|
|
292
305
|
*/
|
|
@@ -313,7 +326,7 @@ function promptUser() {
|
|
|
313
326
|
rl.setPrompt(colors.bold(`Enter new value for ${propertiesContext.selectedKey}: `));
|
|
314
327
|
rl.prompt();
|
|
315
328
|
}
|
|
316
|
-
else if (currentState === 'CONSOLE' || currentState === 'LOG_VIEW') {
|
|
329
|
+
else if (currentState === 'CONSOLE' || currentState === 'LOG_VIEW' || currentState === 'TUNNEL_LOG_VIEW') {
|
|
317
330
|
// Log/console streaming has no custom prompt.
|
|
318
331
|
rl.setPrompt('');
|
|
319
332
|
}
|
|
@@ -338,7 +351,7 @@ function showPropertiesMenu() {
|
|
|
338
351
|
function startPropertiesEditor() {
|
|
339
352
|
const server = configManager.getServer();
|
|
340
353
|
if (!server) {
|
|
341
|
-
console.log(colors.failure('No server connected. Use
|
|
354
|
+
console.log(colors.failure('No server connected. Use sync <path>.'));
|
|
342
355
|
currentState = 'COMMAND';
|
|
343
356
|
promptUser();
|
|
344
357
|
return;
|
|
@@ -373,7 +386,7 @@ function enterConsoleMode() {
|
|
|
373
386
|
return;
|
|
374
387
|
}
|
|
375
388
|
if (!processManager.getActiveServer(server.name)) {
|
|
376
|
-
console.log(colors.failure(`Server "${server.name}" is not running. Start it first using
|
|
389
|
+
console.log(colors.failure(`Server "${server.name}" is not running. Start it first using start.`));
|
|
377
390
|
currentState = 'COMMAND';
|
|
378
391
|
promptUser();
|
|
379
392
|
return;
|
|
@@ -393,8 +406,8 @@ function enterConsoleMode() {
|
|
|
393
406
|
});
|
|
394
407
|
}
|
|
395
408
|
/**
|
|
396
|
-
* /log —
|
|
397
|
-
*
|
|
409
|
+
* /log — streams live server logs read-only inside THIS terminal (like
|
|
410
|
+
* /console, but without sending commands). Type /back or /exit to return.
|
|
398
411
|
*/
|
|
399
412
|
function handleLogCommand() {
|
|
400
413
|
const server = configManager.getServer();
|
|
@@ -403,7 +416,6 @@ function handleLogCommand() {
|
|
|
403
416
|
return;
|
|
404
417
|
}
|
|
405
418
|
const logPath = logger_1.logger.getServerLogPath(server.name);
|
|
406
|
-
// Ensure the file exists so `tail -f` has something to follow.
|
|
407
419
|
if (!fs.existsSync(logPath)) {
|
|
408
420
|
try {
|
|
409
421
|
fs.writeFileSync(logPath, '', 'utf-8');
|
|
@@ -411,27 +423,41 @@ function handleLogCommand() {
|
|
|
411
423
|
catch { /* ignore */ }
|
|
412
424
|
}
|
|
413
425
|
const running = !!processManager.getActiveServer(server.name);
|
|
414
|
-
const opened = (0, helpers_1.openTerminalTail)(logPath, `MCPANEL Logs - ${server.name}`);
|
|
415
|
-
if (opened) {
|
|
416
|
-
console.log(colors.success('Live server logs opened in a new terminal window.'));
|
|
417
|
-
if (!running) {
|
|
418
|
-
console.log(colors.warning('Server is not running yet — log lines will appear once you /start it.'));
|
|
419
|
-
}
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
// Fallback: stream the logs read-only inside this shell.
|
|
423
|
-
console.log(colors.warning('Could not open a separate terminal window — showing logs here instead.'));
|
|
424
426
|
logViewServer = server.name;
|
|
425
427
|
currentState = 'LOG_VIEW';
|
|
426
|
-
console.log(colors.bold(colors.magenta(`\n--- Live Logs: ${server.name}
|
|
428
|
+
console.log(colors.bold(colors.magenta(`\n--- Live Server Logs: ${server.name} ---`)));
|
|
429
|
+
console.log(colors.gray('Read-only. Type back or exit to return to MCPANEL shell.\n'));
|
|
427
430
|
if (fs.existsSync(logPath)) {
|
|
428
431
|
const logs = fs.readFileSync(logPath, 'utf-8').split('\n');
|
|
429
432
|
process.stdout.write(logs.slice(-30).join('\n') + '\n');
|
|
430
433
|
}
|
|
434
|
+
if (!running) {
|
|
435
|
+
console.log(colors.warning('Server is not running yet — lines will appear once you start it.'));
|
|
436
|
+
}
|
|
431
437
|
processManager.registerConsoleStream(server.name, (data) => {
|
|
432
438
|
process.stdout.write(data);
|
|
433
439
|
});
|
|
434
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* /tunnel log — streams the live playit relay log read-only in THIS terminal.
|
|
443
|
+
* Seeds from tunnel.log, then follows the running relay's output. /back to exit.
|
|
444
|
+
*/
|
|
445
|
+
function enterTunnelLogView() {
|
|
446
|
+
const logPath = logger_1.logger.getTunnelLogPath();
|
|
447
|
+
currentState = 'TUNNEL_LOG_VIEW';
|
|
448
|
+
console.log(colors.bold(colors.magenta('\n--- Live Tunnel Logs (playit relay) ---')));
|
|
449
|
+
console.log(colors.gray('Read-only. Type back or exit to return to MCPANEL shell.\n'));
|
|
450
|
+
if (fs.existsSync(logPath)) {
|
|
451
|
+
const logs = fs.readFileSync(logPath, 'utf-8').split('\n');
|
|
452
|
+
process.stdout.write(logs.slice(-30).join('\n') + '\n');
|
|
453
|
+
}
|
|
454
|
+
if (!playitManager.isAgentRunning()) {
|
|
455
|
+
console.log(colors.warning('Tunnel agent is not running — start it with tunnel java or tunnel bedrock.'));
|
|
456
|
+
}
|
|
457
|
+
playitManager.registerTunnelStream((data) => {
|
|
458
|
+
process.stdout.write(data);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
435
461
|
/**
|
|
436
462
|
* Command line loop orchestrator
|
|
437
463
|
*/
|
|
@@ -444,11 +470,11 @@ async function handleLine(line) {
|
|
|
444
470
|
break;
|
|
445
471
|
case 'WIZARD_SYNC_PATH': {
|
|
446
472
|
if (!trimmed) {
|
|
447
|
-
console.log(colors.failure('Please enter a folder path (or type
|
|
473
|
+
console.log(colors.failure('Please enter a folder path (or type exit to quit).'));
|
|
448
474
|
promptUser();
|
|
449
475
|
break;
|
|
450
476
|
}
|
|
451
|
-
if (trimmed === '/exit') {
|
|
477
|
+
if (trimmed === 'exit' || trimmed === '/exit') {
|
|
452
478
|
process.exit(0);
|
|
453
479
|
}
|
|
454
480
|
try {
|
|
@@ -526,11 +552,17 @@ async function handleLine(line) {
|
|
|
526
552
|
}
|
|
527
553
|
break;
|
|
528
554
|
case 'LOG_VIEW':
|
|
529
|
-
// Read-only: only
|
|
530
|
-
if (trimmed === '/exit' || trimmed === '/back') {
|
|
555
|
+
// Read-only: only back or exit leaves; everything else is ignored.
|
|
556
|
+
if (trimmed === 'exit' || trimmed === 'back' || trimmed === '/exit' || trimmed === '/back') {
|
|
531
557
|
exitLogView();
|
|
532
558
|
}
|
|
533
559
|
break;
|
|
560
|
+
case 'TUNNEL_LOG_VIEW':
|
|
561
|
+
// Read-only: only back or exit leaves; everything else is ignored.
|
|
562
|
+
if (trimmed === 'exit' || trimmed === 'back' || trimmed === '/exit' || trimmed === '/back') {
|
|
563
|
+
exitTunnelLogView();
|
|
564
|
+
}
|
|
565
|
+
break;
|
|
534
566
|
}
|
|
535
567
|
}
|
|
536
568
|
/**
|
|
@@ -542,18 +574,14 @@ async function handleCommandState(line) {
|
|
|
542
574
|
return;
|
|
543
575
|
}
|
|
544
576
|
const parts = line.split(/\s+/);
|
|
545
|
-
|
|
577
|
+
// Commands have no leading slash, but tolerate one for muscle memory / old history.
|
|
578
|
+
const cmd = parts[0].toLowerCase().replace(/^\//, '');
|
|
546
579
|
const args = parts.slice(1);
|
|
547
|
-
if (!line.startsWith('/')) {
|
|
548
|
-
console.log(colors.failure(`Unknown command: "${line}". All commands must start with "/". Type /help for assistance.`));
|
|
549
|
-
promptUser();
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
580
|
switch (cmd) {
|
|
553
|
-
case '
|
|
581
|
+
case 'help':
|
|
554
582
|
console.log(router.getHelpText());
|
|
555
583
|
break;
|
|
556
|
-
case '
|
|
584
|
+
case 'clear':
|
|
557
585
|
try {
|
|
558
586
|
fs.writeFileSync(HISTORY_PATH, '', 'utf-8');
|
|
559
587
|
if (rl) {
|
|
@@ -568,8 +596,8 @@ async function handleCommandState(line) {
|
|
|
568
596
|
console.log(colors.failure(`Failed to clear: ${err.message}`));
|
|
569
597
|
}
|
|
570
598
|
break;
|
|
571
|
-
case '
|
|
572
|
-
case '
|
|
599
|
+
case 'tray':
|
|
600
|
+
case 'background': {
|
|
573
601
|
console.log(colors.info('\nPutting MCPANEL in the background...'));
|
|
574
602
|
console.log(colors.gray('The terminal window will be hidden. Use the system tray icon to restore it.'));
|
|
575
603
|
const success = trayManager.hideConsole();
|
|
@@ -578,7 +606,7 @@ async function handleCommandState(line) {
|
|
|
578
606
|
}
|
|
579
607
|
break;
|
|
580
608
|
}
|
|
581
|
-
case '
|
|
609
|
+
case 'exit':
|
|
582
610
|
logger_1.logger.info('Exiting MCPANEL manager.');
|
|
583
611
|
playitManager.stopTunnel();
|
|
584
612
|
console.log(colors.cyan('\nStopping the server if running...'));
|
|
@@ -591,57 +619,60 @@ async function handleCommandState(line) {
|
|
|
591
619
|
console.log(colors.success('Goodbye!'));
|
|
592
620
|
process.exit(0);
|
|
593
621
|
break;
|
|
594
|
-
case '
|
|
622
|
+
case 'sync':
|
|
595
623
|
if (args.length === 0) {
|
|
596
|
-
console.log(colors.failure('Syntax:
|
|
624
|
+
console.log(colors.failure('Syntax: sync <path-to-server-folder>'));
|
|
597
625
|
}
|
|
598
626
|
else {
|
|
599
627
|
console.log(router.executeSync(args.join(' ')));
|
|
600
628
|
}
|
|
601
629
|
break;
|
|
602
|
-
case '
|
|
603
|
-
case '
|
|
630
|
+
case 'info':
|
|
631
|
+
case 'path':
|
|
604
632
|
console.log(router.executeInfo());
|
|
605
633
|
break;
|
|
606
|
-
case '
|
|
634
|
+
case 'start':
|
|
607
635
|
console.log(colors.cyan('Starting server...'));
|
|
608
636
|
console.log(await router.executeStart());
|
|
609
637
|
break;
|
|
610
|
-
case '
|
|
638
|
+
case 'stop':
|
|
611
639
|
console.log(colors.cyan('Stopping server...'));
|
|
612
640
|
console.log(await router.executeStop());
|
|
613
641
|
break;
|
|
614
|
-
case '
|
|
642
|
+
case 'restart':
|
|
615
643
|
console.log(colors.cyan('Restarting server...'));
|
|
616
644
|
console.log(await router.executeRestart());
|
|
617
645
|
break;
|
|
618
|
-
case '
|
|
646
|
+
case 'console':
|
|
619
647
|
enterConsoleMode();
|
|
620
648
|
break;
|
|
621
|
-
case '
|
|
649
|
+
case 'log':
|
|
622
650
|
handleLogCommand();
|
|
623
651
|
break;
|
|
624
|
-
case '
|
|
652
|
+
case 'playit':
|
|
653
|
+
enterTunnelLogView();
|
|
654
|
+
break;
|
|
655
|
+
case 'stats':
|
|
625
656
|
console.log(await router.executeStats());
|
|
626
657
|
break;
|
|
627
|
-
case '
|
|
658
|
+
case 'folder':
|
|
628
659
|
console.log(router.executeFolder());
|
|
629
660
|
break;
|
|
630
|
-
case '
|
|
661
|
+
case 'properties':
|
|
631
662
|
startPropertiesEditor();
|
|
632
663
|
break;
|
|
633
|
-
case '
|
|
664
|
+
case 'java':
|
|
634
665
|
console.log(router.executeJava(args.length ? args.join(' ') : undefined));
|
|
635
666
|
break;
|
|
636
|
-
case '
|
|
667
|
+
case 'update':
|
|
637
668
|
console.log(await router.executeUpdate());
|
|
638
669
|
break;
|
|
639
|
-
case '
|
|
670
|
+
case 'config':
|
|
640
671
|
console.log(router.executeConfig());
|
|
641
672
|
break;
|
|
642
|
-
case '
|
|
673
|
+
case 'backup':
|
|
643
674
|
if (args.length === 0) {
|
|
644
|
-
console.log(colors.failure('Syntax:
|
|
675
|
+
console.log(colors.failure('Syntax: backup [create|list|restore]'));
|
|
645
676
|
}
|
|
646
677
|
else if (args[0].toLowerCase() === 'create') {
|
|
647
678
|
console.log(router.executeBackupCreate());
|
|
@@ -651,44 +682,44 @@ async function handleCommandState(line) {
|
|
|
651
682
|
}
|
|
652
683
|
else if (args[0].toLowerCase() === 'restore') {
|
|
653
684
|
if (!args[1])
|
|
654
|
-
console.log(colors.failure('Syntax:
|
|
685
|
+
console.log(colors.failure('Syntax: backup restore <backup-id>'));
|
|
655
686
|
else
|
|
656
687
|
console.log(router.executeBackupRestore(args[1]));
|
|
657
688
|
}
|
|
658
689
|
else {
|
|
659
|
-
console.log(colors.failure('Syntax:
|
|
690
|
+
console.log(colors.failure('Syntax: backup [create|list|restore]'));
|
|
660
691
|
}
|
|
661
692
|
break;
|
|
662
|
-
case '
|
|
693
|
+
case 'plugins':
|
|
663
694
|
if (args.length === 0) {
|
|
664
|
-
console.log(colors.failure('Syntax:
|
|
695
|
+
console.log(colors.failure('Syntax: plugins [list|install|remove]'));
|
|
665
696
|
}
|
|
666
697
|
else if (args[0].toLowerCase() === 'list') {
|
|
667
698
|
console.log(router.executePluginsList());
|
|
668
699
|
}
|
|
669
700
|
else if (args[0].toLowerCase() === 'install') {
|
|
670
701
|
if (!args[1])
|
|
671
|
-
console.log(colors.failure('Syntax:
|
|
702
|
+
console.log(colors.failure('Syntax: plugins install <plugin-url>'));
|
|
672
703
|
else
|
|
673
704
|
console.log(await router.executePluginsInstall(args[1]));
|
|
674
705
|
}
|
|
675
706
|
else if (args[0].toLowerCase() === 'remove') {
|
|
676
707
|
if (!args[1])
|
|
677
|
-
console.log(colors.failure('Syntax:
|
|
708
|
+
console.log(colors.failure('Syntax: plugins remove <plugin-name>'));
|
|
678
709
|
else
|
|
679
710
|
console.log(router.executePluginsRemove(args[1]));
|
|
680
711
|
}
|
|
681
712
|
else {
|
|
682
|
-
console.log(colors.failure('Syntax:
|
|
713
|
+
console.log(colors.failure('Syntax: plugins [list|install|remove]'));
|
|
683
714
|
}
|
|
684
715
|
break;
|
|
685
|
-
case '
|
|
716
|
+
case 'setup':
|
|
686
717
|
console.log(await router.executeSetup());
|
|
687
718
|
break;
|
|
688
|
-
case '
|
|
719
|
+
case 'tunnel': {
|
|
689
720
|
const sub = (args[0] || '').toLowerCase();
|
|
690
721
|
if (!sub) {
|
|
691
|
-
console.log(colors.failure('Syntax:
|
|
722
|
+
console.log(colors.failure('Syntax: tunnel [java|bedrock|status|log|stop|reset]'));
|
|
692
723
|
}
|
|
693
724
|
else if (sub === 'java' || sub === 'bedrock') {
|
|
694
725
|
console.log(await router.executeTunnelCreate(sub));
|
|
@@ -708,11 +739,14 @@ async function handleCommandState(line) {
|
|
|
708
739
|
else if (sub === 'status') {
|
|
709
740
|
console.log(router.executeTunnelStatus());
|
|
710
741
|
}
|
|
742
|
+
else if (sub === 'log') {
|
|
743
|
+
enterTunnelLogView();
|
|
744
|
+
}
|
|
711
745
|
else if (sub === 'reset') {
|
|
712
746
|
console.log(await router.executeTunnelReset());
|
|
713
747
|
}
|
|
714
748
|
else {
|
|
715
|
-
console.log(colors.failure('Syntax:
|
|
749
|
+
console.log(colors.failure('Syntax: tunnel [java|bedrock|status|log|stop|reset]'));
|
|
716
750
|
}
|
|
717
751
|
break;
|
|
718
752
|
}
|
|
@@ -722,7 +756,7 @@ async function handleCommandState(line) {
|
|
|
722
756
|
console.log(colors.failure(`Unknown command: "${cmd}".`) + ' ' + colors.gray(`Did you mean: ${suggestions.join(', ')} ?`));
|
|
723
757
|
}
|
|
724
758
|
else {
|
|
725
|
-
console.log(colors.failure(`Unknown command: "${cmd}". Type
|
|
759
|
+
console.log(colors.failure(`Unknown command: "${cmd}". Type help for available commands.`));
|
|
726
760
|
}
|
|
727
761
|
break;
|
|
728
762
|
}
|
|
@@ -743,7 +777,7 @@ async function finishStartup() {
|
|
|
743
777
|
catch {
|
|
744
778
|
// Continue despite download failure (tunnel will fail until resolved).
|
|
745
779
|
}
|
|
746
|
-
console.log('\nType ' + chalk_1.default.cyan('
|
|
780
|
+
console.log('\nType ' + chalk_1.default.cyan('help') + ' for available commands\n');
|
|
747
781
|
currentState = 'COMMAND';
|
|
748
782
|
promptUser();
|
|
749
783
|
}
|
|
@@ -792,7 +826,7 @@ async function main() {
|
|
|
792
826
|
exitLogView();
|
|
793
827
|
}
|
|
794
828
|
else if (currentState === 'WIZARD_SYNC_PATH') {
|
|
795
|
-
console.log(colors.info('\nType
|
|
829
|
+
console.log(colors.info('\nType exit to quit, or enter a server folder path.'));
|
|
796
830
|
promptUser();
|
|
797
831
|
}
|
|
798
832
|
else if (currentState !== 'COMMAND') {
|
|
@@ -801,7 +835,7 @@ async function main() {
|
|
|
801
835
|
promptUser();
|
|
802
836
|
}
|
|
803
837
|
else {
|
|
804
|
-
console.log(colors.info('\nType
|
|
838
|
+
console.log(colors.info('\nType exit to exit MCPANEL.'));
|
|
805
839
|
promptUser();
|
|
806
840
|
}
|
|
807
841
|
});
|
|
@@ -58,6 +58,8 @@ class PlayitManager {
|
|
|
58
58
|
playitProcess = null;
|
|
59
59
|
claimProcess = null;
|
|
60
60
|
tunnelStatus;
|
|
61
|
+
// Optional live consumer of relay output, used by the inline `/tunnel log` view.
|
|
62
|
+
tunnelLogCallback = null;
|
|
61
63
|
constructor(configManager) {
|
|
62
64
|
this.configManager = configManager;
|
|
63
65
|
this.tunnelStatus = this.offlineStatus();
|
|
@@ -256,6 +258,56 @@ class PlayitManager {
|
|
|
256
258
|
const tunnels = (rd?.tunnels || []);
|
|
257
259
|
return tunnels.find((t) => t.proto === proto) || null;
|
|
258
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Reads the server's actual listen port from server.properties so the tunnel
|
|
263
|
+
* forwards traffic to where the server is really bound. Falls back to the
|
|
264
|
+
* protocol defaults (Java 25565 / Bedrock 19132) when it can't be determined.
|
|
265
|
+
*/
|
|
266
|
+
getLocalPort(type) {
|
|
267
|
+
const fallback = type === 'java' ? 25565 : 19132;
|
|
268
|
+
try {
|
|
269
|
+
const server = this.configManager.getServer();
|
|
270
|
+
if (!server)
|
|
271
|
+
return fallback;
|
|
272
|
+
const txt = fs.readFileSync(path.join(server.path, 'server.properties'), 'utf-8');
|
|
273
|
+
const m = txt.match(/^\s*server-port\s*=\s*(\d{1,5})\s*$/m);
|
|
274
|
+
if (m) {
|
|
275
|
+
const p = parseInt(m[1], 10);
|
|
276
|
+
if (p > 0 && p < 65536)
|
|
277
|
+
return p;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch { /* fall back to default */ }
|
|
281
|
+
return fallback;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Self-healing: if a reused tunnel's origin still points at the wrong local
|
|
285
|
+
* port (e.g. created when the server used a different server-port, or with the
|
|
286
|
+
* old hardcoded default), repoint it to the server's current port via the API.
|
|
287
|
+
* Returns the port the tunnel now forwards to. No-op when already correct.
|
|
288
|
+
*/
|
|
289
|
+
async reconcileTunnelPort(tunnel, type, secret, callbacks = {}) {
|
|
290
|
+
const desired = this.getLocalPort(type);
|
|
291
|
+
const current = Number(tunnel?.local_port);
|
|
292
|
+
const currentIp = tunnel?.local_ip || '127.0.0.1';
|
|
293
|
+
if (current === desired && currentIp === '127.0.0.1')
|
|
294
|
+
return desired;
|
|
295
|
+
callbacks.onStatus?.(`Adjusting tunnel to match your server port (${current || '?'} → ${desired})...`);
|
|
296
|
+
// playit's /tunnels/update requires the full origin: tunnel_id, local_ip,
|
|
297
|
+
// local_port and the (required) enabled flag. agent_id is intentionally
|
|
298
|
+
// omitted — sending a different one is rejected (ChangingAgentIdNotAllowed),
|
|
299
|
+
// and omitting it keeps the tunnel on its current agent.
|
|
300
|
+
await this.apiPost('/tunnels/update', {
|
|
301
|
+
tunnel_id: tunnel.id,
|
|
302
|
+
local_ip: '127.0.0.1',
|
|
303
|
+
local_port: desired,
|
|
304
|
+
enabled: tunnel.disabled == null,
|
|
305
|
+
}, secret);
|
|
306
|
+
tunnel.local_port = desired;
|
|
307
|
+
tunnel.local_ip = '127.0.0.1';
|
|
308
|
+
logger_1.logger.info(`Reconciled playit tunnel ${tunnel.id} local port ${current} -> ${desired}`);
|
|
309
|
+
return desired;
|
|
310
|
+
}
|
|
259
311
|
/** Creates a new tunnel via the API (replaces the broken `tunnels prepare` CLI). */
|
|
260
312
|
async createApiTunnel(type, agentId, secret) {
|
|
261
313
|
const body = {
|
|
@@ -268,7 +320,7 @@ class PlayitManager {
|
|
|
268
320
|
data: {
|
|
269
321
|
agent_id: agentId,
|
|
270
322
|
local_ip: '127.0.0.1',
|
|
271
|
-
local_port: type
|
|
323
|
+
local_port: this.getLocalPort(type),
|
|
272
324
|
},
|
|
273
325
|
},
|
|
274
326
|
enabled: true,
|
|
@@ -331,12 +383,12 @@ class PlayitManager {
|
|
|
331
383
|
}
|
|
332
384
|
if (status === 'UserRejected') {
|
|
333
385
|
this.tunnelStatus.claimUrl = null;
|
|
334
|
-
throw new Error('Claim was rejected in the browser. Run
|
|
386
|
+
throw new Error('Claim was rejected in the browser. Run setup to try again.');
|
|
335
387
|
}
|
|
336
388
|
}
|
|
337
389
|
if (!approved) {
|
|
338
390
|
this.tunnelStatus.claimUrl = null;
|
|
339
|
-
throw new Error('Timed out waiting for approval. Open the link, click Approve, then run
|
|
391
|
+
throw new Error('Timed out waiting for approval. Open the link, click Approve, then run setup again.');
|
|
340
392
|
}
|
|
341
393
|
// Exchange the approved code for the 64-char agent secret.
|
|
342
394
|
callbacks.onStatus?.('Approved! Retrieving your agent secret...');
|
|
@@ -351,7 +403,7 @@ class PlayitManager {
|
|
|
351
403
|
}
|
|
352
404
|
if (!secret) {
|
|
353
405
|
this.tunnelStatus.claimUrl = null;
|
|
354
|
-
throw new Error('Could not retrieve the agent secret after approval. Run
|
|
406
|
+
throw new Error('Could not retrieve the agent secret after approval. Run setup to try again.');
|
|
355
407
|
}
|
|
356
408
|
this.configManager.setPlayitSecret(secret);
|
|
357
409
|
this.tunnelStatus.claimUrl = null;
|
|
@@ -391,7 +443,17 @@ class PlayitManager {
|
|
|
391
443
|
tunnel = this.findTunnel(rd, type);
|
|
392
444
|
}
|
|
393
445
|
if (!tunnel) {
|
|
394
|
-
throw new Error('Tunnel was created but no public address appeared yet. Try
|
|
446
|
+
throw new Error('Tunnel was created but no public address appeared yet. Try tunnel status shortly.');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
// Reusing an existing tunnel: make sure it still forwards to the server's
|
|
451
|
+
// current port, so the user never has to touch port config by hand.
|
|
452
|
+
try {
|
|
453
|
+
await this.reconcileTunnelPort(tunnel, type, secret, callbacks);
|
|
454
|
+
}
|
|
455
|
+
catch (err) {
|
|
456
|
+
logger_1.logger.warn(`Could not auto-adjust tunnel port: ${err.message}`);
|
|
395
457
|
}
|
|
396
458
|
}
|
|
397
459
|
const { address, port } = this.tunnelAddress(tunnel);
|
|
@@ -428,7 +490,7 @@ class PlayitManager {
|
|
|
428
490
|
}
|
|
429
491
|
}
|
|
430
492
|
throw new Error(`${lastErr?.message || 'AgentVersionTooOld'} — the playit agent did not register in time. ` +
|
|
431
|
-
`Make sure the server can reach playit.gg, then try
|
|
493
|
+
`Make sure the server can reach playit.gg, then try tunnel again.`);
|
|
432
494
|
}
|
|
433
495
|
/** Spawns the long-running daemon that relays tunnel traffic. */
|
|
434
496
|
startAgent(secret) {
|
|
@@ -456,11 +518,13 @@ class PlayitManager {
|
|
|
456
518
|
const chunk = stripAnsi(d.toString());
|
|
457
519
|
logger_1.logger.logTunnel(`[stdout] ${chunk.trim()}`);
|
|
458
520
|
this.parsePlayitOutput(chunk);
|
|
521
|
+
this.tunnelLogCallback?.(chunk);
|
|
459
522
|
});
|
|
460
523
|
this.playitProcess.stderr?.on('data', (d) => {
|
|
461
524
|
const chunk = stripAnsi(d.toString());
|
|
462
525
|
logger_1.logger.logTunnel(`[stderr] ${chunk.trim()}`);
|
|
463
526
|
this.parsePlayitOutput(chunk);
|
|
527
|
+
this.tunnelLogCallback?.(chunk);
|
|
464
528
|
});
|
|
465
529
|
this.playitProcess.on('close', (code) => {
|
|
466
530
|
logger_1.logger.logTunnel(`Playit relay exited with code ${code}`);
|
|
@@ -502,6 +566,18 @@ class PlayitManager {
|
|
|
502
566
|
getStatus() {
|
|
503
567
|
return this.tunnelStatus;
|
|
504
568
|
}
|
|
569
|
+
/** True when the relay daemon process is currently running. */
|
|
570
|
+
isAgentRunning() {
|
|
571
|
+
return this.playitProcess !== null;
|
|
572
|
+
}
|
|
573
|
+
/** Streams live relay output (stdout/stderr) to the given consumer. */
|
|
574
|
+
registerTunnelStream(cb) {
|
|
575
|
+
this.tunnelLogCallback = cb;
|
|
576
|
+
}
|
|
577
|
+
/** Stops streaming live relay output. */
|
|
578
|
+
unregisterTunnelStream() {
|
|
579
|
+
this.tunnelLogCallback = null;
|
|
580
|
+
}
|
|
505
581
|
/** Clears the saved secret so the agent can be re-claimed from scratch. */
|
|
506
582
|
async resetSecret() {
|
|
507
583
|
this.stopTunnel();
|
package/dist/utils/logger.js
CHANGED
|
@@ -77,6 +77,10 @@ exports.logger = {
|
|
|
77
77
|
// Return path to the runtime console log for the server
|
|
78
78
|
return path.join(LOGS_DIR, `server-${serverName.toLowerCase()}.log`);
|
|
79
79
|
},
|
|
80
|
+
getTunnelLogPath() {
|
|
81
|
+
ensureLogsDirExists();
|
|
82
|
+
return path.join(LOGS_DIR, 'tunnel.log');
|
|
83
|
+
},
|
|
80
84
|
writeServerConsoleLog(serverName, data) {
|
|
81
85
|
ensureLogsDirExists();
|
|
82
86
|
const filePath = path.join(LOGS_DIR, `server-${serverName.toLowerCase()}.log`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@woopsy/mcpanel",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "MCPANEL — a terminal-based, single-server Minecraft server manager with an Arch/neofetch-style UI, live logs, backups, plugins and Playit.gg tunnels.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|