From e1a3f919a2367fe21d843d4fb056c56c0f23f7cc Mon Sep 17 00:00:00 2001 From: sanasol Date: Thu, 26 Feb 2026 23:37:55 +0100 Subject: [PATCH] Fix JRE flatten failing silently on Windows EPERM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When extracting the bundled JRE, flattenJREDir renames files from the nested jdk subdirectory up one level. On Windows this fails with EPERM when antivirus or file indexing holds handles open, leaving the JRE nested and unfindable — causing "Server failed to boot". - Fall back to copy+delete when rename gets EPERM/EACCES/EBUSY - getBundledJavaPath checks nested JRE subdirs as last resort --- backend/managers/javaManager.js | 61 ++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/backend/managers/javaManager.js b/backend/managers/javaManager.js index c2ebf85..48e34b2 100644 --- a/backend/managers/javaManager.js +++ b/backend/managers/javaManager.js @@ -106,6 +106,23 @@ function getBundledJavaPath(jreDir = JRE_DIR) { } } + // Fallback: check for nested JRE directory (e.g. jdk-25.0.2+10-jre/bin/java) + // This happens when flattenJREDir fails due to EPERM/EACCES on Windows + try { + if (fs.existsSync(jreDir)) { + const entries = fs.readdirSync(jreDir, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory() && entry.name !== 'bin' && entry.name !== 'lib') { + const nestedCandidate = path.join(jreDir, entry.name, 'bin', JAVA_EXECUTABLE); + if (fs.existsSync(nestedCandidate)) { + console.log(`[JRE] Using nested Java path: ${nestedCandidate}`); + return nestedCandidate; + } + } + } + } + } catch (_) { /* ignore */ } + return null; } @@ -409,7 +426,7 @@ function extractTarGz(tarGzPath, dest) { function flattenJREDir(jreLatest) { try { const entries = fs.readdirSync(jreLatest, { withFileTypes: true }); - + if (entries.length !== 1 || !entries[0].isDirectory()) { return; } @@ -420,12 +437,48 @@ function flattenJREDir(jreLatest) { for (const file of files) { const oldPath = path.join(nested, file.name); const newPath = path.join(jreLatest, file.name); - fs.renameSync(oldPath, newPath); + try { + fs.renameSync(oldPath, newPath); + } catch (renameErr) { + if (renameErr.code === 'EPERM' || renameErr.code === 'EACCES' || renameErr.code === 'EBUSY') { + console.log(`[JRE] Rename failed for ${file.name} (${renameErr.code}), using copy fallback`); + copyRecursiveSync(oldPath, newPath); + } else { + throw renameErr; + } + } } - fs.rmSync(nested, { recursive: true, force: true }); + try { + fs.rmSync(nested, { recursive: true, force: true }); + } catch (rmErr) { + console.log('[JRE] Could not remove nested JRE dir (non-critical):', rmErr.message); + } } catch (err) { - console.log('Notice: could not restructure Java directory:', err.message); + console.error('[JRE] Failed to restructure Java directory:', err.message); + // Last resort: check if java exists in a nested subdir and skip flatten + try { + const entries = fs.readdirSync(jreLatest, { withFileTypes: true }); + const nestedDir = entries.find(e => e.isDirectory() && e.name !== 'bin' && e.name !== 'lib'); + if (nestedDir) { + const nestedBin = path.join(jreLatest, nestedDir.name, 'bin', process.platform === 'win32' ? 'java.exe' : 'java'); + if (fs.existsSync(nestedBin)) { + console.log(`[JRE] Java found in nested dir: ${nestedDir.name}, leaving structure as-is`); + } + } + } catch (_) { /* ignore */ } + } +} + +function copyRecursiveSync(src, dest) { + const stat = fs.statSync(src); + if (stat.isDirectory()) { + fs.mkdirSync(dest, { recursive: true }); + for (const child of fs.readdirSync(src)) { + copyRecursiveSync(path.join(src, child), path.join(dest, child)); + } + } else { + fs.copyFileSync(src, dest); } }