File size: 13,849 Bytes
b676880
 
 
 
 
417d689
b676880
 
417d689
 
b676880
417d689
 
b676880
 
 
417d689
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b676880
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417d689
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b676880
 
417d689
 
 
 
 
 
 
 
 
 
 
 
 
b676880
 
 
 
 
 
 
417d689
b676880
 
417d689
b676880
 
 
 
 
 
 
 
 
 
 
 
 
417d689
b676880
 
417d689
b676880
 
417d689
 
 
 
b676880
 
 
417d689
b676880
 
 
 
417d689
 
 
 
 
 
 
b676880
 
417d689
b676880
417d689
 
 
b676880
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417d689
b676880
 
 
 
 
 
 
 
417d689
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
import urllib.request
from pathlib import Path
import os
import subprocess
import sys
import shutil

PAPER_JAR_URL = "https://fill-data.papermc.io/v1/objects/234a9b32098100c6fc116664d64e36ccdb58b5b649af0f80bcccb08b0255eaea/paper-1.20.1-196.jar"
NGROK_URL = "https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz"

DATA_DIR = Path("/data")
JAVA_DIR = DATA_DIR / "java"
NGROK_DIR = DATA_DIR / "ngrok"
JAR_NAME = "paper-1.20.1-196.jar"
JAR_PATH = DATA_DIR / JAR_NAME

# Java download URLs (OpenJDK 17)
JAVA_URLS = {
    "linux_x64": "https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz",
    "linux_aarch64": "https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-aarch64_bin.tar.gz"
}

def load_env_vars():
    """Load environment variables from .env file if it exists"""
    env_vars = {}
    env_file = Path(".env")
    
    if env_file.exists():
        try:
            with open(env_file, 'r') as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith('#') and '=' in line:
                        key, value = line.split('=', 1)
                        env_vars[key.strip()] = value.strip().strip('"\'')
            print(f"Loaded {len(env_vars)} environment variables from .env")
        except Exception as e:
            print(f"Warning: Could not read .env file: {e}")
    
    # Also check system environment variables
    ngrok_token = env_vars.get('NGROK_TOKEN') or os.getenv('NGROK_TOKEN')
    
    return {
        'NGROK_TOKEN': ngrok_token
    }

def check_ngrok():
    """Check if ngrok is available and return the path"""
    # Check system PATH first
    ngrok_path = shutil.which("ngrok")
    if ngrok_path:
        try:
            result = subprocess.run([ngrok_path, "version"], capture_output=True, text=True)
            print(f"Found ngrok: {ngrok_path}")
            print(f"Version: {result.stdout.strip()}")
            return ngrok_path
        except:
            pass
    
    # Check our custom ngrok installation
    custom_ngrok = NGROK_DIR / "ngrok"
    if custom_ngrok.exists():
        try:
            result = subprocess.run([str(custom_ngrok), "version"], capture_output=True, text=True)
            print(f"Found custom ngrok: {custom_ngrok}")
            print(f"Version: {result.stdout.strip()}")
            return str(custom_ngrok)
        except:
            pass
    
    return None

def install_ngrok():
    """Download and install ngrok"""
    print("Installing ngrok...")
    
    # Create ngrok directory
    NGROK_DIR.mkdir(parents=True, exist_ok=True)
    
    # Download ngrok
    ngrok_archive = DATA_DIR / "ngrok.tgz"
    if not download_file(NGROK_URL, ngrok_archive):
        return False
    
    # Extract ngrok
    print("Extracting ngrok...")
    try:
        import tarfile
        with tarfile.open(ngrok_archive, 'r:gz') as tar:
            tar.extractall(NGROK_DIR)
        
        # Make ngrok executable
        ngrok_bin = NGROK_DIR / "ngrok"
        ngrok_bin.chmod(0o755)
        
        # Clean up
        ngrok_archive.unlink()
        
        print(f"βœ“ ngrok installed to: {ngrok_bin}")
        return True
        
    except Exception as e:
        print(f"Failed to extract ngrok: {e}")
        return False

def setup_ngrok():
    """Setup ngrok with authentication token"""
    env_vars = load_env_vars()
    ngrok_token = env_vars.get('NGROK_TOKEN')
    
    if not ngrok_token:
        print("Warning: NGROK_TOKEN not found in environment variables or .env file")
        print("Please set NGROK_TOKEN in your .env file or Hugging Face Space secrets")
        return False
    
    # Get ngrok path
    ngrok_path = check_ngrok()
    if not ngrok_path:
        print("ngrok not found. Installing...")
        if not install_ngrok():
            print("Failed to install ngrok")
            return False
        ngrok_path = check_ngrok()
        if not ngrok_path:
            print("ngrok installation failed")
            return False
    
    # Configure ngrok with auth token
    print("Configuring ngrok with auth token...")
    try:
        result = subprocess.run([
            ngrok_path, "config", "add-authtoken", ngrok_token
        ], capture_output=True, text=True)
        
        if result.returncode == 0:
            print("βœ“ ngrok configured successfully")
            return True
        else:
            print(f"Failed to configure ngrok: {result.stderr}")
            return False
            
    except Exception as e:
        print(f"Failed to configure ngrok: {e}")
        return False

def start_ngrok_tunnel(port=25565):
    """Start ngrok tunnel for the Minecraft server"""
    ngrok_path = check_ngrok()
    if not ngrok_path:
        print("ngrok not found. Run setup first.")
        return None
    
    print(f"Starting ngrok tunnel for port {port}...")
    try:
        # Start ngrok in background
        process = subprocess.Popen([
            ngrok_path, "tcp", str(port), "--log", "stdout"
        ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        
        # Give ngrok a moment to start
        import time
        time.sleep(3)
        
        # Try to get the tunnel URL from ngrok API
        try:
            import json
            import urllib.request
            
            # ngrok exposes a local API on port 4040
            api_url = "http://localhost:4040/api/tunnels"
            with urllib.request.urlopen(api_url) as response:
                data = json.loads(response.read().decode())
                
            if data.get('tunnels'):
                tunnel_url = data['tunnels'][0]['public_url']
                print(f"βœ“ ngrok tunnel active: {tunnel_url}")
                print(f"Players can connect to: {tunnel_url.replace('tcp://', '')}")
                return process, tunnel_url
            else:
                print("No active tunnels found")
                return process, None
                
        except Exception as e:
            print(f"Could not retrieve tunnel URL: {e}")
            print("Check ngrok dashboard at http://localhost:4040")
            return process, None
            
    except Exception as e:
        print(f"Failed to start ngrok tunnel: {e}")
        return None, None

def check_java():
    """Check if Java is available and return the path"""
    # Check if java is already in PATH
    java_path = shutil.which("java")
    if java_path:
        try:
            result = subprocess.run([java_path, "-version"], capture_output=True, text=True)
            print(f"Found Java: {java_path}")
            print(result.stderr.split('\n')[0])  # Java version info is in stderr
            return java_path
        except:
            pass
    
    # Check our custom Java installation
    custom_java = JAVA_DIR / "bin" / "java"
    if custom_java.exists():
        try:
            result = subprocess.run([str(custom_java), "-version"], capture_output=True, text=True)
            print(f"Found custom Java: {custom_java}")
            print(result.stderr.split('\n')[0])  # Java version info is in stderr
            return str(custom_java)
        except:
            pass
    
    return None
    """Detect the platform architecture"""
    import platform
    machine = platform.machine().lower()
    if 'x86_64' in machine or 'amd64' in machine:
        return "linux_x64"
    elif 'aarch64' in machine or 'arm64' in machine:
        return "linux_aarch64"
    else:
        print(f"Unsupported architecture: {machine}")
        return "linux_x64"  # Default fallback

def download_file(url, destination):
    print(f"Downloading {url}...")
    try:
        destination.parent.mkdir(parents=True, exist_ok=True)
        
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        }
        request = urllib.request.Request(url, headers=headers)
        
        with urllib.request.urlopen(request) as response:
            with open(destination, 'wb') as f:
                total_size = int(response.headers.get('content-length', 0))
                block_size = 8192
                downloaded = 0
                
                while True:
                    buffer = response.read(block_size)
                    if not buffer:
                        break
                    
                    downloaded += len(buffer)
                    f.write(buffer)
                    
                    if total_size:
                        percent = min(100, (downloaded * 100) // total_size)
                        print(f"\rProgress: {percent}% ({downloaded}/{total_size} bytes)", end="")
                
        print(f"\nβœ“ Downloaded: {destination}")
        return True
    except Exception as e:
        print(f"\nβœ— Failed to download {url}: {e}")
        return False

def install_java():
    """Download and install Java if not available"""
    print("Installing Java...")
    
    platform_key = get_platform()
    java_url = JAVA_URLS.get(platform_key)
    
    if not java_url:
        print(f"No Java download available for platform: {platform_key}")
        return False
    
    # Download Java
    java_archive = DATA_DIR / "java.tar.gz"
    if not download_file(java_url, java_archive):
        return False
    
    # Extract Java
    print("Extracting Java...")
    try:
        import tarfile
        with tarfile.open(java_archive, 'r:gz') as tar:
            # Extract to temporary directory first
            temp_dir = DATA_DIR / "java_temp"
            temp_dir.mkdir(exist_ok=True)
            tar.extractall(temp_dir)
            
            # Find the extracted JDK directory (usually has a version number)
            extracted_dirs = [d for d in temp_dir.iterdir() if d.is_dir() and d.name.startswith('jdk')]
            if not extracted_dirs:
                print("Could not find extracted JDK directory")
                return False
            
            jdk_dir = extracted_dirs[0]
            
            # Move to final location
            if JAVA_DIR.exists():
                shutil.rmtree(JAVA_DIR)
            shutil.move(str(jdk_dir), str(JAVA_DIR))
            
            # Clean up
            shutil.rmtree(temp_dir)
            java_archive.unlink()
            
        print(f"βœ“ Java installed to: {JAVA_DIR}")
        
        # Make java executable
        java_bin = JAVA_DIR / "bin" / "java"
        java_bin.chmod(0o755)
        
        return True
        
    except Exception as e:
        print(f"Failed to extract Java: {e}")
        return False

def setup_minecraft_server():
    """Set up the Minecraft server in persistent storage"""
    
    # Ensure the data directory exists
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    
    # Check and install Java if needed
    java_path = check_java()
    if not java_path:
        print("Java not found. Installing Java...")
        if not install_java():
            print("Failed to install Java. Cannot proceed.")
            return False
        java_path = check_java()
        if not java_path:
            print("Java installation failed or not working.")
            return False
    
    # Download the server JAR if it doesn't exist
    if not JAR_PATH.exists():
        print(f"Downloading Minecraft server to {JAR_PATH}")
        if not download_file(PAPER_JAR_URL, JAR_PATH):
            return False
    else:
        print(f"Server JAR already exists at {JAR_PATH}")
    
    # Create server.properties file if it doesn't exist
    server_properties_path = DATA_DIR / "server.properties"
    if not server_properties_path.exists():
        print("Creating server.properties...")
        server_properties = """# Minecraft server properties
server-port=25565
gamemode=survival
difficulty=easy
spawn-protection=16
max-players=20
online-mode=false
white-list=false
motd=Hugging Face Minecraft Server
"""
        with open(server_properties_path, 'w') as f:
            f.write(server_properties)
    
    # Create eula.txt (required for server to start)
    eula_path = DATA_DIR / "eula.txt"
    if not eula_path.exists():
        print("Creating eula.txt...")
        with open(eula_path, 'w') as f:
            f.write("eula=true\n")
    
    print("βœ“ Minecraft server setup complete!")
    print(f"Server files are stored in: {DATA_DIR}")
    print(f"Java path: {java_path}")
    return True

def start_server():
    """Start the Minecraft server"""
    if not JAR_PATH.exists():
        print("Server JAR not found. Run setup first.")
        return False
    
    # Get Java path
    java_path = check_java()
    if not java_path:
        print("Java not found. Run setup first.")
        return False
    
    # Change to the data directory
    os.chdir(DATA_DIR)
    
    # Start the server
    cmd = [
        java_path,
        "-Xmx2G",  # Max heap size
        "-Xms1G",  # Initial heap size
        "-jar", str(JAR_PATH),
        "--nogui"
    ]
    
    print(f"Starting Minecraft server with command: {' '.join(cmd)}")
    print(f"Working directory: {DATA_DIR}")
    
    try:
        process = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            universal_newlines=True,
            bufsize=1
        )
        
        # Print server output in real-time
        for line in process.stdout:
            print(line.strip())
            
    except Exception as e:
        print(f"Failed to start server: {e}")
        return False

if __name__ == "__main__":
    if setup_minecraft_server():
        start_server()
    else:
        print("Setup failed. Cannot start server.")