@woopsy/mcpanel 2.1.0 → 2.1.2

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.
@@ -0,0 +1,52 @@
1
+ <svg width="1200" height="320" viewBox="0 0 1200 320" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="MCPANEL - Terminal Minecraft Server Manager">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#0d1117"/>
5
+ <stop offset="1" stop-color="#10231a"/>
6
+ </linearGradient>
7
+ <linearGradient id="title" x1="0" y1="0" x2="1" y2="0">
8
+ <stop offset="0" stop-color="#5eead4"/>
9
+ <stop offset="0.5" stop-color="#34d399"/>
10
+ <stop offset="1" stop-color="#22c55e"/>
11
+ </linearGradient>
12
+ </defs>
13
+
14
+ <!-- backdrop -->
15
+ <rect width="1200" height="320" rx="18" fill="url(#bg)"/>
16
+ <rect x="1" y="1" width="1198" height="318" rx="17" fill="none" stroke="#1f6f4d" stroke-opacity="0.5"/>
17
+
18
+ <!-- terminal traffic lights -->
19
+ <circle cx="42" cy="40" r="8" fill="#ff5f56"/>
20
+ <circle cx="68" cy="40" r="8" fill="#ffbd2e"/>
21
+ <circle cx="94" cy="40" r="8" fill="#27c93f"/>
22
+ <text x="120" y="46" font-family="'JetBrains Mono','Fira Code',Consolas,monospace" font-size="16" fill="#7d8590">mcpanel — minecraft server manager</text>
23
+
24
+ <!-- creeper face accent -->
25
+ <g transform="translate(70,110)">
26
+ <rect width="150" height="150" rx="8" fill="#22c55e"/>
27
+ <rect width="150" height="150" rx="8" fill="#000000" opacity="0.08"/>
28
+ <rect x="28" y="36" width="34" height="34" fill="#0d1117"/>
29
+ <rect x="88" y="36" width="34" height="34" fill="#0d1117"/>
30
+ <rect x="58" y="74" width="34" height="40" fill="#0d1117"/>
31
+ <rect x="28" y="100" width="30" height="34" fill="#0d1117"/>
32
+ <rect x="92" y="100" width="30" height="34" fill="#0d1117"/>
33
+ </g>
34
+
35
+ <!-- title -->
36
+ <text x="270" y="170" font-family="'Segoe UI',Arial,sans-serif" font-size="96" font-weight="800" letter-spacing="6" fill="url(#title)">MCPANEL</text>
37
+ <text x="274" y="212" font-family="'JetBrains Mono','Fira Code',Consolas,monospace" font-size="24" fill="#9ca3af">Terminal Minecraft Server Manager</text>
38
+
39
+ <!-- prompt line -->
40
+ <text x="274" y="262" font-family="'JetBrains Mono','Fira Code',Consolas,monospace" font-size="22" fill="#34d399">mcpanel&gt; <tspan fill="#e5e7eb">/start</tspan> <tspan fill="#6b7280">·</tspan> <tspan fill="#e5e7eb">/log</tspan> <tspan fill="#6b7280">·</tspan> <tspan fill="#e5e7eb">/backup</tspan> <tspan fill="#6b7280">·</tspan> <tspan fill="#e5e7eb">/tunnel</tspan></text>
41
+
42
+ <!-- neofetch palette strip -->
43
+ <g transform="translate(274,286)">
44
+ <rect x="0" y="0" width="34" height="16" fill="#ef4444"/>
45
+ <rect x="34" y="0" width="34" height="16" fill="#eab308"/>
46
+ <rect x="68" y="0" width="34" height="16" fill="#22c55e"/>
47
+ <rect x="102" y="0" width="34" height="16" fill="#06b6d4"/>
48
+ <rect x="136" y="0" width="34" height="16" fill="#3b82f6"/>
49
+ <rect x="170" y="0" width="34" height="16" fill="#a855f7"/>
50
+ <rect x="204" y="0" width="34" height="16" fill="#e5e7eb"/>
51
+ </g>
52
+ </svg>
Binary file
Binary file
@@ -80,7 +80,8 @@ class ConfigManager {
80
80
  */
81
81
  load() {
82
82
  if (!fs.existsSync(CONFIG_PATH)) {
83
- this.config = { ...DEFAULT_CONFIG };
83
+ this.config = { ...DEFAULT_CONFIG, playitSettings: {} };
84
+ this.migrateLegacyConfig();
84
85
  this.save();
85
86
  return;
86
87
  }
@@ -121,6 +122,9 @@ class ConfigManager {
121
122
  server,
122
123
  externalBackups: parsed.externalBackups || [],
123
124
  };
125
+ // Recover an already-claimed playit secret from the pre-2.0 config
126
+ // location before persisting (no-op once a secret is present here).
127
+ this.migrateLegacyConfig();
124
128
  // Persist the migrated shape so the legacy keys are cleaned up on disk.
125
129
  this.save();
126
130
  }
@@ -131,6 +135,42 @@ class ConfigManager {
131
135
  this.save();
132
136
  }
133
137
  }
138
+ /**
139
+ * Recovers settings written by versions <2.0, which stored config.json next
140
+ * to the app (`APP_ROOT/config.json`) instead of in `~/.mcpanel`. Without
141
+ * this, an already-claimed playit agent secret is invisible to the new
142
+ * location, so the agent gets re-claimed in the browser on every launch.
143
+ *
144
+ * Only runs when the current config has no playit secret, and never
145
+ * overwrites values already present in the new location.
146
+ */
147
+ migrateLegacyConfig() {
148
+ const legacyPath = path.join(exports.APP_ROOT, 'config.json');
149
+ if (legacyPath === CONFIG_PATH)
150
+ return; // same file — nothing to migrate
151
+ if (this.config.playitSettings.secret)
152
+ return; // already linked
153
+ if (!fs.existsSync(legacyPath))
154
+ return;
155
+ try {
156
+ const legacy = JSON.parse(fs.readFileSync(legacyPath, 'utf-8'));
157
+ if (legacy?.playitSettings?.secret) {
158
+ // Bring the secret + last-known tunnel forward so the existing agent
159
+ // and tunnel are reused instead of re-claimed/recreated.
160
+ this.config.playitSettings = {
161
+ ...legacy.playitSettings,
162
+ ...this.config.playitSettings,
163
+ };
164
+ }
165
+ // Adopt the legacy server only if none is synced in the new location.
166
+ if (!this.config.server && legacy.server && typeof legacy.server === 'object') {
167
+ this.config.server = legacy.server;
168
+ }
169
+ }
170
+ catch {
171
+ // Unreadable/corrupt legacy config — ignore and continue with defaults.
172
+ }
173
+ }
134
174
  /**
135
175
  * Saves the current config memory state to disk
136
176
  */
@@ -134,6 +134,10 @@ class TrayManager {
134
134
  systray = null;
135
135
  activeHandle = null;
136
136
  isInitialized = false;
137
+ // The tray runs a separate native helper over a stdin pipe. If that helper
138
+ // dies, writes to its pipe raise an async EPIPE. The tray is best-effort, so
139
+ // this flag lets us stop writing and degrade to CLI-only instead of crashing.
140
+ trayAlive = false;
137
141
  // Menu item state tracking
138
142
  itemShow = { title: 'Open Console', tooltip: 'Restore terminal window', enabled: true };
139
143
  itemHide = { title: 'Hide Console', tooltip: 'Hide terminal window from taskbar', enabled: true };
@@ -154,12 +158,22 @@ class TrayManager {
154
158
  if (this.isInitialized)
155
159
  return true;
156
160
  const osType = (0, helpers_1.detectOS)();
157
- const iconFile = osType === 'Windows' || osType === 'WSL'
161
+ const iconCandidate = osType === 'Windows' || osType === 'WSL'
158
162
  ? path.join(configManager_1.APP_ROOT, 'assets', 'logo.ico')
159
163
  : path.join(configManager_1.APP_ROOT, 'assets', 'logo.png');
164
+ // systray2 only base64-encodes the icon if the file exists; otherwise it
165
+ // ships the raw path, which the native helper can't decode and then exits.
166
+ // Omit a missing icon so the helper stays alive (CLI works without one).
167
+ const iconFile = fs.existsSync(iconCandidate) ? iconCandidate : undefined;
168
+ if (!iconFile) {
169
+ logger_1.logger.warn(`Tray icon not found at ${iconCandidate}; starting tray without an icon.`);
170
+ }
160
171
  try {
161
172
  this.systray = new WSLSysTray({
162
173
  menu: {
174
+ // May be undefined when the icon file is absent; systray2 tolerates
175
+ // this at runtime (it pathExists-checks before encoding) — the cast
176
+ // just satisfies the `Conf` type, which declares icon as required.
163
177
  icon: iconFile,
164
178
  title: 'MCPANEL',
165
179
  tooltip: 'MCPANEL Server Manager',
@@ -185,6 +199,8 @@ class TrayManager {
185
199
  });
186
200
  await this.systray.ready();
187
201
  this.isInitialized = true;
202
+ this.trayAlive = true;
203
+ this.attachTrayGuards();
188
204
  logger_1.logger.info('System tray initialized successfully.');
189
205
  this.updateMenu();
190
206
  return true;
@@ -196,12 +212,35 @@ class TrayManager {
196
212
  return false;
197
213
  }
198
214
  }
215
+ /**
216
+ * Attaches error/exit listeners to the native tray helper so a dead helper
217
+ * (e.g. EPIPE when writing to its closed stdin) degrades to CLI-only mode
218
+ * instead of throwing an unhandled 'error' event that crashes the process.
219
+ */
220
+ attachTrayGuards() {
221
+ const proc = this.systray?._process;
222
+ if (!proc)
223
+ return;
224
+ const onDead = (err) => {
225
+ if (this.trayAlive) {
226
+ const detail = err ? ` (${err.code || err.message})` : '';
227
+ logger_1.logger.warn(`System tray helper stopped; continuing in CLI-only mode.${detail}`);
228
+ }
229
+ this.trayAlive = false;
230
+ };
231
+ // The load-bearing handler: without an 'error' listener, an EPIPE on the
232
+ // helper's stdin is emitted as an unhandled 'error' event and crashes Node.
233
+ proc.stdin?.on('error', onDead);
234
+ proc.on('error', onDead);
235
+ proc.on('exit', () => onDead());
236
+ proc.on('close', () => onDead());
237
+ }
199
238
  /**
200
239
  * Dynamically updates the titles and states of the tray menu items
201
240
  */
202
241
  updateMenu() {
203
242
  const tray = this.systray;
204
- if (!tray || !this.isInitialized)
243
+ if (!tray || !this.isInitialized || !this.trayAlive)
205
244
  return;
206
245
  const server = this.configManager.getServer();
207
246
  const running = server ? !!this.processManager.getActiveServer(server.name) : false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@woopsy/mcpanel",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
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": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
+ "assets",
11
12
  "README.md",
12
13
  "LICENSE"
13
14
  ],