@dimzxzzx07/mc-headless 2.2.1 → 2.2.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.
- package/README.md +46 -29
- package/dist/core/JavaChecker.d.ts +8 -2
- package/dist/core/JavaChecker.d.ts.map +1 -1
- package/dist/core/JavaChecker.js +219 -104
- package/dist/core/JavaChecker.js.map +1 -1
- package/dist/core/MinecraftServer.d.ts +11 -1
- package/dist/core/MinecraftServer.d.ts.map +1 -1
- package/dist/core/MinecraftServer.js +337 -158
- package/dist/core/MinecraftServer.js.map +1 -1
- package/package.json +1 -1
- package/src/core/JavaChecker.ts +224 -108
- package/src/core/MinecraftServer.ts +402 -201
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
</div>
|
|
6
6
|
|
|
7
7
|
<div align="center">
|
|
8
|
-
<img src="https://img.shields.io/badge/Version-2.2.
|
|
8
|
+
<img src="https://img.shields.io/badge/Version-2.2.2-2563eb?style=for-the-badge&logo=typescript" alt="Version">
|
|
9
9
|
<img src="https://img.shields.io/badge/License-MIT-green?style=for-the-badge&logo=open-source-initiative" alt="License">
|
|
10
10
|
<img src="https://img.shields.io/badge/Node-18%2B-339933?style=for-the-badge&logo=nodedotjs" alt="Node">
|
|
11
11
|
<img src="https://img.shields.io/badge/Java-Portable-007396?style=for-the-badge&logo=openjdk" alt="Java Portable">
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
## Table of Contents
|
|
56
56
|
|
|
57
57
|
- [What is MC-Headless?](#what-is-mc-headless)
|
|
58
|
-
- [What's New in 2.2.
|
|
58
|
+
- [What's New in 2.2.2](#whats-new-in-222)
|
|
59
59
|
- [Features](#features)
|
|
60
60
|
- [Why MC-Headless?](#why-mc-headless)
|
|
61
61
|
- [Installation](#installation)
|
|
@@ -111,19 +111,21 @@ Built specifically for developers, sysadmins, and Minecraft enthusiasts who want
|
|
|
111
111
|
|
|
112
112
|
---
|
|
113
113
|
|
|
114
|
-
## What's New in 2.2.
|
|
114
|
+
## What's New in 2.2.2
|
|
115
115
|
|
|
116
|
-
### Version 2.2.
|
|
116
|
+
### Version 2.2.2 - March 2026
|
|
117
117
|
|
|
118
|
-
- **
|
|
119
|
-
- **
|
|
120
|
-
- **
|
|
121
|
-
- **
|
|
122
|
-
- **
|
|
123
|
-
- **
|
|
124
|
-
- **
|
|
125
|
-
- **
|
|
126
|
-
- **
|
|
118
|
+
- **Smart Java Storage** - Automatically detects Pterodactyl environment and stores Java in `/home/container/.java` instead of RAM-heavy `/tmp`
|
|
119
|
+
- **Multi-Architecture Support** - Full support for x64, ARM64 (Oracle Cloud Ampere), and ARM (Raspberry Pi)
|
|
120
|
+
- **Streaming Download + Extract** - Downloads and extracts Java simultaneously, saving disk space and time
|
|
121
|
+
- **Partial Download Recovery** - Automatically cleans up corrupted partial downloads and retries
|
|
122
|
+
- **Content-Length Verification** - Ensures downloaded files match expected size (within 95% tolerance)
|
|
123
|
+
- **Rotating User-Agents** - Bypasses rate limiting with 4 different user-agent strings
|
|
124
|
+
- **Smart Retry Logic** - Retries on rate limits (429), server errors (500+), and network timeouts
|
|
125
|
+
- **30-Day Auto-Update** - Automatically checks and updates Java if older than 30 days
|
|
126
|
+
- **Low RAM Warning** - Alerts users when storing Java in `/tmp` on systems with less than 3GB RAM
|
|
127
|
+
- **Robust Redirect Handling** - Follows up to 3 redirects with proper error handling
|
|
128
|
+
- **Stalled Download Detection** - Cancels and retries if no data received for 30 seconds
|
|
127
129
|
|
|
128
130
|
---
|
|
129
131
|
|
|
@@ -133,17 +135,20 @@ Built specifically for developers, sysadmins, and Minecraft enthusiasts who want
|
|
|
133
135
|
|----------|----------|
|
|
134
136
|
| **Server Types** | Paper, Purpur, Vanilla, Spigot, Forge, Fabric |
|
|
135
137
|
| **Platforms** | Java Edition, Bedrock Edition, Cross-play (Geyser) |
|
|
138
|
+
| **Architecture Support** | x64, ARM64, ARM (Raspberry Pi, Oracle Cloud) |
|
|
136
139
|
| **Auto Setup** | Automatic Java detection, EULA acceptance, server.properties generation |
|
|
137
|
-
| **Portable Java** | Download JRE to
|
|
140
|
+
| **Portable Java** | Download JRE to `/home/container/.java` (Pterodactyl) or `/tmp/.mc-headless` |
|
|
141
|
+
| **Smart Storage** | Detects Pterodactyl, warns on low RAM for /tmp usage |
|
|
142
|
+
| **Streaming Download** | Download and extract simultaneously, no temporary files |
|
|
138
143
|
| **Cgroups Stats** | CPU/Memory stats like Pterodactyl (30s interval) |
|
|
139
|
-
| **Downloader** | Pure Node.js
|
|
144
|
+
| **Downloader** | Pure Node.js with rotating user-agents, retry logic, stalled detection |
|
|
140
145
|
| **Memory Management** | Custom memory allocation, Aikar's flags optimization |
|
|
141
146
|
| **Backup System** | Automatic scheduled backups, manual backup triggers |
|
|
142
147
|
| **Monitoring** | Real-time CPU/memory usage, player tracking, server events |
|
|
143
148
|
| **Cross-play** | Built-in Geyser & Floodgate support for Bedrock clients |
|
|
144
149
|
| **ViaVersion** | Built-in ViaVersion, ViaBackwards, ViaRewind support |
|
|
145
150
|
| **SkinRestorer** | Auto-download and install SkinRestorer plugin |
|
|
146
|
-
| **Pterodactyl Ready** | Optimized for panel hosting |
|
|
151
|
+
| **Pterodactyl Ready** | Optimized for panel hosting with environment variable support |
|
|
147
152
|
| **Termux Friendly** | Optimized for Android/Termux environments |
|
|
148
153
|
| **Headless Ready** | No GUI required, perfect for servers and automation |
|
|
149
154
|
| **Silent Mode** | Direct log piping for minimal CPU usage |
|
|
@@ -181,7 +186,7 @@ wget https://github.com/SkinsRestorer/SkinsRestorerX/releases/latest/download/Sk
|
|
|
181
186
|
tail -f logs/latest.log
|
|
182
187
|
```
|
|
183
188
|
|
|
184
|
-
After (MC-Headless v2.2.
|
|
189
|
+
After (MC-Headless v2.2.2)
|
|
185
190
|
|
|
186
191
|
```javascript
|
|
187
192
|
const { MinecraftServer } = require('@dimzxzzx07/mc-headless');
|
|
@@ -223,7 +228,7 @@ RAM 2 GB 4 GB or more
|
|
|
223
228
|
Storage 2 GB 10 GB
|
|
224
229
|
OS Linux, macOS, Windows, Termux Linux (production)
|
|
225
230
|
|
|
226
|
-
Note: Java is auto-downloaded as portable JRE (no system installation needed)
|
|
231
|
+
Note: Java is auto-downloaded as portable JRE (no system installation needed). On Pterodactyl, Java is stored in /home/container/.java to save RAM.
|
|
227
232
|
|
|
228
233
|
---
|
|
229
234
|
|
|
@@ -341,7 +346,7 @@ async function startServer() {
|
|
|
341
346
|
server.on("ready", (info) => {
|
|
342
347
|
console.clear();
|
|
343
348
|
console.log(`\n==========================================`);
|
|
344
|
-
console.log(`Minecraft Server - v2.2.
|
|
349
|
+
console.log(`Minecraft Server - v2.2.2`);
|
|
345
350
|
console.log(` IP: ${publicIp}:${info.port}`);
|
|
346
351
|
console.log(` Version: ${info.version}`);
|
|
347
352
|
console.log(` Memory: ${info.memory.used}/${info.memory.max} MB`);
|
|
@@ -470,7 +475,7 @@ autoAcceptEula boolean true Automatically accept Minecraft EULA
|
|
|
470
475
|
Java Options
|
|
471
476
|
|
|
472
477
|
Option Type Default Description
|
|
473
|
-
usePortableJava boolean true Download portable JRE to
|
|
478
|
+
usePortableJava boolean true Download portable JRE to /home/container/.java (Pterodactyl) or /tmp/.mc-headless
|
|
474
479
|
javaVersion string 'auto' '17', '21', or 'auto'
|
|
475
480
|
|
|
476
481
|
Memory Options
|
|
@@ -1070,12 +1075,13 @@ const server = new MinecraftServer({
|
|
|
1070
1075
|
});
|
|
1071
1076
|
```
|
|
1072
1077
|
|
|
1073
|
-
·
|
|
1074
|
-
·
|
|
1075
|
-
·
|
|
1076
|
-
·
|
|
1077
|
-
·
|
|
1078
|
-
·
|
|
1078
|
+
· Smart Storage: Automatically detects Pterodactyl environment and stores Java in /home/container/.java (disk) instead of /tmp (RAM)
|
|
1079
|
+
· Low RAM Warning: Alerts if storing Java in /tmp on systems with less than 3GB RAM
|
|
1080
|
+
· Multi-Architecture: Supports x64, ARM64 (Oracle Cloud), and ARM (Raspberry Pi)
|
|
1081
|
+
· Streaming Download: Downloads and extracts simultaneously, saving disk space
|
|
1082
|
+
· Auto-Update: Checks and updates Java if older than 30 days
|
|
1083
|
+
· Robust Download: Rotating user-agents, retry logic, stalled detection, size verification
|
|
1084
|
+
· No System Installation: Java runs from local folder, no root required
|
|
1079
1085
|
|
|
1080
1086
|
Java Version Requirements
|
|
1081
1087
|
|
|
@@ -1170,9 +1176,18 @@ EOF
|
|
|
1170
1176
|
node index.js
|
|
1171
1177
|
```
|
|
1172
1178
|
|
|
1173
|
-
Pterodactyl
|
|
1179
|
+
Pterodactyl Environment Variables
|
|
1180
|
+
|
|
1181
|
+
MC-Headless automatically detects Pterodactyl and respects these environment variables:
|
|
1174
1182
|
|
|
1175
|
-
|
|
1183
|
+
Variable Description Default
|
|
1184
|
+
SERVER_PORT Server port 25565
|
|
1185
|
+
SERVER_MEMORY_INIT Initial memory 2G
|
|
1186
|
+
SERVER_MEMORY_MAX Max memory 4G
|
|
1187
|
+
SERVER_MOTD Message of the day "Minecraft Server"
|
|
1188
|
+
SERVER_IP Bind IP 0.0.0.0
|
|
1189
|
+
|
|
1190
|
+
Pterodactyl Egg Configuration
|
|
1176
1191
|
|
|
1177
1192
|
```json
|
|
1178
1193
|
{
|
|
@@ -1261,11 +1276,13 @@ Common Issues
|
|
|
1261
1276
|
Issue Cause Solution
|
|
1262
1277
|
Java not found Java not installed Enable usePortableJava: true
|
|
1263
1278
|
ENOSPC: no space left Disk full Free disk space, reduce memory
|
|
1264
|
-
Download failed: 302 URL redirect
|
|
1279
|
+
Download failed: 302 URL redirect Automatic in v2.2.2
|
|
1265
1280
|
Port already in use Another server running Change port number
|
|
1266
1281
|
Plugin corrupt Bad download Delete plugin and restart
|
|
1267
1282
|
High CPU usage Too many chunks Reduce viewDistance to 4
|
|
1268
1283
|
Out of memory RAM too low Reduce max memory or add RAM
|
|
1284
|
+
Java verification failed Corrupt download Automatic retry in v2.2.2
|
|
1285
|
+
Download stalled Slow connection Automatic retry in v2.2.2
|
|
1269
1286
|
|
|
1270
1287
|
Disk Space Issues
|
|
1271
1288
|
|
|
@@ -4,16 +4,22 @@ export interface JavaInfo {
|
|
|
4
4
|
type: 'system' | 'portable';
|
|
5
5
|
}
|
|
6
6
|
export declare class JavaChecker {
|
|
7
|
-
private static logger;
|
|
8
7
|
private static readonly JAVA_DIR;
|
|
9
8
|
private static readonly JAVA_URLS;
|
|
9
|
+
private static getOptimalJavaDirectory;
|
|
10
|
+
private static getArch;
|
|
10
11
|
static getRequiredJavaVersion(serverVersion: string): '17' | '21';
|
|
11
12
|
static checkJava(): Promise<boolean>;
|
|
12
13
|
static getSystemJavaPath(): string | null;
|
|
13
14
|
static getSystemJavaVersion(): string | null;
|
|
14
|
-
private static
|
|
15
|
+
private static shouldUpdateJava;
|
|
16
|
+
private static downloadAndExtractWithStream;
|
|
15
17
|
static getOrDownloadPortableJava(version: '17' | '21'): Promise<JavaInfo>;
|
|
16
18
|
static ensureJava(serverVersion: string, usePortable?: boolean): Promise<JavaInfo>;
|
|
17
19
|
static cleanupOldJava(): void;
|
|
20
|
+
static getJavaInfo(): {
|
|
21
|
+
dir: string;
|
|
22
|
+
arch: string;
|
|
23
|
+
};
|
|
18
24
|
}
|
|
19
25
|
//# sourceMappingURL=JavaChecker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JavaChecker.d.ts","sourceRoot":"","sources":["../../src/core/JavaChecker.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;CAC/B;AAED,qBAAa,WAAW;IACpB,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"JavaChecker.d.ts","sourceRoot":"","sources":["../../src/core/JavaChecker.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;CAC/B;AAED,qBAAa,WAAW;IACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAyC;IAEzE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CA+B/B;IAEF,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAmBtC,OAAO,CAAC,MAAM,CAAC,OAAO;WAOR,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;WAUpD,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;WAYnC,iBAAiB,IAAI,MAAM,GAAG,IAAI;WASlC,oBAAoB,IAAI,MAAM,GAAG,IAAI;IA2BnD,OAAO,CAAC,MAAM,CAAC,gBAAgB;mBAUV,4BAA4B;WAoI7B,yBAAyB,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;WAgElE,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,WAAW,GAAE,OAAc,GAAG,OAAO,CAAC,QAAQ,CAAC;WAoBvF,cAAc,IAAI,IAAI;WAwBtB,WAAW,IAAI;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAM7D"}
|
package/dist/core/JavaChecker.js
CHANGED
|
@@ -39,22 +39,66 @@ const fs = __importStar(require("fs-extra"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const https = __importStar(require("https"));
|
|
41
41
|
const http = __importStar(require("http"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
42
43
|
const tar = __importStar(require("tar"));
|
|
43
|
-
const Logger_1 = require("../utils/Logger");
|
|
44
44
|
class JavaChecker {
|
|
45
|
-
static
|
|
46
|
-
static JAVA_DIR = path.join(process.cwd(), '.java');
|
|
47
|
-
// Hanya pakai URL yang reliable dan direct download
|
|
45
|
+
static JAVA_DIR = JavaChecker.getOptimalJavaDirectory();
|
|
48
46
|
static JAVA_URLS = {
|
|
49
|
-
'21':
|
|
50
|
-
'
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'
|
|
56
|
-
|
|
47
|
+
'21': {
|
|
48
|
+
'x64': [
|
|
49
|
+
'https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jre_x64_linux_hotspot_21.0.2_13.tar.gz',
|
|
50
|
+
'https://corretto.aws/downloads/latest/amazon-corretto-21-x64-linux-jre.tar.gz',
|
|
51
|
+
'https://download.bell-sw.com/java/21.0.2+13/bellsoft-jre21.0.2+13-linux-amd64.tar.gz'
|
|
52
|
+
],
|
|
53
|
+
'arm64': [
|
|
54
|
+
'https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jre_aarch64_linux_hotspot_21.0.2_13.tar.gz',
|
|
55
|
+
'https://corretto.aws/downloads/latest/amazon-corretto-21-aarch64-linux-jre.tar.gz',
|
|
56
|
+
'https://download.bell-sw.com/java/21.0.2+13/bellsoft-jre21.0.2+13-linux-aarch64.tar.gz'
|
|
57
|
+
],
|
|
58
|
+
'arm': [
|
|
59
|
+
'https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jre_arm_linux_hotspot_21.0.2_13.tar.gz'
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
'17': {
|
|
63
|
+
'x64': [
|
|
64
|
+
'https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.10%2B7/OpenJDK17U-jre_x64_linux_hotspot_17.0.10_7.tar.gz',
|
|
65
|
+
'https://corretto.aws/downloads/latest/amazon-corretto-17-x64-linux-jre.tar.gz',
|
|
66
|
+
'https://download.bell-sw.com/java/17.0.10+13/bellsoft-jre17.0.10+13-linux-amd64.tar.gz'
|
|
67
|
+
],
|
|
68
|
+
'arm64': [
|
|
69
|
+
'https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.10%2B7/OpenJDK17U-jre_aarch64_linux_hotspot_17.0.10_7.tar.gz',
|
|
70
|
+
'https://corretto.aws/downloads/latest/amazon-corretto-17-aarch64-linux-jre.tar.gz',
|
|
71
|
+
'https://download.bell-sw.com/java/17.0.10+13/bellsoft-jre17.0.10+13-linux-aarch64.tar.gz'
|
|
72
|
+
],
|
|
73
|
+
'arm': [
|
|
74
|
+
'https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.10%2B7/OpenJDK17U-jre_arm_linux_hotspot_17.0.10_7.tar.gz'
|
|
75
|
+
]
|
|
76
|
+
}
|
|
57
77
|
};
|
|
78
|
+
static getOptimalJavaDirectory() {
|
|
79
|
+
const pterodactylDir = '/home/container/.java';
|
|
80
|
+
const tmpDir = '/tmp/.mc-headless';
|
|
81
|
+
try {
|
|
82
|
+
if (fs.existsSync('/home/container')) {
|
|
83
|
+
return pterodactylDir;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
}
|
|
88
|
+
const totalRamGB = os.totalmem() / 1024 / 1024 / 1024;
|
|
89
|
+
if (totalRamGB < 3) {
|
|
90
|
+
console.warn(`Warning: Low RAM (${totalRamGB.toFixed(1)} GB). Java in /tmp may cause issues.`);
|
|
91
|
+
}
|
|
92
|
+
return tmpDir;
|
|
93
|
+
}
|
|
94
|
+
static getArch() {
|
|
95
|
+
const arch = process.arch;
|
|
96
|
+
if (arch === 'arm64')
|
|
97
|
+
return 'arm64';
|
|
98
|
+
if (arch === 'arm')
|
|
99
|
+
return 'arm';
|
|
100
|
+
return 'x64';
|
|
101
|
+
}
|
|
58
102
|
static getRequiredJavaVersion(serverVersion) {
|
|
59
103
|
if (serverVersion.startsWith('1.21') || serverVersion === '1.21.11') {
|
|
60
104
|
return '21';
|
|
@@ -68,12 +112,9 @@ class JavaChecker {
|
|
|
68
112
|
return new Promise((resolve) => {
|
|
69
113
|
(0, child_process_1.exec)('which java', (error, stdout) => {
|
|
70
114
|
if (error || !stdout.trim()) {
|
|
71
|
-
this.logger.warning('Java is not installed in system');
|
|
72
115
|
resolve(false);
|
|
73
116
|
}
|
|
74
117
|
else {
|
|
75
|
-
const javaPath = stdout.trim();
|
|
76
|
-
this.logger.success(`System Java found at: ${javaPath}`);
|
|
77
118
|
resolve(true);
|
|
78
119
|
}
|
|
79
120
|
});
|
|
@@ -91,131 +132,199 @@ class JavaChecker {
|
|
|
91
132
|
static getSystemJavaVersion() {
|
|
92
133
|
try {
|
|
93
134
|
const output = (0, child_process_1.execSync)('java -version 2>&1').toString();
|
|
94
|
-
|
|
135
|
+
const versionRegex = /(?:version|openjdk|java)[\s"]+(\d+)(?:\.(\d+))?/i;
|
|
136
|
+
const match = output.match(versionRegex);
|
|
137
|
+
if (match) {
|
|
138
|
+
const majorVersion = parseInt(match[1]);
|
|
139
|
+
if (majorVersion === 21)
|
|
140
|
+
return '21';
|
|
141
|
+
if (majorVersion === 17)
|
|
142
|
+
return '17';
|
|
143
|
+
if (majorVersion === 11)
|
|
144
|
+
return '11';
|
|
145
|
+
if (majorVersion === 8)
|
|
146
|
+
return '8';
|
|
147
|
+
if (majorVersion === 1 && match[2] === '8')
|
|
148
|
+
return '8';
|
|
149
|
+
}
|
|
150
|
+
if (output.includes('version "21') || output.includes('openjdk 21'))
|
|
95
151
|
return '21';
|
|
96
|
-
if (output.includes('version "17'))
|
|
152
|
+
if (output.includes('version "17') || output.includes('openjdk 17'))
|
|
97
153
|
return '17';
|
|
98
|
-
if (output.includes('version "11'))
|
|
154
|
+
if (output.includes('version "11') || output.includes('openjdk 11'))
|
|
99
155
|
return '11';
|
|
100
|
-
if (output.includes('version "1.8'))
|
|
156
|
+
if (output.includes('version "1.8') || output.includes('openjdk 1.8'))
|
|
101
157
|
return '8';
|
|
102
|
-
|
|
103
|
-
return match ? match[1] : null;
|
|
158
|
+
return null;
|
|
104
159
|
}
|
|
105
160
|
catch {
|
|
106
161
|
return null;
|
|
107
162
|
}
|
|
108
163
|
}
|
|
109
|
-
static
|
|
164
|
+
static shouldUpdateJava(javaHome) {
|
|
165
|
+
try {
|
|
166
|
+
const stats = fs.statSync(javaHome);
|
|
167
|
+
const ageInDays = (Date.now() - stats.mtimeMs) / (1000 * 60 * 60 * 24);
|
|
168
|
+
return ageInDays > 30;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
static async downloadAndExtractWithStream(url, javaHome, retryCount = 0) {
|
|
175
|
+
const maxRetries = 3;
|
|
176
|
+
const userAgents = [
|
|
177
|
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
|
|
178
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
|
|
179
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36'
|
|
180
|
+
];
|
|
110
181
|
return new Promise((resolve, reject) => {
|
|
111
|
-
|
|
182
|
+
console.log(`Downloading Java...`);
|
|
112
183
|
const protocol = url.startsWith('https') ? https : http;
|
|
184
|
+
let isDone = false;
|
|
185
|
+
let cleanup = () => {
|
|
186
|
+
if (!isDone) {
|
|
187
|
+
isDone = true;
|
|
188
|
+
fs.remove(javaHome).catch(() => { });
|
|
189
|
+
}
|
|
190
|
+
};
|
|
113
191
|
const request = protocol.get(url, {
|
|
114
192
|
headers: {
|
|
115
|
-
'User-Agent':
|
|
193
|
+
'User-Agent': userAgents[retryCount % userAgents.length],
|
|
194
|
+
'Accept': 'application/octet-stream',
|
|
195
|
+
'Accept-Encoding': 'gzip, deflate'
|
|
116
196
|
}
|
|
117
197
|
}, (response) => {
|
|
118
198
|
if (response.statusCode === 301 || response.statusCode === 302 || response.statusCode === 307 || response.statusCode === 308) {
|
|
119
199
|
const location = response.headers.location;
|
|
120
|
-
if (location) {
|
|
121
|
-
this.
|
|
122
|
-
|
|
123
|
-
|
|
200
|
+
if (location && retryCount < maxRetries) {
|
|
201
|
+
this.downloadAndExtractWithStream(location, javaHome, retryCount + 1).then(resolve).catch(reject);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
cleanup();
|
|
205
|
+
reject(new Error('Redirect failed'));
|
|
124
206
|
}
|
|
125
|
-
}
|
|
126
|
-
if (response.statusCode !== 200) {
|
|
127
|
-
reject(new Error(`Download failed: ${response.statusCode}`));
|
|
128
207
|
return;
|
|
129
208
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
209
|
+
if (response.statusCode !== 200) {
|
|
210
|
+
if (response.statusCode === 429 && retryCount < maxRetries) {
|
|
211
|
+
setTimeout(() => {
|
|
212
|
+
this.downloadAndExtractWithStream(url, javaHome, retryCount + 1).then(resolve).catch(reject);
|
|
213
|
+
}, 10000);
|
|
214
|
+
}
|
|
215
|
+
else if (response.statusCode && response.statusCode >= 500 && retryCount < maxRetries) {
|
|
216
|
+
setTimeout(() => {
|
|
217
|
+
this.downloadAndExtractWithStream(url, javaHome, retryCount + 1).then(resolve).catch(reject);
|
|
218
|
+
}, 10000);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
cleanup();
|
|
222
|
+
reject(new Error(`Download failed: ${response.statusCode}`));
|
|
223
|
+
}
|
|
133
224
|
return;
|
|
134
225
|
}
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (totalSize > 0) {
|
|
142
|
-
const percent = ((downloadedSize / totalSize) * 100).toFixed(1);
|
|
143
|
-
if (parseFloat(percent) >= lastPercent + 5 || parseFloat(percent) >= 100) {
|
|
144
|
-
lastPercent = parseFloat(percent);
|
|
145
|
-
process.stdout.write(`\rDownloading Java: ${percent}% (${(downloadedSize / 1024 / 1024).toFixed(1)} MB)`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
226
|
+
const contentLength = response.headers['content-length'];
|
|
227
|
+
let downloadedBytes = 0;
|
|
228
|
+
fs.ensureDirSync(javaHome);
|
|
229
|
+
const extractor = tar.extract({
|
|
230
|
+
cwd: javaHome,
|
|
231
|
+
strip: 1
|
|
148
232
|
});
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (stats.size < 1000000) {
|
|
155
|
-
fs.unlinkSync(destPath);
|
|
156
|
-
reject(new Error(`Downloaded file too small: ${stats.size} bytes - possibly HTML page`));
|
|
157
|
-
return;
|
|
233
|
+
let extractError = null;
|
|
234
|
+
extractor.on('finish', () => {
|
|
235
|
+
if (!isDone) {
|
|
236
|
+
isDone = true;
|
|
237
|
+
resolve();
|
|
158
238
|
}
|
|
159
|
-
resolve();
|
|
160
239
|
});
|
|
161
|
-
|
|
162
|
-
|
|
240
|
+
extractor.on('error', (err) => {
|
|
241
|
+
extractError = err;
|
|
242
|
+
cleanup();
|
|
163
243
|
reject(err);
|
|
164
244
|
});
|
|
245
|
+
response.on('data', (chunk) => {
|
|
246
|
+
downloadedBytes += chunk.length;
|
|
247
|
+
});
|
|
248
|
+
response.on('error', (err) => {
|
|
249
|
+
extractor.end();
|
|
250
|
+
cleanup();
|
|
251
|
+
reject(err);
|
|
252
|
+
});
|
|
253
|
+
response.on('end', () => {
|
|
254
|
+
if (contentLength) {
|
|
255
|
+
const expected = parseInt(contentLength);
|
|
256
|
+
if (downloadedBytes < expected * 0.95) {
|
|
257
|
+
cleanup();
|
|
258
|
+
reject(new Error(`Download incomplete: ${downloadedBytes} of ${expected} bytes`));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
extractor.on('finish', () => {
|
|
263
|
+
if (!isDone && !extractError) {
|
|
264
|
+
isDone = true;
|
|
265
|
+
resolve();
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
response.pipe(extractor);
|
|
165
270
|
});
|
|
166
271
|
request.on('error', (err) => {
|
|
167
|
-
|
|
168
|
-
|
|
272
|
+
if (retryCount < maxRetries) {
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
this.downloadAndExtractWithStream(url, javaHome, retryCount + 1).then(resolve).catch(reject);
|
|
275
|
+
}, 5000);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
cleanup();
|
|
279
|
+
reject(err);
|
|
280
|
+
}
|
|
169
281
|
});
|
|
170
282
|
request.setTimeout(300000, () => {
|
|
171
283
|
request.destroy();
|
|
172
|
-
|
|
173
|
-
reject(new Error('Download timeout
|
|
284
|
+
cleanup();
|
|
285
|
+
reject(new Error('Download timeout'));
|
|
174
286
|
});
|
|
175
287
|
request.end();
|
|
176
288
|
});
|
|
177
289
|
}
|
|
178
290
|
static async getOrDownloadPortableJava(version) {
|
|
179
291
|
await fs.ensureDir(this.JAVA_DIR);
|
|
180
|
-
const
|
|
292
|
+
const arch = this.getArch();
|
|
293
|
+
const javaHome = path.join(this.JAVA_DIR, `jre-${version}-${arch}`);
|
|
181
294
|
const javaBin = path.join(javaHome, 'bin', 'java');
|
|
182
295
|
if (await fs.pathExists(javaBin)) {
|
|
183
|
-
this.
|
|
184
|
-
try {
|
|
185
|
-
const versionOutput = (0, child_process_1.execSync)(`"${javaBin}" -version 2>&1`).toString();
|
|
186
|
-
this.logger.debug(`Portable Java version: ${versionOutput.split('\n')[0]}`);
|
|
296
|
+
if (!this.shouldUpdateJava(javaHome)) {
|
|
187
297
|
return {
|
|
188
298
|
path: javaBin,
|
|
189
299
|
version: version,
|
|
190
300
|
type: 'portable'
|
|
191
301
|
};
|
|
192
302
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
303
|
+
console.log('Java update available, downloading latest...');
|
|
304
|
+
await fs.remove(javaHome);
|
|
305
|
+
}
|
|
306
|
+
const archUrls = this.JAVA_URLS[version]?.[arch];
|
|
307
|
+
if (!archUrls || archUrls.length === 0) {
|
|
308
|
+
throw new Error(`No Java download for ${version} on ${arch}`);
|
|
197
309
|
}
|
|
198
|
-
const urls = this.JAVA_URLS[version] || this.JAVA_URLS['21'];
|
|
199
310
|
let lastError = null;
|
|
200
|
-
for (let i = 0; i <
|
|
201
|
-
const url =
|
|
202
|
-
const tarPath = path.join(this.JAVA_DIR, `jre-${version}-${Date.now()}.tar.gz`);
|
|
311
|
+
for (let i = 0; i < archUrls.length; i++) {
|
|
312
|
+
const url = archUrls[i];
|
|
203
313
|
try {
|
|
204
|
-
|
|
205
|
-
this.logger.info(`URL: ${url}`);
|
|
206
|
-
await this.downloadWithNode(url, tarPath);
|
|
207
|
-
this.logger.info(`Extracting Java ${version}...`);
|
|
314
|
+
await fs.remove(javaHome);
|
|
208
315
|
await fs.ensureDir(javaHome);
|
|
209
|
-
await
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
});
|
|
214
|
-
await fs.remove(tarPath);
|
|
316
|
+
await this.downloadAndExtractWithStream(url, javaHome);
|
|
317
|
+
if (!await fs.pathExists(javaBin)) {
|
|
318
|
+
throw new Error('Java binary not found after extraction');
|
|
319
|
+
}
|
|
215
320
|
await fs.chmod(javaBin, 0o755);
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
321
|
+
try {
|
|
322
|
+
(0, child_process_1.execSync)(`"${javaBin}" -version 2>&1`);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
await fs.remove(javaHome);
|
|
326
|
+
throw new Error('Java verification failed');
|
|
327
|
+
}
|
|
219
328
|
return {
|
|
220
329
|
path: javaBin,
|
|
221
330
|
version: version,
|
|
@@ -224,53 +333,59 @@ class JavaChecker {
|
|
|
224
333
|
}
|
|
225
334
|
catch (error) {
|
|
226
335
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
227
|
-
|
|
228
|
-
await fs.remove(
|
|
336
|
+
console.log(`Download attempt ${i + 1} failed, trying next source...`);
|
|
337
|
+
await fs.remove(javaHome).catch(() => { });
|
|
229
338
|
}
|
|
230
339
|
}
|
|
231
|
-
throw new Error(`Failed to download Java
|
|
340
|
+
throw new Error(`Failed to download Java: ${lastError?.message}`);
|
|
232
341
|
}
|
|
233
342
|
static async ensureJava(serverVersion, usePortable = true) {
|
|
234
343
|
const requiredVersion = this.getRequiredJavaVersion(serverVersion);
|
|
235
|
-
this.logger.info(`Server requires Java ${requiredVersion}`);
|
|
236
344
|
if (!usePortable) {
|
|
237
345
|
const systemJava = this.getSystemJavaPath();
|
|
238
346
|
if (systemJava) {
|
|
239
347
|
const systemVersion = this.getSystemJavaVersion();
|
|
240
348
|
if (systemVersion === requiredVersion) {
|
|
241
|
-
this.logger.success(`Using system Java ${systemVersion} at ${systemJava}`);
|
|
242
349
|
return {
|
|
243
350
|
path: systemJava,
|
|
244
351
|
version: systemVersion,
|
|
245
352
|
type: 'system'
|
|
246
353
|
};
|
|
247
354
|
}
|
|
248
|
-
else {
|
|
249
|
-
this.logger.warning(`System Java is ${systemVersion}, but server needs Java ${requiredVersion}`);
|
|
250
|
-
}
|
|
251
355
|
}
|
|
252
356
|
}
|
|
253
|
-
this.logger.info(`Downloading portable Java ${requiredVersion}...`);
|
|
254
357
|
return this.getOrDownloadPortableJava(requiredVersion);
|
|
255
358
|
}
|
|
256
359
|
static cleanupOldJava() {
|
|
257
360
|
try {
|
|
361
|
+
if (!fs.existsSync(this.JAVA_DIR))
|
|
362
|
+
return;
|
|
258
363
|
const files = fs.readdirSync(this.JAVA_DIR);
|
|
259
364
|
const now = Date.now();
|
|
260
365
|
const oneDay = 24 * 60 * 60 * 1000;
|
|
261
366
|
files.forEach(file => {
|
|
262
367
|
const filePath = path.join(this.JAVA_DIR, file);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
368
|
+
try {
|
|
369
|
+
const stats = fs.statSync(filePath);
|
|
370
|
+
if (now - stats.mtimeMs > oneDay) {
|
|
371
|
+
if (stats.isDirectory()) {
|
|
372
|
+
fs.removeSync(filePath);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
267
377
|
}
|
|
268
378
|
});
|
|
269
379
|
}
|
|
270
|
-
catch
|
|
271
|
-
this.logger.debug('No old Java to cleanup');
|
|
380
|
+
catch {
|
|
272
381
|
}
|
|
273
382
|
}
|
|
383
|
+
static getJavaInfo() {
|
|
384
|
+
return {
|
|
385
|
+
dir: this.JAVA_DIR,
|
|
386
|
+
arch: this.getArch()
|
|
387
|
+
};
|
|
388
|
+
}
|
|
274
389
|
}
|
|
275
390
|
exports.JavaChecker = JavaChecker;
|
|
276
391
|
//# sourceMappingURL=JavaChecker.js.map
|