Update Dockerfile
Browse files- Dockerfile +211 -44
Dockerfile
CHANGED
|
@@ -11,7 +11,7 @@ RUN apt-get update && apt-get install -y \
|
|
| 11 |
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
|
| 13 |
# Install Python dependencies
|
| 14 |
-
RUN pip3 install flask flask-sock psutil --break-system-packages
|
| 15 |
|
| 16 |
WORKDIR /app
|
| 17 |
|
|
@@ -49,6 +49,9 @@ if [ ! -f eula.txt ]; then
|
|
| 49 |
echo "✅ EULA accepted"
|
| 50 |
fi
|
| 51 |
|
|
|
|
|
|
|
|
|
|
| 52 |
# Create backup script
|
| 53 |
cat > /app/backup.sh << 'BACKUPSCRIPT'
|
| 54 |
#!/bin/bash
|
|
@@ -77,7 +80,7 @@ echo "✅ Auto-backup enabled (every 5 minutes)"
|
|
| 77 |
echo ""
|
| 78 |
|
| 79 |
# ==========================================
|
| 80 |
-
# WEB TERMINAL SERVER (
|
| 81 |
# ==========================================
|
| 82 |
cat > /app/web_terminal.py << 'PYTHON_SCRIPT'
|
| 83 |
import os
|
|
@@ -88,9 +91,11 @@ import threading
|
|
| 88 |
import time
|
| 89 |
import queue
|
| 90 |
import signal
|
|
|
|
| 91 |
from flask import Flask, render_template_string
|
| 92 |
from flask_sock import Sock
|
| 93 |
import psutil
|
|
|
|
| 94 |
|
| 95 |
app = Flask(__name__)
|
| 96 |
sock = Sock(app)
|
|
@@ -100,11 +105,131 @@ output_queue = queue.Queue()
|
|
| 100 |
command_queue = queue.Queue()
|
| 101 |
server_ready = threading.Event()
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
def start_minecraft():
|
| 104 |
-
global mc_process
|
| 105 |
mc_dir = "/app/mcserver"
|
| 106 |
|
| 107 |
print("[MC] Starting Minecraft server...", flush=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
mc_process = subprocess.Popen(
|
| 110 |
["java", "-Xms1G", "-Xmx14G", "-XX:+UseG1GC",
|
|
@@ -120,16 +245,20 @@ def start_minecraft():
|
|
| 120 |
)
|
| 121 |
|
| 122 |
print(f"[MC] Minecraft PID: {mc_process.pid}", flush=True)
|
|
|
|
| 123 |
|
| 124 |
def read_output():
|
| 125 |
try:
|
| 126 |
for line in mc_process.stdout:
|
| 127 |
-
|
|
|
|
| 128 |
if "Done" in line and "For help, type" in line:
|
| 129 |
server_ready.set()
|
| 130 |
print("[MC] Server is ready!", flush=True)
|
|
|
|
| 131 |
except Exception as e:
|
| 132 |
print(f"[MC] Output error: {e}", flush=True)
|
|
|
|
| 133 |
|
| 134 |
threading.Thread(target=read_output, daemon=True).start()
|
| 135 |
|
|
@@ -142,6 +271,7 @@ def start_minecraft():
|
|
| 142 |
mc_process.stdin.write(cmd + "\n")
|
| 143 |
mc_process.stdin.flush()
|
| 144 |
print(f"[MC] Command: {cmd}", flush=True)
|
|
|
|
| 145 |
except Exception as e:
|
| 146 |
print(f"[MC] Command error: {e}", flush=True)
|
| 147 |
except queue.Empty:
|
|
@@ -172,8 +302,6 @@ HTML_TEMPLATE = """
|
|
| 172 |
touch-action: manipulation;
|
| 173 |
}
|
| 174 |
|
| 175 |
-
/* Mobile-first responsive design */
|
| 176 |
-
|
| 177 |
/* Top Navigation */
|
| 178 |
.top-nav {
|
| 179 |
background: #252a30;
|
|
@@ -327,6 +455,7 @@ HTML_TEMPLATE = """
|
|
| 327 |
.terminal-line.warn { color: #d29922; }
|
| 328 |
.terminal-line.error { color: #f85149; }
|
| 329 |
.terminal-line.success { color: #3fb950; }
|
|
|
|
| 330 |
|
| 331 |
/* Command Input Area */
|
| 332 |
.command-area {
|
|
@@ -827,9 +956,9 @@ HTML_TEMPLATE = """
|
|
| 827 |
let ws = null;
|
| 828 |
let reconnectAttempts = 0;
|
| 829 |
let maxReconnectAttempts = 10;
|
| 830 |
-
let serverStartTime = null;
|
| 831 |
let isServerRunning = false;
|
| 832 |
let sidebarOpen = false;
|
|
|
|
| 833 |
|
| 834 |
function updateConnectionStatus(status, message) {
|
| 835 |
connectionBar.className = 'connection-bar ' + status;
|
|
@@ -841,7 +970,7 @@ HTML_TEMPLATE = """
|
|
| 841 |
connectionBar.innerHTML = '<i class="fas ' + icons[status] + '"></i><span>' + message + '</span>';
|
| 842 |
}
|
| 843 |
|
| 844 |
-
function updateServerStatus(running) {
|
| 845 |
isServerRunning = running;
|
| 846 |
const statusEl = document.getElementById('serverStatus');
|
| 847 |
|
|
@@ -853,7 +982,9 @@ HTML_TEMPLATE = """
|
|
| 853 |
stopBtn.disabled = false;
|
| 854 |
commandInput.disabled = false;
|
| 855 |
sendBtn.disabled = false;
|
| 856 |
-
if (
|
|
|
|
|
|
|
| 857 |
} else {
|
| 858 |
statusEl.className = 'status-indicator status-offline';
|
| 859 |
statusEl.innerHTML = '<span class="pulse"></span>Offline';
|
|
@@ -869,6 +1000,20 @@ HTML_TEMPLATE = """
|
|
| 869 |
function addTerminalLine(text, type = '') {
|
| 870 |
const line = document.createElement('div');
|
| 871 |
line.className = 'terminal-line ' + type;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 872 |
line.textContent = text;
|
| 873 |
terminal.appendChild(line);
|
| 874 |
terminal.scrollTop = terminal.scrollHeight;
|
|
@@ -879,20 +1024,24 @@ HTML_TEMPLATE = """
|
|
| 879 |
}
|
| 880 |
}
|
| 881 |
|
| 882 |
-
function formatUptime(
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
const
|
| 886 |
-
const
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
return
|
|
|
|
|
|
|
|
|
|
| 891 |
}
|
| 892 |
|
| 893 |
function updateUptime() {
|
| 894 |
if (serverStartTime && isServerRunning) {
|
| 895 |
-
const
|
|
|
|
| 896 |
document.getElementById('uptime').textContent = formatUptime(uptime);
|
| 897 |
document.getElementById('uptimeSub').textContent = 'Running';
|
| 898 |
} else {
|
|
@@ -918,17 +1067,24 @@ HTML_TEMPLATE = """
|
|
| 918 |
const data = JSON.parse(event.data);
|
| 919 |
|
| 920 |
if (data.type === 'output') {
|
| 921 |
-
addTerminalLine(data.data
|
| 922 |
} else if (data.type === 'stats') {
|
| 923 |
document.getElementById('cpuLoad').textContent = data.cpu.toFixed(1) + '%';
|
| 924 |
document.getElementById('memoryUsage').textContent = data.memory.toFixed(1) + '%';
|
| 925 |
const memTotal = (data.memoryTotal / 1024 / 1024 / 1024).toFixed(2);
|
| 926 |
const memUsed = ((data.memoryTotal * data.memory / 100) / 1024 / 1024 / 1024).toFixed(2);
|
| 927 |
document.getElementById('memorySub').textContent = memUsed + ' / ' + memTotal + ' GB';
|
| 928 |
-
|
|
|
|
|
|
|
| 929 |
if (data.players !== undefined) {
|
| 930 |
document.getElementById('playerCount').textContent = data.players;
|
| 931 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 932 |
}
|
| 933 |
};
|
| 934 |
|
|
@@ -953,7 +1109,6 @@ HTML_TEMPLATE = """
|
|
| 953 |
const cmd = commandInput.value.trim();
|
| 954 |
if (cmd && ws && ws.readyState === WebSocket.OPEN) {
|
| 955 |
ws.send(JSON.stringify({type: 'command', data: cmd}));
|
| 956 |
-
addTerminalLine('> ' + cmd, 'info');
|
| 957 |
commandInput.value = '';
|
| 958 |
}
|
| 959 |
}
|
|
@@ -961,7 +1116,6 @@ HTML_TEMPLATE = """
|
|
| 961 |
function sendCmd(cmd) {
|
| 962 |
if (ws && ws.readyState === WebSocket.OPEN) {
|
| 963 |
ws.send(JSON.stringify({type: 'command', data: cmd}));
|
| 964 |
-
addTerminalLine('[' + cmd + ']', 'info');
|
| 965 |
}
|
| 966 |
}
|
| 967 |
|
|
@@ -1033,7 +1187,7 @@ HTML_TEMPLATE = """
|
|
| 1033 |
}, false);
|
| 1034 |
|
| 1035 |
document.getElementById('serverAddress').textContent = window.location.hostname;
|
| 1036 |
-
addTerminalLine('Connecting to Minecraft server...', '
|
| 1037 |
</script>
|
| 1038 |
</body>
|
| 1039 |
</html>
|
|
@@ -1052,27 +1206,18 @@ def health():
|
|
| 1052 |
|
| 1053 |
@sock.route('/ws')
|
| 1054 |
def websocket(ws):
|
|
|
|
| 1055 |
print("[WS] Client connected", flush=True)
|
| 1056 |
|
| 1057 |
-
# Send
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
| 1065 |
-
|
| 1066 |
-
|
| 1067 |
-
for line in temp_list:
|
| 1068 |
-
output_queue.put(line)
|
| 1069 |
-
history.append(line)
|
| 1070 |
-
|
| 1071 |
-
for line in history[-50:]:
|
| 1072 |
-
try:
|
| 1073 |
-
ws.send(json.dumps({'type': 'output', 'data': line}))
|
| 1074 |
-
except:
|
| 1075 |
-
break
|
| 1076 |
|
| 1077 |
def send_stats():
|
| 1078 |
while True:
|
|
@@ -1081,12 +1226,18 @@ def websocket(ws):
|
|
| 1081 |
memory = psutil.virtual_memory()
|
| 1082 |
running = mc_process is not None and mc_process.poll() is None
|
| 1083 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1084 |
ws.send(json.dumps({
|
| 1085 |
'type': 'stats',
|
| 1086 |
'cpu': cpu,
|
| 1087 |
'memory': memory.percent,
|
| 1088 |
'memoryTotal': memory.total,
|
| 1089 |
'running': running,
|
|
|
|
| 1090 |
'players': 0
|
| 1091 |
}))
|
| 1092 |
except Exception as e:
|
|
@@ -1134,6 +1285,7 @@ def websocket(ws):
|
|
| 1134 |
|
| 1135 |
def cleanup(signum=None, frame=None):
|
| 1136 |
print("\n[MAIN] Shutting down...", flush=True)
|
|
|
|
| 1137 |
if mc_process:
|
| 1138 |
try:
|
| 1139 |
mc_process.stdin.write("stop\n")
|
|
@@ -1150,12 +1302,25 @@ signal.signal(signal.SIGTERM, cleanup)
|
|
| 1150 |
signal.signal(signal.SIGINT, cleanup)
|
| 1151 |
|
| 1152 |
if __name__ == '__main__':
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1153 |
print("[MAIN] Starting web server...", flush=True)
|
| 1154 |
|
| 1155 |
# Start Minecraft immediately
|
| 1156 |
mc_thread = threading.Thread(target=start_minecraft, daemon=True)
|
| 1157 |
mc_thread.start()
|
| 1158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1159 |
from werkzeug.serving import run_simple
|
| 1160 |
run_simple('0.0.0.0', 7860, app, threaded=True, use_reloader=False)
|
| 1161 |
PYTHON_SCRIPT
|
|
@@ -1174,9 +1339,11 @@ fi
|
|
| 1174 |
|
| 1175 |
echo ""
|
| 1176 |
echo "=========================================="
|
| 1177 |
-
echo "✅ Minecraft Server with
|
| 1178 |
echo "🌐 Access at: https://your-space.hf.space"
|
| 1179 |
echo "📱 Mobile optimized interface enabled"
|
|
|
|
|
|
|
| 1180 |
echo "=========================================="
|
| 1181 |
|
| 1182 |
# Keep container alive
|
|
|
|
| 11 |
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
|
| 13 |
# Install Python dependencies
|
| 14 |
+
RUN pip3 install flask flask-sock psutil requests --break-system-packages
|
| 15 |
|
| 16 |
WORKDIR /app
|
| 17 |
|
|
|
|
| 49 |
echo "✅ EULA accepted"
|
| 50 |
fi
|
| 51 |
|
| 52 |
+
# Create logs directory for persistent storage
|
| 53 |
+
mkdir -p /app/logs
|
| 54 |
+
|
| 55 |
# Create backup script
|
| 56 |
cat > /app/backup.sh << 'BACKUPSCRIPT'
|
| 57 |
#!/bin/bash
|
|
|
|
| 80 |
echo ""
|
| 81 |
|
| 82 |
# ==========================================
|
| 83 |
+
# WEB TERMINAL SERVER (Fixed Uptime + Persistent Logs)
|
| 84 |
# ==========================================
|
| 85 |
cat > /app/web_terminal.py << 'PYTHON_SCRIPT'
|
| 86 |
import os
|
|
|
|
| 91 |
import time
|
| 92 |
import queue
|
| 93 |
import signal
|
| 94 |
+
import requests
|
| 95 |
from flask import Flask, render_template_string
|
| 96 |
from flask_sock import Sock
|
| 97 |
import psutil
|
| 98 |
+
from datetime import datetime
|
| 99 |
|
| 100 |
app = Flask(__name__)
|
| 101 |
sock = Sock(app)
|
|
|
|
| 105 |
command_queue = queue.Queue()
|
| 106 |
server_ready = threading.Event()
|
| 107 |
|
| 108 |
+
# Persistent storage variables
|
| 109 |
+
server_start_time = None
|
| 110 |
+
logs_history = []
|
| 111 |
+
LOGS_FILE = "/app/logs/console_logs.txt"
|
| 112 |
+
UPTIME_FILE = "/app/logs/server_start_time.txt"
|
| 113 |
+
GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN', '')
|
| 114 |
+
GITHUB_REPO = "Walukapah/mcserver"
|
| 115 |
+
|
| 116 |
+
def load_logs_from_file():
|
| 117 |
+
"""Load previous logs from local file"""
|
| 118 |
+
global logs_history
|
| 119 |
+
try:
|
| 120 |
+
if os.path.exists(LOGS_FILE):
|
| 121 |
+
with open(LOGS_FILE, 'r', encoding='utf-8') as f:
|
| 122 |
+
logs_history = f.read().splitlines()
|
| 123 |
+
# Keep only last 1000 lines
|
| 124 |
+
logs_history = logs_history[-1000:]
|
| 125 |
+
print(f"[LOGS] Loaded {len(logs_history)} lines from file", flush=True)
|
| 126 |
+
except Exception as e:
|
| 127 |
+
print(f"[LOGS] Error loading logs: {e}", flush=True)
|
| 128 |
+
logs_history = []
|
| 129 |
+
|
| 130 |
+
def save_logs_to_file():
|
| 131 |
+
"""Save logs to local file"""
|
| 132 |
+
try:
|
| 133 |
+
os.makedirs(os.path.dirname(LOGS_FILE), exist_ok=True)
|
| 134 |
+
with open(LOGS_FILE, 'w', encoding='utf-8') as f:
|
| 135 |
+
f.write('\n'.join(logs_history[-1000:]))
|
| 136 |
+
except Exception as e:
|
| 137 |
+
print(f"[LOGS] Error saving logs: {e}", flush=True)
|
| 138 |
+
|
| 139 |
+
def push_logs_to_github():
|
| 140 |
+
"""Push logs to GitHub for persistent storage"""
|
| 141 |
+
try:
|
| 142 |
+
if not GITHUB_TOKEN:
|
| 143 |
+
return
|
| 144 |
+
|
| 145 |
+
# Read current logs
|
| 146 |
+
if not os.path.exists(LOGS_FILE):
|
| 147 |
+
return
|
| 148 |
+
|
| 149 |
+
with open(LOGS_FILE, 'r', encoding='utf-8') as f:
|
| 150 |
+
content = f.read()
|
| 151 |
+
|
| 152 |
+
# GitHub API - get current file SHA
|
| 153 |
+
api_url = f"https://api.github.com/repos/{GITHUB_REPO}/contents/logs/console_logs.txt"
|
| 154 |
+
headers = {
|
| 155 |
+
"Authorization": f"token {GITHUB_TOKEN}",
|
| 156 |
+
"Accept": "application/vnd.github.v3+json"
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
# Check if file exists
|
| 160 |
+
sha = None
|
| 161 |
+
try:
|
| 162 |
+
resp = requests.get(api_url, headers=headers, timeout=10)
|
| 163 |
+
if resp.status_code == 200:
|
| 164 |
+
sha = resp.json().get('sha')
|
| 165 |
+
except:
|
| 166 |
+
pass
|
| 167 |
+
|
| 168 |
+
# Create or update file
|
| 169 |
+
import base64
|
| 170 |
+
encoded_content = base64.b64encode(content.encode()).decode()
|
| 171 |
+
|
| 172 |
+
data = {
|
| 173 |
+
"message": f"Update logs - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
|
| 174 |
+
"content": encoded_content,
|
| 175 |
+
"branch": "main"
|
| 176 |
+
}
|
| 177 |
+
if sha:
|
| 178 |
+
data["sha"] = sha
|
| 179 |
+
|
| 180 |
+
resp = requests.put(api_url, headers=headers, json=data, timeout=10)
|
| 181 |
+
if resp.status_code in [200, 201]:
|
| 182 |
+
print("[LOGS] Pushed to GitHub successfully", flush=True)
|
| 183 |
+
except Exception as e:
|
| 184 |
+
print(f"[LOGS] GitHub push error: {e}", flush=True)
|
| 185 |
+
|
| 186 |
+
def load_uptime_from_file():
|
| 187 |
+
"""Load server start time from file"""
|
| 188 |
+
global server_start_time
|
| 189 |
+
try:
|
| 190 |
+
if os.path.exists(UPTIME_FILE):
|
| 191 |
+
with open(UPTIME_FILE, 'r') as f:
|
| 192 |
+
timestamp = f.read().strip()
|
| 193 |
+
if timestamp:
|
| 194 |
+
server_start_time = float(timestamp)
|
| 195 |
+
print(f"[UPTIME] Loaded start time: {datetime.fromtimestamp(server_start_time)}", flush=True)
|
| 196 |
+
except Exception as e:
|
| 197 |
+
print(f"[UPTIME] Error loading uptime: {e}", flush=True)
|
| 198 |
+
server_start_time = None
|
| 199 |
+
|
| 200 |
+
def save_uptime_to_file():
|
| 201 |
+
"""Save server start time to file"""
|
| 202 |
+
try:
|
| 203 |
+
os.makedirs(os.path.dirname(UPTIME_FILE), exist_ok=True)
|
| 204 |
+
with open(UPTIME_FILE, 'w') as f:
|
| 205 |
+
f.write(str(server_start_time))
|
| 206 |
+
except Exception as e:
|
| 207 |
+
print(f"[UPTIME] Error saving uptime: {e}", flush=True)
|
| 208 |
+
|
| 209 |
+
def add_log_line(line):
|
| 210 |
+
"""Add a log line to history and save"""
|
| 211 |
+
global logs_history
|
| 212 |
+
timestamp = datetime.now().strftime('%H:%M:%S')
|
| 213 |
+
log_entry = f"[{timestamp}] {line}"
|
| 214 |
+
logs_history.append(log_entry)
|
| 215 |
+
# Keep only last 1000 lines
|
| 216 |
+
if len(logs_history) > 1000:
|
| 217 |
+
logs_history = logs_history[-1000:]
|
| 218 |
+
output_queue.put(log_entry)
|
| 219 |
+
# Save to file periodically (every 10 lines)
|
| 220 |
+
if len(logs_history) % 10 == 0:
|
| 221 |
+
save_logs_to_file()
|
| 222 |
+
|
| 223 |
def start_minecraft():
|
| 224 |
+
global mc_process, server_start_time
|
| 225 |
mc_dir = "/app/mcserver"
|
| 226 |
|
| 227 |
print("[MC] Starting Minecraft server...", flush=True)
|
| 228 |
+
add_log_line("[SYSTEM] Starting Minecraft server...")
|
| 229 |
+
|
| 230 |
+
# Record start time
|
| 231 |
+
server_start_time = time.time()
|
| 232 |
+
save_uptime_to_file()
|
| 233 |
|
| 234 |
mc_process = subprocess.Popen(
|
| 235 |
["java", "-Xms1G", "-Xmx14G", "-XX:+UseG1GC",
|
|
|
|
| 245 |
)
|
| 246 |
|
| 247 |
print(f"[MC] Minecraft PID: {mc_process.pid}", flush=True)
|
| 248 |
+
add_log_line(f"[SYSTEM] Minecraft server started with PID: {mc_process.pid}")
|
| 249 |
|
| 250 |
def read_output():
|
| 251 |
try:
|
| 252 |
for line in mc_process.stdout:
|
| 253 |
+
line = line.rstrip()
|
| 254 |
+
add_log_line(line)
|
| 255 |
if "Done" in line and "For help, type" in line:
|
| 256 |
server_ready.set()
|
| 257 |
print("[MC] Server is ready!", flush=True)
|
| 258 |
+
add_log_line("[SYSTEM] Server is ready!")
|
| 259 |
except Exception as e:
|
| 260 |
print(f"[MC] Output error: {e}", flush=True)
|
| 261 |
+
add_log_line(f"[ERROR] Output reader error: {e}")
|
| 262 |
|
| 263 |
threading.Thread(target=read_output, daemon=True).start()
|
| 264 |
|
|
|
|
| 271 |
mc_process.stdin.write(cmd + "\n")
|
| 272 |
mc_process.stdin.flush()
|
| 273 |
print(f"[MC] Command: {cmd}", flush=True)
|
| 274 |
+
add_log_line(f"> {cmd}")
|
| 275 |
except Exception as e:
|
| 276 |
print(f"[MC] Command error: {e}", flush=True)
|
| 277 |
except queue.Empty:
|
|
|
|
| 302 |
touch-action: manipulation;
|
| 303 |
}
|
| 304 |
|
|
|
|
|
|
|
| 305 |
/* Top Navigation */
|
| 306 |
.top-nav {
|
| 307 |
background: #252a30;
|
|
|
|
| 455 |
.terminal-line.warn { color: #d29922; }
|
| 456 |
.terminal-line.error { color: #f85149; }
|
| 457 |
.terminal-line.success { color: #3fb950; }
|
| 458 |
+
.terminal-line.system { color: #a855f7; font-weight: 500; }
|
| 459 |
|
| 460 |
/* Command Input Area */
|
| 461 |
.command-area {
|
|
|
|
| 956 |
let ws = null;
|
| 957 |
let reconnectAttempts = 0;
|
| 958 |
let maxReconnectAttempts = 10;
|
|
|
|
| 959 |
let isServerRunning = false;
|
| 960 |
let sidebarOpen = false;
|
| 961 |
+
let serverStartTime = null;
|
| 962 |
|
| 963 |
function updateConnectionStatus(status, message) {
|
| 964 |
connectionBar.className = 'connection-bar ' + status;
|
|
|
|
| 970 |
connectionBar.innerHTML = '<i class="fas ' + icons[status] + '"></i><span>' + message + '</span>';
|
| 971 |
}
|
| 972 |
|
| 973 |
+
function updateServerStatus(running, startTime = null) {
|
| 974 |
isServerRunning = running;
|
| 975 |
const statusEl = document.getElementById('serverStatus');
|
| 976 |
|
|
|
|
| 982 |
stopBtn.disabled = false;
|
| 983 |
commandInput.disabled = false;
|
| 984 |
sendBtn.disabled = false;
|
| 985 |
+
if (startTime) {
|
| 986 |
+
serverStartTime = startTime * 1000; // Convert to milliseconds
|
| 987 |
+
}
|
| 988 |
} else {
|
| 989 |
statusEl.className = 'status-indicator status-offline';
|
| 990 |
statusEl.innerHTML = '<span class="pulse"></span>Offline';
|
|
|
|
| 1000 |
function addTerminalLine(text, type = '') {
|
| 1001 |
const line = document.createElement('div');
|
| 1002 |
line.className = 'terminal-line ' + type;
|
| 1003 |
+
|
| 1004 |
+
// Check if it's a system message
|
| 1005 |
+
if (text.includes('[SYSTEM]')) {
|
| 1006 |
+
line.className += ' system';
|
| 1007 |
+
} else if (text.includes('> ')) {
|
| 1008 |
+
line.className += ' info';
|
| 1009 |
+
} else if (text.toLowerCase().includes('error') || text.toLowerCase().includes('exception')) {
|
| 1010 |
+
line.className += ' error';
|
| 1011 |
+
} else if (text.toLowerCase().includes('warn')) {
|
| 1012 |
+
line.className += ' warn';
|
| 1013 |
+
} else if (text.toLowerCase().includes('done') || text.toLowerCase().includes('success')) {
|
| 1014 |
+
line.className += ' success';
|
| 1015 |
+
}
|
| 1016 |
+
|
| 1017 |
line.textContent = text;
|
| 1018 |
terminal.appendChild(line);
|
| 1019 |
terminal.scrollTop = terminal.scrollHeight;
|
|
|
|
| 1024 |
}
|
| 1025 |
}
|
| 1026 |
|
| 1027 |
+
function formatUptime(seconds) {
|
| 1028 |
+
if (!seconds || seconds <= 0) return 'Offline';
|
| 1029 |
+
|
| 1030 |
+
const days = Math.floor(seconds / 86400);
|
| 1031 |
+
const hours = Math.floor((seconds % 86400) / 3600);
|
| 1032 |
+
const minutes = Math.floor((seconds % 3600) / 60);
|
| 1033 |
+
const secs = Math.floor(seconds % 60);
|
| 1034 |
+
|
| 1035 |
+
if (days > 0) return days + 'd ' + hours + 'h ' + minutes + 'm';
|
| 1036 |
+
if (hours > 0) return hours + 'h ' + minutes + 'm ' + secs + 's';
|
| 1037 |
+
if (minutes > 0) return minutes + 'm ' + secs + 's';
|
| 1038 |
+
return secs + 's';
|
| 1039 |
}
|
| 1040 |
|
| 1041 |
function updateUptime() {
|
| 1042 |
if (serverStartTime && isServerRunning) {
|
| 1043 |
+
const now = Date.now();
|
| 1044 |
+
const uptime = Math.floor((now - serverStartTime) / 1000);
|
| 1045 |
document.getElementById('uptime').textContent = formatUptime(uptime);
|
| 1046 |
document.getElementById('uptimeSub').textContent = 'Running';
|
| 1047 |
} else {
|
|
|
|
| 1067 |
const data = JSON.parse(event.data);
|
| 1068 |
|
| 1069 |
if (data.type === 'output') {
|
| 1070 |
+
addTerminalLine(data.data);
|
| 1071 |
} else if (data.type === 'stats') {
|
| 1072 |
document.getElementById('cpuLoad').textContent = data.cpu.toFixed(1) + '%';
|
| 1073 |
document.getElementById('memoryUsage').textContent = data.memory.toFixed(1) + '%';
|
| 1074 |
const memTotal = (data.memoryTotal / 1024 / 1024 / 1024).toFixed(2);
|
| 1075 |
const memUsed = ((data.memoryTotal * data.memory / 100) / 1024 / 1024 / 1024).toFixed(2);
|
| 1076 |
document.getElementById('memorySub').textContent = memUsed + ' / ' + memTotal + ' GB';
|
| 1077 |
+
|
| 1078 |
+
updateServerStatus(data.running, data.serverStartTime);
|
| 1079 |
+
|
| 1080 |
if (data.players !== undefined) {
|
| 1081 |
document.getElementById('playerCount').textContent = data.players;
|
| 1082 |
}
|
| 1083 |
+
} else if (data.type === 'history') {
|
| 1084 |
+
// Load history logs
|
| 1085 |
+
data.logs.forEach(log => {
|
| 1086 |
+
addTerminalLine(log);
|
| 1087 |
+
});
|
| 1088 |
}
|
| 1089 |
};
|
| 1090 |
|
|
|
|
| 1109 |
const cmd = commandInput.value.trim();
|
| 1110 |
if (cmd && ws && ws.readyState === WebSocket.OPEN) {
|
| 1111 |
ws.send(JSON.stringify({type: 'command', data: cmd}));
|
|
|
|
| 1112 |
commandInput.value = '';
|
| 1113 |
}
|
| 1114 |
}
|
|
|
|
| 1116 |
function sendCmd(cmd) {
|
| 1117 |
if (ws && ws.readyState === WebSocket.OPEN) {
|
| 1118 |
ws.send(JSON.stringify({type: 'command', data: cmd}));
|
|
|
|
| 1119 |
}
|
| 1120 |
}
|
| 1121 |
|
|
|
|
| 1187 |
}, false);
|
| 1188 |
|
| 1189 |
document.getElementById('serverAddress').textContent = window.location.hostname;
|
| 1190 |
+
addTerminalLine('[SYSTEM] Connecting to Minecraft server...', 'system');
|
| 1191 |
</script>
|
| 1192 |
</body>
|
| 1193 |
</html>
|
|
|
|
| 1206 |
|
| 1207 |
@sock.route('/ws')
|
| 1208 |
def websocket(ws):
|
| 1209 |
+
global server_start_time
|
| 1210 |
print("[WS] Client connected", flush=True)
|
| 1211 |
|
| 1212 |
+
# Send history logs first
|
| 1213 |
+
try:
|
| 1214 |
+
if logs_history:
|
| 1215 |
+
ws.send(json.dumps({
|
| 1216 |
+
'type': 'history',
|
| 1217 |
+
'logs': logs_history[-100:] # Send last 100 lines
|
| 1218 |
+
}))
|
| 1219 |
+
except Exception as e:
|
| 1220 |
+
print(f"[WS] History send error: {e}", flush=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1221 |
|
| 1222 |
def send_stats():
|
| 1223 |
while True:
|
|
|
|
| 1226 |
memory = psutil.virtual_memory()
|
| 1227 |
running = mc_process is not None and mc_process.poll() is None
|
| 1228 |
|
| 1229 |
+
# Calculate actual uptime from server_start_time
|
| 1230 |
+
uptime_to_send = None
|
| 1231 |
+
if running and server_start_time:
|
| 1232 |
+
uptime_to_send = server_start_time
|
| 1233 |
+
|
| 1234 |
ws.send(json.dumps({
|
| 1235 |
'type': 'stats',
|
| 1236 |
'cpu': cpu,
|
| 1237 |
'memory': memory.percent,
|
| 1238 |
'memoryTotal': memory.total,
|
| 1239 |
'running': running,
|
| 1240 |
+
'serverStartTime': uptime_to_send,
|
| 1241 |
'players': 0
|
| 1242 |
}))
|
| 1243 |
except Exception as e:
|
|
|
|
| 1285 |
|
| 1286 |
def cleanup(signum=None, frame=None):
|
| 1287 |
print("\n[MAIN] Shutting down...", flush=True)
|
| 1288 |
+
save_logs_to_file()
|
| 1289 |
if mc_process:
|
| 1290 |
try:
|
| 1291 |
mc_process.stdin.write("stop\n")
|
|
|
|
| 1302 |
signal.signal(signal.SIGINT, cleanup)
|
| 1303 |
|
| 1304 |
if __name__ == '__main__':
|
| 1305 |
+
# Load previous logs and uptime
|
| 1306 |
+
load_logs_from_file()
|
| 1307 |
+
load_uptime_from_file()
|
| 1308 |
+
|
| 1309 |
print("[MAIN] Starting web server...", flush=True)
|
| 1310 |
|
| 1311 |
# Start Minecraft immediately
|
| 1312 |
mc_thread = threading.Thread(target=start_minecraft, daemon=True)
|
| 1313 |
mc_thread.start()
|
| 1314 |
|
| 1315 |
+
# Start GitHub push thread (every 5 minutes)
|
| 1316 |
+
def github_push_loop():
|
| 1317 |
+
while True:
|
| 1318 |
+
time.sleep(300)
|
| 1319 |
+
push_logs_to_github()
|
| 1320 |
+
|
| 1321 |
+
github_thread = threading.Thread(target=github_push_loop, daemon=True)
|
| 1322 |
+
github_thread.start()
|
| 1323 |
+
|
| 1324 |
from werkzeug.serving import run_simple
|
| 1325 |
run_simple('0.0.0.0', 7860, app, threaded=True, use_reloader=False)
|
| 1326 |
PYTHON_SCRIPT
|
|
|
|
| 1339 |
|
| 1340 |
echo ""
|
| 1341 |
echo "=========================================="
|
| 1342 |
+
echo "✅ Minecraft Server with Fixed Uptime + Persistent Logs is LIVE!"
|
| 1343 |
echo "🌐 Access at: https://your-space.hf.space"
|
| 1344 |
echo "📱 Mobile optimized interface enabled"
|
| 1345 |
+
echo "💾 Logs are now persistent across refreshes"
|
| 1346 |
+
echo "⏰ Uptime tracking fixed"
|
| 1347 |
echo "=========================================="
|
| 1348 |
|
| 1349 |
# Keep container alive
|