@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 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.1-2563eb?style=for-the-badge&logo=typescript" alt="Version">
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.1](#whats-new-in-221)
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.1
114
+ ## What's New in 2.2.2
115
115
 
116
- ### Version 2.2.1 - March 2026
116
+ ### Version 2.2.2 - March 2026
117
117
 
118
- - **Pterodactyl Optimized** - Full compatibility with Pterodactyl panel
119
- - **No curl/wget required** - Pure Node.js downloader works everywhere
120
- - **Better error handling** - Clear messages for disk space issues
121
- - **Smaller Java downloads** - Uses JRE instead of full JDK (saves 150MB)
122
- - **Automatic disk space check** - Warns before downloading
123
- - **Improved platform detection** - Works on all Linux distros
124
- - **Fallback URLs** - Multiple mirrors for Java downloads
125
- - **Corrupted file detection** - Auto-retry on bad downloads
126
- - **Memory limit awareness** - Respects cgroup limits in containers
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 current directory, no system installation required |
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 downloader (no curl/wget needed) |
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.1)
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.1`);
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 current directory
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
- · Downloads JRE (not full JDK) ~50MB
1074
- · Extracts to .java/jre-{version}/ in current directory
1075
- · Auto-cleanup after 24 hours
1076
- · No system installation required
1077
- · Sets JAVA_HOME and PATH automatically
1078
- · Adds MALLOC_ARENA_MAX=2 for memory efficiency
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 Egg Configuration
1179
+ Pterodactyl Environment Variables
1180
+
1181
+ MC-Headless automatically detects Pterodactyl and respects these environment variables:
1174
1182
 
1175
- If you want to create a custom Pterodactyl egg:
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 Using Node.js downloader (fixed in v2.2.1)
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 downloadWithNode;
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,MAAM,CAAwB;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAGrE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAS/B;WAEY,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;WAUpD,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;WAenC,iBAAiB,IAAI,MAAM,GAAG,IAAI;WASlC,oBAAoB,IAAI,MAAM,GAAG,IAAI;mBAc9B,gBAAgB;WAoFjB,yBAAyB,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;WAqElE,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,WAAW,GAAE,OAAc,GAAG,OAAO,CAAC,QAAQ,CAAC;WAyBvF,cAAc,IAAI,IAAI;CAkBvC"}
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"}
@@ -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 logger = Logger_1.Logger.getInstance();
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
- 'https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_x64_linux_hotspot_21.0.2_13.tar.gz',
51
- 'https://corretto.aws/downloads/latest/amazon-corretto-21-x64-linux-jre.tar.gz'
52
- ],
53
- '17': [
54
- 'https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.10%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.10_7.tar.gz',
55
- 'https://corretto.aws/downloads/latest/amazon-corretto-17-x64-linux-jre.tar.gz'
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
- if (output.includes('version "21'))
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
- const match = output.match(/version "([^"]+)"/);
103
- return match ? match[1] : null;
158
+ return null;
104
159
  }
105
160
  catch {
106
161
  return null;
107
162
  }
108
163
  }
109
- static async downloadWithNode(url, destPath) {
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
- this.logger.info(`Downloading Java from ${url}`);
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': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
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.logger.info(`Redirecting to ${location}`);
122
- this.downloadWithNode(location, destPath).then(resolve).catch(reject);
123
- return;
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
- const contentType = response.headers['content-type'];
131
- if (contentType && contentType.includes('text/html')) {
132
- reject(new Error('Received HTML instead of binary - likely license redirect'));
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 file = fs.createWriteStream(destPath);
136
- const totalSize = parseInt(response.headers['content-length'] || '0');
137
- let downloadedSize = 0;
138
- let lastPercent = 0;
139
- response.on('data', (chunk) => {
140
- downloadedSize += chunk.length;
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
- response.pipe(file);
150
- file.on('finish', () => {
151
- process.stdout.write('\n');
152
- file.close();
153
- const stats = fs.statSync(destPath);
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
- file.on('error', (err) => {
162
- fs.unlink(destPath).catch(() => { });
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
- fs.unlink(destPath).catch(() => { });
168
- reject(err);
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
- fs.unlink(destPath).catch(() => { });
173
- reject(new Error('Download timeout after 5 minutes'));
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 javaHome = path.join(this.JAVA_DIR, `jre-${version}`);
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.logger.info(`Portable Java ${version} already exists at ${javaBin}`);
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
- catch (error) {
194
- this.logger.warning(`Existing Java seems corrupted, re-downloading...`);
195
- await fs.remove(javaHome);
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 < urls.length; i++) {
201
- const url = urls[i];
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
- this.logger.info(`Downloading portable Java ${version} (attempt ${i + 1}/${urls.length})...`);
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 tar.extract({
210
- file: tarPath,
211
- cwd: javaHome,
212
- strip: 1
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
- this.logger.success(`Portable Java ${version} installed at ${javaBin}`);
217
- const versionOutput = (0, child_process_1.execSync)(`"${javaBin}" -version 2>&1`).toString();
218
- this.logger.debug(`Portable Java version: ${versionOutput.split('\n')[0]}`);
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
- this.logger.warning(`Attempt ${i + 1} failed: ${lastError.message}`);
228
- await fs.remove(tarPath).catch(() => { });
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 ${version} after ${urls.length} attempts. Last error: ${lastError?.message}`);
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
- const stats = fs.statSync(filePath);
264
- if (now - stats.mtimeMs > oneDay) {
265
- fs.removeSync(filePath);
266
- this.logger.debug(`Cleaned up old Java: ${file}`);
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 (error) {
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