Fix server management issues and improve overall stability
Major server management fixes: - Replace Flatpak-specific pkill with universal process tree termination using pstree + process.kill() - Fix signal format errors (SIGTERM/SIGKILL instead of TERM/KILL strings) - Add 5-second cooldown after server stop to prevent race conditions with external detection - Enable Stop Server button for external servers in UI - Implement proper timeout handling with process tree killing ContentDB improvements: - Fix download retry logic and "closed" error by preventing concurrent zip extraction - Implement smart root directory detection and stripping during package extraction - Add game-specific timeout handling (8s for VoxeLibre vs 3s for simple games) World creation fixes: - Make world creation asynchronous to prevent browser hangs - Add WebSocket notifications for world creation completion status Other improvements: - Remove excessive debug logging - Improve error handling and user feedback throughout the application - Clean up temporary files and unnecessary logging 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
107
utils/paths.js
107
utils/paths.js
@@ -14,24 +14,104 @@ class LuantiPaths {
|
||||
await appConfig.load();
|
||||
const configuredDataDir = appConfig.getDataDirectory();
|
||||
this.setDataDirectory(configuredDataDir);
|
||||
console.log(`Paths initialized with data directory: ${configuredDataDir}`);
|
||||
}
|
||||
|
||||
async forceReload() {
|
||||
// Force reload the app config and reinitialize paths
|
||||
delete require.cache[require.resolve('./app-config')];
|
||||
const appConfig = require('./app-config');
|
||||
await appConfig.load();
|
||||
const configuredDataDir = appConfig.getDataDirectory();
|
||||
this.setDataDirectory(configuredDataDir);
|
||||
console.log(`Paths force reloaded with data directory: ${configuredDataDir}`);
|
||||
}
|
||||
|
||||
getDefaultDataDirectory() {
|
||||
// Check for common Luanti data directories
|
||||
const validDirs = this.detectLuantiDataDirectories();
|
||||
return validDirs.length > 0 ? validDirs[0].path : path.join(os.homedir(), '.minetest');
|
||||
}
|
||||
|
||||
detectLuantiDataDirectories() {
|
||||
const homeDir = os.homedir();
|
||||
const possibleDirs = [
|
||||
path.join(homeDir, '.luanti'),
|
||||
path.join(homeDir, '.minetest')
|
||||
const candidateDirs = [
|
||||
// Flatpak installations
|
||||
{
|
||||
path: path.join(homeDir, '.var/app/org.luanti.luanti/.minetest'),
|
||||
type: 'Flatpak (org.luanti.luanti)',
|
||||
description: 'Luanti installed via Flatpak'
|
||||
},
|
||||
{
|
||||
path: path.join(homeDir, '.var/app/net.minetest.Minetest/.minetest'),
|
||||
type: 'Flatpak (net.minetest.Minetest)',
|
||||
description: 'Minetest installed via Flatpak'
|
||||
},
|
||||
// Snap installations (they typically use ~/snap/app-name/current/.local/share/minetest)
|
||||
{
|
||||
path: path.join(homeDir, 'snap/luanti/current/.local/share/minetest'),
|
||||
type: 'Snap (luanti)',
|
||||
description: 'Luanti installed via Snap'
|
||||
},
|
||||
{
|
||||
path: path.join(homeDir, 'snap/minetest/current/.local/share/minetest'),
|
||||
type: 'Snap (minetest)',
|
||||
description: 'Minetest installed via Snap'
|
||||
},
|
||||
// System package installations
|
||||
{
|
||||
path: path.join(homeDir, '.luanti'),
|
||||
type: 'System Package',
|
||||
description: 'System-wide Luanti installation'
|
||||
},
|
||||
{
|
||||
path: path.join(homeDir, '.minetest'),
|
||||
type: 'System Package',
|
||||
description: 'System-wide Minetest installation'
|
||||
},
|
||||
// AppImage or manual installations might use these
|
||||
{
|
||||
path: path.join(homeDir, '.local/share/minetest'),
|
||||
type: 'User Installation',
|
||||
description: 'User-local installation'
|
||||
}
|
||||
];
|
||||
|
||||
// Use the first one that exists, or default to .minetest
|
||||
for (const dir of possibleDirs) {
|
||||
if (fs.existsSync(dir)) {
|
||||
return dir;
|
||||
// Check which directories exist and contain expected Luanti files
|
||||
const validDirs = [];
|
||||
for (const candidate of candidateDirs) {
|
||||
if (fs.existsSync(candidate.path)) {
|
||||
// Check if it looks like a valid Luanti data directory
|
||||
const hasConfig = fs.existsSync(path.join(candidate.path, 'minetest.conf'));
|
||||
const hasWorlds = fs.existsSync(path.join(candidate.path, 'worlds'));
|
||||
const hasDebug = fs.existsSync(path.join(candidate.path, 'debug.txt'));
|
||||
|
||||
// Even if some files don't exist, the directory might be valid if it exists
|
||||
// (Luanti creates files on first run)
|
||||
candidate.confidence = (hasConfig ? 1 : 0) + (hasWorlds ? 1 : 0) + (hasDebug ? 0.5 : 0);
|
||||
candidate.exists = true;
|
||||
candidate.hasConfig = hasConfig;
|
||||
candidate.hasWorlds = hasWorlds;
|
||||
candidate.hasDebug = hasDebug;
|
||||
|
||||
validDirs.push(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by confidence (directories with more Luanti files first)
|
||||
validDirs.sort((a, b) => b.confidence - a.confidence);
|
||||
|
||||
return path.join(homeDir, '.minetest');
|
||||
return validDirs;
|
||||
}
|
||||
|
||||
async getGameDisplayName(gameId) {
|
||||
try {
|
||||
const games = await this.getInstalledGames();
|
||||
const game = games.find(g => g.name === gameId);
|
||||
return game ? game.title : gameId;
|
||||
} catch (error) {
|
||||
console.error('Error getting game display name:', error);
|
||||
return gameId;
|
||||
}
|
||||
}
|
||||
|
||||
setDataDirectory(dataDir) {
|
||||
@@ -113,12 +193,11 @@ class LuantiPaths {
|
||||
|
||||
async getInstalledGames() {
|
||||
const games = [];
|
||||
// For Flatpak and other sandboxed installations, only look in the configured data directory
|
||||
// System installations might also have access to additional directories, but we should
|
||||
// primarily focus on the configured data directory to match what Luanti actually sees
|
||||
const possibleGameDirs = [
|
||||
this.gamesDir, // User games directory
|
||||
'/usr/share/luanti/games', // System games directory
|
||||
'/usr/share/minetest/games', // Legacy system games directory
|
||||
path.join(process.env.HOME || '/root', '.minetest/games'), // Explicit user path
|
||||
path.join(process.env.HOME || '/root', '.luanti/games') // New user path
|
||||
this.gamesDir // Only the configured games directory
|
||||
];
|
||||
|
||||
for (const gameDir of possibleGameDirs) {
|
||||
|
Reference in New Issue
Block a user