Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,7 +9,6 @@ import time
|
|
| 9 |
from datetime import datetime
|
| 10 |
from functools import wraps
|
| 11 |
from pathlib import Path
|
| 12 |
-
import requests
|
| 13 |
import subprocess
|
| 14 |
|
| 15 |
from flask import Flask, render_template, request, jsonify, redirect, url_for, session, send_file, Response
|
|
@@ -58,106 +57,105 @@ def login_required(f):
|
|
| 58 |
return f(*args, **kwargs)
|
| 59 |
return decorated_function
|
| 60 |
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
"""
|
| 63 |
-
|
| 64 |
-
the public tunnel address.
|
| 65 |
"""
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
try:
|
| 81 |
-
#
|
| 82 |
subprocess.Popen(
|
| 83 |
['playit', 'run', '--api-address', '127.0.0.1:9000'],
|
| 84 |
-
#
|
| 85 |
-
|
| 86 |
-
stderr=subprocess.DEVNULL # Send errors to nowhere
|
| 87 |
)
|
| 88 |
-
server_manager.log("PlayIt.gg agent launched.
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
return
|
| 92 |
-
|
| 93 |
-
# 2. Poll the local API until the tunnel address is found
|
| 94 |
-
API_URL = "http://127.0.0.1:9000/api/v1/status"
|
| 95 |
-
MAX_ATTEMPTS = 60 # Poll for up to 60 seconds (1 minute)
|
| 96 |
-
|
| 97 |
-
for attempt in range(MAX_ATTEMPTS):
|
| 98 |
-
time.sleep(1) # Wait 1 second between attempts
|
| 99 |
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
# The structure of the status API is a list of tunnels
|
| 105 |
-
for tunnel in data.get('tunnels', []):
|
| 106 |
-
# Look for a tunnel that targets the Minecraft port (25565)
|
| 107 |
-
if tunnel.get('target_ip') == '127.0.0.1' and tunnel.get('target_port') == config.SERVER_PORT:
|
| 108 |
-
# Get the first available public address from the tunnel info
|
| 109 |
-
public_address_info = tunnel.get('available_addresses', [None])[0]
|
| 110 |
-
|
| 111 |
-
if public_address_info:
|
| 112 |
-
# Format is {host}.playit.gg:port
|
| 113 |
-
public_address = f"{public_address_info['host']}:{public_address_info['port']}"
|
| 114 |
-
|
| 115 |
-
print("=" * 50)
|
| 116 |
-
print(f"π PLAYIT.GG TUNNEL ESTABLISHED! π")
|
| 117 |
-
print(f"MINECRAFT ADDRESS: **{public_address}**")
|
| 118 |
-
print("=" * 50)
|
| 119 |
-
|
| 120 |
-
server_manager.log(f"PlayIt.gg Tunnel Ready: {public_address}", "success")
|
| 121 |
-
socketio.emit('server_connect_info', {'address': public_address, 'type': 'playit'})
|
| 122 |
-
return # Success! Exit the polling loop
|
| 123 |
-
|
| 124 |
-
# If tunnels were found but the correct one wasn't ready yet, continue polling
|
| 125 |
-
if data.get('tunnels'):
|
| 126 |
-
server_manager.log(f"Attempt {attempt+1}/{MAX_ATTEMPTS}: PlayIt agent running, tunnel not yet ready...", "info")
|
| 127 |
-
else:
|
| 128 |
-
server_manager.log(f"Attempt {attempt+1}/{MAX_ATTEMPTS}: Waiting for PlayIt API...", "info")
|
| 129 |
-
|
| 130 |
-
except requests.exceptions.ConnectionError:
|
| 131 |
-
# PlayIt agent hasn't started its API yet
|
| 132 |
-
server_manager.log(f"Attempt {attempt+1}/{MAX_ATTEMPTS}: PlayIt agent API not yet available...", "info")
|
| 133 |
-
except Exception as e:
|
| 134 |
-
server_manager.log(f"Error reading PlayIt API: {e}", "error")
|
| 135 |
-
|
| 136 |
-
# If the loop finishes without finding the address
|
| 137 |
-
server_manager.log("PlayIt.gg tunnel address not found after 60 seconds.", "error")
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
connect_address = f"{config.SPACE_URL}:{config.SERVER_PORT}"
|
| 145 |
|
| 146 |
-
# We can't know the PlayIt.gg tunnel address immediately, as it's generated
|
| 147 |
-
# asynchronously by the agent. We instruct the user to check the logs.
|
| 148 |
print("=" * 50)
|
| 149 |
print("π MINECRAFT CONNECTION INFO π")
|
| 150 |
print(f"Web Panel Address: {connect_address}")
|
| 151 |
print("------------------------------------------")
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
print("------------------------------------------")
|
| 155 |
print("=" * 50)
|
| 156 |
|
| 157 |
-
server_manager.log(f"
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
|
|
|
| 161 |
|
| 162 |
|
| 163 |
# --- Automated Save/Cleanup Thread ---
|
|
@@ -196,31 +194,31 @@ def logout():
|
|
| 196 |
@login_required
|
| 197 |
def index():
|
| 198 |
return render_template('index.html',
|
| 199 |
-
|
| 200 |
-
|
| 201 |
|
| 202 |
@app.route('/console')
|
| 203 |
@login_required
|
| 204 |
def console():
|
| 205 |
return render_template('console.html',
|
| 206 |
-
|
| 207 |
|
| 208 |
@app.route('/players')
|
| 209 |
@login_required
|
| 210 |
def players():
|
| 211 |
return render_template('players.html',
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
|
| 218 |
@app.route('/mods')
|
| 219 |
@login_required
|
| 220 |
def mods():
|
| 221 |
return render_template('mods.html',
|
| 222 |
-
|
| 223 |
-
|
| 224 |
|
| 225 |
@app.route('/files')
|
| 226 |
@app.route('/files/<path:filepath>')
|
|
@@ -228,22 +226,22 @@ def mods():
|
|
| 228 |
def files(filepath=''):
|
| 229 |
dir_content = file_manager.list_directory(filepath)
|
| 230 |
return render_template('files.html',
|
| 231 |
-
|
| 232 |
-
|
| 233 |
|
| 234 |
@app.route('/import')
|
| 235 |
@login_required
|
| 236 |
def import_page():
|
| 237 |
return render_template('import.html',
|
| 238 |
-
|
| 239 |
|
| 240 |
@app.route('/settings')
|
| 241 |
@login_required
|
| 242 |
def settings():
|
| 243 |
props = file_manager.get_server_properties()
|
| 244 |
return render_template('settings.html',
|
| 245 |
-
|
| 246 |
-
|
| 247 |
|
| 248 |
# --- API Routes ---
|
| 249 |
|
|
@@ -524,12 +522,9 @@ def main():
|
|
| 524 |
print(f"MC Server: Port {config.SERVER_PORT}")
|
| 525 |
print("=" * 50)
|
| 526 |
|
| 527 |
-
#
|
| 528 |
threading.Thread(target=start_playit_agent, daemon=True).start()
|
| 529 |
|
| 530 |
-
# 1. Start the Tunneling/Connection Display
|
| 531 |
-
threading.Thread(target=start_tunneling, daemon=True).start()
|
| 532 |
-
|
| 533 |
# 2. Start the Auto-Save and Cleanup Thread
|
| 534 |
threading.Thread(target=start_auto_save_cleanup_thread, daemon=True).start()
|
| 535 |
|
|
|
|
| 9 |
from datetime import datetime
|
| 10 |
from functools import wraps
|
| 11 |
from pathlib import Path
|
|
|
|
| 12 |
import subprocess
|
| 13 |
|
| 14 |
from flask import Flask, render_template, request, jsonify, redirect, url_for, session, send_file, Response
|
|
|
|
| 57 |
return f(*args, **kwargs)
|
| 58 |
return decorated_function
|
| 59 |
|
| 60 |
+
# ====================================================================
|
| 61 |
+
# === START PLAYIT.GG AGENT FUNCTIONS (CLAIM/SECRET KEY LOGIC) ===
|
| 62 |
+
# ====================================================================
|
| 63 |
+
|
| 64 |
+
def run_playit_claim_mode():
|
| 65 |
"""
|
| 66 |
+
Executes 'playit claim' to generate a claim code, required when the key is missing.
|
|
|
|
| 67 |
"""
|
| 68 |
+
try:
|
| 69 |
+
# Run 'playit claim' synchronously to get the output
|
| 70 |
+
result = subprocess.run(
|
| 71 |
+
['playit', 'claim'],
|
| 72 |
+
capture_output=True,
|
| 73 |
+
text=True,
|
| 74 |
+
timeout=10
|
| 75 |
+
)
|
| 76 |
|
| 77 |
+
if result.returncode == 0:
|
| 78 |
+
# The claim code is usually the last word printed by the command
|
| 79 |
+
claim_code = result.stdout.strip().split()[-1]
|
| 80 |
+
# Call the display function with the claim code
|
| 81 |
+
start_tunneling(claim_code=claim_code)
|
| 82 |
+
else:
|
| 83 |
+
server_manager.log(f"PlayIt claim failed: {result.stderr.strip()}", "error")
|
| 84 |
+
start_tunneling(error_message="PlayIt 'claim' failed.")
|
| 85 |
|
| 86 |
+
except Exception as e:
|
| 87 |
+
server_manager.log(f"Error during PlayIt claim: {e}", "error")
|
| 88 |
+
start_tunneling(error_message="PlayIt claim encountered an error.")
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def run_playit_secret_mode():
|
| 92 |
+
"""
|
| 93 |
+
Launches the 'playit run' agent in the background when the SECRET_KEY is present.
|
| 94 |
+
"""
|
| 95 |
try:
|
| 96 |
+
# Launch the agent in the background
|
| 97 |
subprocess.Popen(
|
| 98 |
['playit', 'run', '--api-address', '127.0.0.1:9000'],
|
| 99 |
+
stdout=subprocess.DEVNULL, # Keep console clean
|
| 100 |
+
stderr=subprocess.DEVNULL
|
|
|
|
| 101 |
)
|
| 102 |
+
server_manager.log("PlayIt.gg agent launched with SECRET KEY. The public address should appear in the main logs shortly.", "info")
|
| 103 |
+
# Display the connection info immediately
|
| 104 |
+
start_tunneling(secret_mode=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
+
except Exception as e:
|
| 107 |
+
server_manager.log(f"Error launching PlayIt.gg agent with key: {e}", "error")
|
| 108 |
+
start_tunneling(error_message="PlayIt 'run' failed with Secret Key. Check logs.")
|
| 109 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
+
def start_playit_agent():
|
| 112 |
+
"""
|
| 113 |
+
Determines which PlayIt.gg mode to run based on the environment variable.
|
| 114 |
+
"""
|
| 115 |
+
if os.environ.get('PLAYIT_SECRET_KEY'):
|
| 116 |
+
print("=" * 50)
|
| 117 |
+
print("π STARTING PLAYIT.GG AGENT (Secret Key Mode) π")
|
| 118 |
+
print("=" * 50)
|
| 119 |
+
run_playit_secret_mode()
|
| 120 |
+
else:
|
| 121 |
+
print("=" * 50)
|
| 122 |
+
print("π STARTING PLAYIT.GG AGENT (Claim Code Mode) π")
|
| 123 |
+
print("Action Required: Go to playit.gg/claim and enter the code below.")
|
| 124 |
+
print("=" * 50)
|
| 125 |
+
run_playit_claim_mode()
|
| 126 |
+
|
| 127 |
|
| 128 |
+
def start_tunneling(claim_code=None, secret_mode=False, error_message=None):
|
| 129 |
+
"""
|
| 130 |
+
Displays the connection information based on the current PlayIt mode.
|
| 131 |
+
"""
|
| 132 |
connect_address = f"{config.SPACE_URL}:{config.SERVER_PORT}"
|
| 133 |
|
|
|
|
|
|
|
| 134 |
print("=" * 50)
|
| 135 |
print("π MINECRAFT CONNECTION INFO π")
|
| 136 |
print(f"Web Panel Address: {connect_address}")
|
| 137 |
print("------------------------------------------")
|
| 138 |
+
|
| 139 |
+
if error_message:
|
| 140 |
+
print(f"MC Tunnel: **ERROR: {error_message}**")
|
| 141 |
+
socketio.emit('server_connect_info', {'address': error_message, 'type': 'error'})
|
| 142 |
+
elif claim_code:
|
| 143 |
+
print(f"MC Tunnel: CLAIM CODE")
|
| 144 |
+
print(f"Claim Code: **{claim_code}** (Use at playit.gg/claim)")
|
| 145 |
+
socketio.emit('server_connect_info', {'address': f'CLAIM: {claim_code}', 'type': 'claim'})
|
| 146 |
+
elif secret_mode:
|
| 147 |
+
print("MC Tunnel: **CHECK THE CONTAINER LOGS**")
|
| 148 |
+
print("The PlayIt.gg agent is running and will print the public address once connected.")
|
| 149 |
+
socketio.emit('server_connect_info', {'address': 'Agent Running (Check Logs)', 'type': 'playit'})
|
| 150 |
+
|
| 151 |
print("------------------------------------------")
|
| 152 |
print("=" * 50)
|
| 153 |
|
| 154 |
+
server_manager.log(f"Connect Address: {connect_address}", "info")
|
| 155 |
+
|
| 156 |
+
# ====================================================================
|
| 157 |
+
# === END PLAYIT.GG AGENT FUNCTIONS ===
|
| 158 |
+
# ====================================================================
|
| 159 |
|
| 160 |
|
| 161 |
# --- Automated Save/Cleanup Thread ---
|
|
|
|
| 194 |
@login_required
|
| 195 |
def index():
|
| 196 |
return render_template('index.html',
|
| 197 |
+
status=server_manager.get_status(),
|
| 198 |
+
mods_count=len(mod_manager.get_installed_mods()))
|
| 199 |
|
| 200 |
@app.route('/console')
|
| 201 |
@login_required
|
| 202 |
def console():
|
| 203 |
return render_template('console.html',
|
| 204 |
+
history=server_manager.get_console_history(200))
|
| 205 |
|
| 206 |
@app.route('/players')
|
| 207 |
@login_required
|
| 208 |
def players():
|
| 209 |
return render_template('players.html',
|
| 210 |
+
online=server_manager.players_online,
|
| 211 |
+
all_players=player_tracker.get_all_players(),
|
| 212 |
+
whitelist=player_tracker.get_whitelist(),
|
| 213 |
+
ops=player_tracker.get_ops(),
|
| 214 |
+
banned=player_tracker.get_banned())
|
| 215 |
|
| 216 |
@app.route('/mods')
|
| 217 |
@login_required
|
| 218 |
def mods():
|
| 219 |
return render_template('mods.html',
|
| 220 |
+
mods=mod_manager.get_installed_mods(),
|
| 221 |
+
total_size=mod_manager.get_total_size())
|
| 222 |
|
| 223 |
@app.route('/files')
|
| 224 |
@app.route('/files/<path:filepath>')
|
|
|
|
| 226 |
def files(filepath=''):
|
| 227 |
dir_content = file_manager.list_directory(filepath)
|
| 228 |
return render_template('files.html',
|
| 229 |
+
content=dir_content,
|
| 230 |
+
current_path=filepath)
|
| 231 |
|
| 232 |
@app.route('/import')
|
| 233 |
@login_required
|
| 234 |
def import_page():
|
| 235 |
return render_template('import.html',
|
| 236 |
+
backups=file_manager.list_backups())
|
| 237 |
|
| 238 |
@app.route('/settings')
|
| 239 |
@login_required
|
| 240 |
def settings():
|
| 241 |
props = file_manager.get_server_properties()
|
| 242 |
return render_template('settings.html',
|
| 243 |
+
properties=props,
|
| 244 |
+
pinger_status=health_pinger.get_status())
|
| 245 |
|
| 246 |
# --- API Routes ---
|
| 247 |
|
|
|
|
| 522 |
print(f"MC Server: Port {config.SERVER_PORT}")
|
| 523 |
print("=" * 50)
|
| 524 |
|
| 525 |
+
# 1. Start the PlayIt.gg agent (handles Claim Code or Secret Key run)
|
| 526 |
threading.Thread(target=start_playit_agent, daemon=True).start()
|
| 527 |
|
|
|
|
|
|
|
|
|
|
| 528 |
# 2. Start the Auto-Save and Cleanup Thread
|
| 529 |
threading.Thread(target=start_auto_save_cleanup_thread, daemon=True).start()
|
| 530 |
|