Skip to content

Commit 1b66291

Browse files
committed
Critical Fixes: Better Install Status, Smart SSH Discovery, Improved Log Sync, and Tunnel Error Reporting
1 parent 4f74b7a commit 1b66291

3 files changed

Lines changed: 89 additions & 24 deletions

File tree

backend/api_server.py

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,20 @@ async def delete_server(server_id: str, delete_files: bool = False):
429429

430430
@app.get("/status")
431431
def get_status():
432-
if not state or not state.server_handler:
433-
return {"status": "not_configured"}
432+
if not state:
433+
return {"status": "offline", "cpu": 0, "ram": 0, "players": 0}
434+
435+
# If we are installing, return 'starting' so the UI knows we are busy
436+
installing = getattr(state, 'install_progress', 0) > 0 and getattr(state, 'install_progress', 0) < 100
437+
438+
if not state.server_handler:
439+
return {
440+
"status": "starting" if installing else "not_configured",
441+
"cpu": 0,
442+
"ram": 0,
443+
"players": 0,
444+
"recent_logs": state.log_history[-50:]
445+
}
434446

435447
stats = state.server_handler.get_stats()
436448

@@ -439,18 +451,21 @@ def get_status():
439451
return {
440452
"status": state.server_handler.get_status(),
441453
"pid": state.server_handler.get_pid(),
454+
"server_id": state.server_handler.server_id,
442455
"server_type": state.server_handler.server_type,
443456
"minecraft_version": state.server_handler.minecraft_version,
444457
"cpu": stats["cpu"],
445458
"ram": stats["ram"],
446459
"players": players_count,
447460
"max_players": state.server_handler.get_max_players(),
448461
"online_players": online_players,
449-
"online_players": online_players,
450462
"uptime": stats["uptime"],
451-
# Return last 50 lines for the mini console polling fallback
452-
"recent_logs": state.log_history[-50:] if state else [],
453-
"shutdown_info": state.server_handler.get_shutdown_info()
463+
"recent_logs": state.log_history[-50:],
464+
"shutdown_info": state.server_handler.get_shutdown_info(),
465+
"tunnel": {
466+
"active": state.tunnel_process is not None and state.tunnel_process.poll() is None,
467+
"address": state.tunnel_address
468+
}
454469
}
455470

456471
@app.post("/server/open-folder")
@@ -1337,6 +1352,40 @@ def start_tunnel(request: Request, region: str = Query("eu")):
13371352
state.tunnel_process = None
13381353
state.tunnel_address = None
13391354

1355+
# Verify SSH is available BEFORE starting the thread
1356+
ssh_executable = shutil.which("ssh")
1357+
if not ssh_executable and sys.platform == "win32":
1358+
# Fallback for Windows if not in PATH
1359+
common_paths = [
1360+
os.path.join(os.environ.get("SystemRoot", "C:\\Windows"), "System32\\OpenSSH\\ssh.exe"),
1361+
os.path.join(os.environ.get("ProgramFiles", "C:\\Program Files"), "OpenSSH\\ssh.exe"),
1362+
os.path.join(os.environ.get("ProgramFiles(x86)", "C:\\Program Files (x86)"), "OpenSSH\\ssh.exe"),
1363+
]
1364+
for p in common_paths:
1365+
if os.path.exists(p):
1366+
ssh_executable = p
1367+
logging.info(f"Found SSH at fallback path: {p}")
1368+
break
1369+
1370+
if not ssh_executable:
1371+
logging.error("SSH not found in PATH or common locations")
1372+
raise HTTPException(
1373+
status_code=400,
1374+
detail="SSH no encontrado. Por favor, instala 'OpenSSH Client' en las características opcionales de Windows para usar 'Make Public'."
1375+
)
1376+
1377+
# Discover ssh-keygen as well
1378+
ssh_keygen_executable = shutil.which("ssh-keygen")
1379+
if not ssh_keygen_executable and ssh_executable:
1380+
# If we found ssh.exe in a folder, its likely ssh-keygen is there too
1381+
potential_keygen = os.path.join(os.path.dirname(ssh_executable), "ssh-keygen.exe")
1382+
if os.path.exists(potential_keygen):
1383+
ssh_keygen_executable = potential_keygen
1384+
1385+
if not ssh_keygen_executable:
1386+
ssh_keygen_executable = "ssh-keygen" # Fallback to PATH and hope for the best if we couldn't find it explicitly
1387+
1388+
13401389
# Get server port (default 25565)
13411390
port = "25565"
13421391
if state.server_handler:
@@ -1365,7 +1414,7 @@ def _ensure_ssh_key():
13651414
if not os.path.exists(key_path) or not os.path.exists(pub_path):
13661415
logging.info("Generating new SSH key for Pinggy...")
13671416
subprocess.run(
1368-
["ssh-keygen", "-t", "rsa", "-b", "2048", "-f", key_path, "-N", ""],
1417+
[ssh_keygen_executable, "-t", "rsa", "-b", "2048", "-f", key_path, "-N", ""],
13691418
check=True,
13701419
stdout=subprocess.DEVNULL,
13711420
stderr=subprocess.DEVNULL,
@@ -1385,12 +1434,6 @@ def run_tunnel():
13851434
# regions: eu, us, ap, sa
13861435
host = f"{region}.free.pinggy.io"
13871436

1388-
# Verify SSH is available
1389-
if not shutil.which("ssh"):
1390-
logging.error("SSH not found in PATH")
1391-
state.broadcast_log_sync("❌ Error: 'ssh' is not installed or not in PATH. Cannot start public tunnel.", "error")
1392-
return
1393-
13941437
logging.info(f"Starting Pinggy tunnel ({region.upper()}) for port {port}...")
13951438
state.broadcast_log_sync(f"🌐 Starting public tunnel ({region.upper()}) for port {port}...", "info")
13961439

@@ -1399,7 +1442,7 @@ def run_tunnel():
13991442

14001443
# Pinggy SSH command - optimized with identity
14011444
cmd = [
1402-
"ssh",
1445+
ssh_executable,
14031446
"-p", "443",
14041447
"-o", "StrictHostKeyChecking=no",
14051448
"-o", "ServerAliveInterval=30",

electron-app/src/App.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ function App() {
9191
a.players === b.players &&
9292
a.cpu === b.cpu &&
9393
a.ram === b.ram &&
94-
JSON.stringify(a.recent_logs?.[a.recent_logs?.length - 1]) === JSON.stringify(b.recent_logs?.[b.recent_logs?.length - 1])
94+
a.recent_logs?.length === b.recent_logs?.length &&
95+
JSON.stringify(a.recent_logs?.[a.recent_logs?.length - 1]) === JSON.stringify(b.recent_logs?.[b.recent_logs?.length - 1]) &&
96+
JSON.stringify(a.tunnel) === JSON.stringify(b.tunnel)
9597
);
9698
};
9799

electron-app/src/components/Dashboard.jsx

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -257,23 +257,36 @@ export default function Dashboard({ status: serverStatus, onRefresh }) {
257257
// Polling Fallback for Logs (If WS is dead/unstable)
258258
if (serverStatus.recent_logs && Array.isArray(serverStatus.recent_logs) && serverStatus.recent_logs.length > 0) {
259259
setLocalLogs(prev => {
260-
// Update if polling has more logs, or if local is empty
261-
if (prev.length === 0 || serverStatus.recent_logs.length > prev.length) {
262-
return serverStatus.recent_logs.slice(-100);
263-
}
260+
// Update if local is empty
261+
if (prev.length === 0) return serverStatus.recent_logs.slice(-100);
264262

265-
// Simple check: if contents are different (even if same length), update
263+
// If the last log from polling isn't in our local logs (or differs from our last log),
264+
// it means we missed something or are out of sync.
266265
const lastPoll = serverStatus.recent_logs[serverStatus.recent_logs.length - 1];
267266
const lastLocal = prev[prev.length - 1];
268-
if (lastPoll && lastLocal && lastPoll.message !== lastLocal.message) {
269-
return serverStatus.recent_logs.slice(-100);
267+
268+
if (lastPoll && (!lastLocal || lastPoll.message !== lastLocal.message)) {
269+
// Check if it's really new (isn't already in the last few local logs)
270+
const isRepetition = prev.slice(-5).some(l => l.message === lastPoll.message && l.time === lastPoll.time);
271+
if (!isRepetition) {
272+
return serverStatus.recent_logs.slice(-100);
273+
}
270274
}
271275

272276
return prev;
273277
});
274278
}
279+
// Sync tunnel info from polling too
280+
if (serverStatus.tunnel) {
281+
if (serverStatus.tunnel.active && serverStatus.tunnel.address) {
282+
setTunnelAddress(serverStatus.tunnel.address);
283+
setTunnelConnecting(false);
284+
} else if (!tunnelConnecting && tunnelAddress) {
285+
setTunnelAddress(null);
286+
}
287+
}
275288
}
276-
}, [serverStatus?.status, serverStatus?.shutdown_info, serverStatus?.recent_logs]);
289+
}, [serverStatus?.status, serverStatus?.shutdown_info, serverStatus?.recent_logs, serverStatus?.tunnel]);
277290

278291
// Reset logs ONLY when the server ID changes (Persist logs after stop)
279292
useEffect(() => {
@@ -585,8 +598,15 @@ export default function Dashboard({ status: serverStatus, onRefresh }) {
585598
} catch (err) {
586599
console.error('[Dashboard] Tunnel error:', err);
587600
setTunnelConnecting(false);
601+
// Get the error message from the response if possible
602+
const errorMsg = err.response?.data?.detail || err.message || "Unknown error";
588603
// Show error in logs
589-
setLocalLogs(prev => [...prev, { message: `❌ Tunnel error: ${err.message}`, level: 'error' }]);
604+
setLocalLogs(prev => [...prev, {
605+
message: `❌ Tunnel error: ${errorMsg}`,
606+
level: 'error',
607+
time: new Date().toISOString()
608+
}]);
609+
alert(`Error de túnel: ${errorMsg}`);
590610
}
591611
}}
592612
disabled={tunnelConnecting && !tunnelAddress}

0 commit comments

Comments
 (0)