Update Dockerfile
Browse files- Dockerfile +280 -133
Dockerfile
CHANGED
|
@@ -49,30 +49,100 @@ if [ ! -f eula.txt ]; then
|
|
| 49 |
echo "✅ EULA accepted"
|
| 50 |
fi
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
# Create logs directory for persistent storage
|
| 53 |
mkdir -p /app/logs
|
| 54 |
|
| 55 |
-
#
|
| 56 |
-
|
|
|
|
|
|
|
| 57 |
#!/bin/bash
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
git add -A
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
BACKUPSCRIPT
|
| 65 |
|
| 66 |
-
chmod +x /app/backup.sh
|
| 67 |
|
| 68 |
-
#
|
|
|
|
|
|
|
| 69 |
(
|
|
|
|
| 70 |
while true; do
|
| 71 |
sleep 300
|
| 72 |
-
|
| 73 |
-
echo "💾 [BACKUP] Running auto-backup at $(date)"
|
| 74 |
-
/app/backup.sh 2>/dev/null || true
|
| 75 |
-
echo ""
|
| 76 |
done
|
| 77 |
) &
|
| 78 |
|
|
@@ -80,7 +150,7 @@ echo "✅ Auto-backup enabled (every 5 minutes)"
|
|
| 80 |
echo ""
|
| 81 |
|
| 82 |
# ==========================================
|
| 83 |
-
# WEB TERMINAL SERVER
|
| 84 |
# ==========================================
|
| 85 |
cat > /app/web_terminal.py << 'PYTHON_SCRIPT'
|
| 86 |
import os
|
|
@@ -273,6 +343,52 @@ def start_minecraft():
|
|
| 273 |
|
| 274 |
threading.Thread(target=process_commands, daemon=True).start()
|
| 275 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
HTML_TEMPLATE = """
|
| 277 |
<!DOCTYPE html>
|
| 278 |
<html lang="en">
|
|
@@ -352,7 +468,6 @@ HTML_TEMPLATE = """
|
|
| 352 |
|
| 353 |
.nav-btn:hover { color: #fff; background: rgba(255,255,255,0.1); }
|
| 354 |
|
| 355 |
-
/* Status indicator dot on status icon */
|
| 356 |
.nav-btn .status-dot {
|
| 357 |
position: absolute;
|
| 358 |
bottom: 6px;
|
|
@@ -364,9 +479,7 @@ HTML_TEMPLATE = """
|
|
| 364 |
border: 2px solid #252a30;
|
| 365 |
}
|
| 366 |
|
| 367 |
-
.nav-btn .status-dot.offline {
|
| 368 |
-
background: #ef4444;
|
| 369 |
-
}
|
| 370 |
|
| 371 |
/* Main Layout */
|
| 372 |
.main-container {
|
|
@@ -433,6 +546,8 @@ HTML_TEMPLATE = """
|
|
| 433 |
.btn-secondary:hover { background: #4b5563; }
|
| 434 |
.btn-danger { background: #dc2626; color: white; }
|
| 435 |
.btn-danger:hover { background: #b91c1c; }
|
|
|
|
|
|
|
| 436 |
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
| 437 |
|
| 438 |
/* Terminal */
|
|
@@ -465,7 +580,6 @@ HTML_TEMPLATE = """
|
|
| 465 |
.terminal-line.success { color: #3fb950; }
|
| 466 |
.terminal-line.system { color: #a855f7; font-weight: 500; }
|
| 467 |
|
| 468 |
-
/* Typing Preview Line */
|
| 469 |
.typing-preview {
|
| 470 |
color: #6e7681;
|
| 471 |
font-style: italic;
|
|
@@ -663,9 +777,7 @@ HTML_TEMPLATE = """
|
|
| 663 |
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
|
| 664 |
}
|
| 665 |
|
| 666 |
-
.status-modal.active {
|
| 667 |
-
display: block;
|
| 668 |
-
}
|
| 669 |
|
| 670 |
.status-modal-header {
|
| 671 |
display: flex;
|
|
@@ -690,9 +802,7 @@ HTML_TEMPLATE = """
|
|
| 690 |
font-size: 18px;
|
| 691 |
}
|
| 692 |
|
| 693 |
-
.status-modal-close:hover {
|
| 694 |
-
color: #fff;
|
| 695 |
-
}
|
| 696 |
|
| 697 |
.status-item {
|
| 698 |
display: flex;
|
|
@@ -701,20 +811,10 @@ HTML_TEMPLATE = """
|
|
| 701 |
border-bottom: 1px solid #2a2f35;
|
| 702 |
}
|
| 703 |
|
| 704 |
-
.status-item:last-child {
|
| 705 |
-
border-bottom: none;
|
| 706 |
-
}
|
| 707 |
-
|
| 708 |
-
.status-label-modal {
|
| 709 |
-
color: #9ca3af;
|
| 710 |
-
font-size: 13px;
|
| 711 |
-
}
|
| 712 |
|
| 713 |
-
.status-
|
| 714 |
-
|
| 715 |
-
font-weight: 500;
|
| 716 |
-
font-size: 13px;
|
| 717 |
-
}
|
| 718 |
|
| 719 |
.modal-overlay {
|
| 720 |
display: none;
|
|
@@ -727,15 +827,61 @@ HTML_TEMPLATE = """
|
|
| 727 |
z-index: 250;
|
| 728 |
}
|
| 729 |
|
| 730 |
-
.modal-overlay.active {
|
| 731 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 732 |
}
|
| 733 |
|
|
|
|
|
|
|
|
|
|
| 734 |
/* ============================================
|
| 735 |
MOBILE RESPONSIVE BREAKPOINTS
|
| 736 |
============================================ */
|
| 737 |
|
| 738 |
-
/* Tablet (768px and below) */
|
| 739 |
@media screen and (max-width: 768px) {
|
| 740 |
.sidebar {
|
| 741 |
position: fixed;
|
|
@@ -748,21 +894,11 @@ HTML_TEMPLATE = """
|
|
| 748 |
box-shadow: -4px 0 20px rgba(0,0,0,0.5);
|
| 749 |
}
|
| 750 |
|
| 751 |
-
.sidebar.active {
|
| 752 |
-
|
| 753 |
-
}
|
| 754 |
-
|
| 755 |
-
.connection-bar {
|
| 756 |
-
right: 0;
|
| 757 |
-
}
|
| 758 |
|
| 759 |
-
.console-header {
|
| 760 |
-
|
| 761 |
-
}
|
| 762 |
-
|
| 763 |
-
.server-title {
|
| 764 |
-
font-size: 14px;
|
| 765 |
-
}
|
| 766 |
|
| 767 |
.btn {
|
| 768 |
padding: 6px 10px;
|
|
@@ -770,43 +906,32 @@ HTML_TEMPLATE = """
|
|
| 770 |
min-height: 32px;
|
| 771 |
}
|
| 772 |
|
| 773 |
-
.btn span {
|
| 774 |
-
display: none;
|
| 775 |
-
}
|
| 776 |
|
| 777 |
.terminal {
|
| 778 |
font-size: 11px;
|
| 779 |
padding: 10px;
|
| 780 |
}
|
| 781 |
|
| 782 |
-
.command-area {
|
| 783 |
-
padding: 8px 10px;
|
| 784 |
-
}
|
| 785 |
|
| 786 |
.send-btn {
|
| 787 |
padding: 10px 14px;
|
| 788 |
font-size: 12px;
|
| 789 |
}
|
| 790 |
|
| 791 |
-
.send-btn span {
|
| 792 |
-
display: none;
|
| 793 |
-
}
|
| 794 |
}
|
| 795 |
|
| 796 |
-
/* Mobile (480px and below) */
|
| 797 |
@media screen and (max-width: 480px) {
|
| 798 |
-
.top-nav {
|
| 799 |
-
padding: 0 10px;
|
| 800 |
-
}
|
| 801 |
|
| 802 |
.nav-tab {
|
| 803 |
padding: 0 10px;
|
| 804 |
font-size: 12px;
|
| 805 |
}
|
| 806 |
|
| 807 |
-
.nav-tab i {
|
| 808 |
-
font-size: 12px;
|
| 809 |
-
}
|
| 810 |
|
| 811 |
.console-header {
|
| 812 |
flex-direction: column;
|
|
@@ -830,13 +955,8 @@ HTML_TEMPLATE = """
|
|
| 830 |
line-height: 1.4;
|
| 831 |
}
|
| 832 |
|
| 833 |
-
.command-input-wrapper {
|
| 834 |
-
|
| 835 |
-
}
|
| 836 |
-
|
| 837 |
-
#commandInput {
|
| 838 |
-
font-size: 16px;
|
| 839 |
-
}
|
| 840 |
|
| 841 |
.send-btn {
|
| 842 |
min-height: 40px;
|
|
@@ -848,9 +968,7 @@ HTML_TEMPLATE = """
|
|
| 848 |
right: -100%;
|
| 849 |
}
|
| 850 |
|
| 851 |
-
.stat-card {
|
| 852 |
-
padding: 10px;
|
| 853 |
-
}
|
| 854 |
|
| 855 |
.stat-icon {
|
| 856 |
width: 36px;
|
|
@@ -858,9 +976,7 @@ HTML_TEMPLATE = """
|
|
| 858 |
font-size: 16px;
|
| 859 |
}
|
| 860 |
|
| 861 |
-
.stat-value {
|
| 862 |
-
font-size: 14px;
|
| 863 |
-
}
|
| 864 |
|
| 865 |
.status-modal {
|
| 866 |
min-width: 90vw;
|
|
@@ -868,39 +984,17 @@ HTML_TEMPLATE = """
|
|
| 868 |
}
|
| 869 |
}
|
| 870 |
|
| 871 |
-
/* Landscape mobile */
|
| 872 |
@media screen and (max-height: 500px) and (orientation: landscape) {
|
| 873 |
-
.console-header {
|
| 874 |
-
|
| 875 |
-
}
|
| 876 |
-
|
| 877 |
-
.command-area {
|
| 878 |
-
padding: 6px 10px;
|
| 879 |
-
}
|
| 880 |
-
|
| 881 |
-
.sidebar {
|
| 882 |
-
padding: 10px;
|
| 883 |
-
}
|
| 884 |
-
|
| 885 |
-
.stat-card {
|
| 886 |
-
padding: 8px;
|
| 887 |
-
margin-bottom: 6px;
|
| 888 |
-
}
|
| 889 |
}
|
| 890 |
|
| 891 |
-
/* Large screens */
|
| 892 |
@media screen and (min-width: 1400px) {
|
| 893 |
-
.sidebar {
|
| 894 |
-
|
| 895 |
-
}
|
| 896 |
-
|
| 897 |
-
.terminal {
|
| 898 |
-
font-size: 13px;
|
| 899 |
-
}
|
| 900 |
-
|
| 901 |
-
.stat-value {
|
| 902 |
-
font-size: 18px;
|
| 903 |
-
}
|
| 904 |
}
|
| 905 |
</style>
|
| 906 |
</head>
|
|
@@ -1072,6 +1166,20 @@ HTML_TEMPLATE = """
|
|
| 1072 |
<div class="stat-sub">Online players</div>
|
| 1073 |
</div>
|
| 1074 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1075 |
</div>
|
| 1076 |
</div>
|
| 1077 |
|
|
@@ -1088,6 +1196,8 @@ HTML_TEMPLATE = """
|
|
| 1088 |
const statusModal = document.getElementById('statusModal');
|
| 1089 |
const modalOverlay = document.getElementById('modalOverlay');
|
| 1090 |
const navStatusDot = document.getElementById('navStatusDot');
|
|
|
|
|
|
|
| 1091 |
|
| 1092 |
let ws = null;
|
| 1093 |
let reconnectAttempts = 0;
|
|
@@ -1107,7 +1217,6 @@ HTML_TEMPLATE = """
|
|
| 1107 |
};
|
| 1108 |
connectionBar.innerHTML = '<i class="fas ' + icons[status] + '"></i><span>' + message + '</span>';
|
| 1109 |
|
| 1110 |
-
// Update modal status
|
| 1111 |
const wsStatus = document.getElementById('modalWsStatus');
|
| 1112 |
if (status === 'connected') {
|
| 1113 |
wsStatus.innerHTML = '<span style="color: #22c55e;">● Connected</span>';
|
|
@@ -1150,7 +1259,6 @@ HTML_TEMPLATE = """
|
|
| 1150 |
}
|
| 1151 |
|
| 1152 |
function addTerminalLine(text, type = '') {
|
| 1153 |
-
// Remove typing preview if exists
|
| 1154 |
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1155 |
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1156 |
typingPreviewLine = null;
|
|
@@ -1183,13 +1291,11 @@ HTML_TEMPLATE = """
|
|
| 1183 |
function updateTypingPreview() {
|
| 1184 |
const text = commandInput.value.trim();
|
| 1185 |
|
| 1186 |
-
// Remove old preview
|
| 1187 |
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1188 |
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1189 |
typingPreviewLine = null;
|
| 1190 |
}
|
| 1191 |
|
| 1192 |
-
// Show preview if text exists
|
| 1193 |
if (text && isServerRunning) {
|
| 1194 |
typingPreviewLine = document.createElement('div');
|
| 1195 |
typingPreviewLine.className = 'terminal-line typing-preview';
|
|
@@ -1236,6 +1342,60 @@ HTML_TEMPLATE = """
|
|
| 1236 |
|
| 1237 |
setInterval(updateUptime, 1000);
|
| 1238 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1239 |
function connect() {
|
| 1240 |
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
| 1241 |
ws = new WebSocket(protocol + '//' + window.location.host + '/ws');
|
|
@@ -1260,7 +1420,6 @@ HTML_TEMPLATE = """
|
|
| 1260 |
const memUsed = ((data.memoryTotal * data.memory / 100) / 1024 / 1024 / 1024).toFixed(2);
|
| 1261 |
document.getElementById('memorySub').textContent = memUsed + ' / ' + memTotal + ' GB';
|
| 1262 |
|
| 1263 |
-
// Update modal stats
|
| 1264 |
document.getElementById('modalCpu').textContent = data.cpu.toFixed(1) + '%';
|
| 1265 |
document.getElementById('modalMemory').textContent = data.memory.toFixed(1) + '%';
|
| 1266 |
|
|
@@ -1297,7 +1456,6 @@ HTML_TEMPLATE = """
|
|
| 1297 |
function sendInput() {
|
| 1298 |
const cmd = commandInput.value.trim();
|
| 1299 |
if (cmd && ws && ws.readyState === WebSocket.OPEN) {
|
| 1300 |
-
// Remove preview immediately on send
|
| 1301 |
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1302 |
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1303 |
typingPreviewLine = null;
|
|
@@ -1313,13 +1471,11 @@ HTML_TEMPLATE = """
|
|
| 1313 |
}
|
| 1314 |
}
|
| 1315 |
|
| 1316 |
-
// Input event listeners for real-time typing preview
|
| 1317 |
commandInput.addEventListener('input', updateTypingPreview);
|
| 1318 |
commandInput.addEventListener('keyup', updateTypingPreview);
|
| 1319 |
|
| 1320 |
commandInput.addEventListener('keypress', (e) => {
|
| 1321 |
if (e.key === 'Enter') {
|
| 1322 |
-
// Remove preview immediately on enter
|
| 1323 |
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1324 |
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1325 |
typingPreviewLine = null;
|
|
@@ -1328,7 +1484,6 @@ HTML_TEMPLATE = """
|
|
| 1328 |
}
|
| 1329 |
});
|
| 1330 |
|
| 1331 |
-
// Clear preview when input loses focus
|
| 1332 |
commandInput.addEventListener('blur', () => {
|
| 1333 |
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1334 |
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
|
@@ -1363,12 +1518,9 @@ HTML_TEMPLATE = """
|
|
| 1363 |
function toggleStatusModal() {
|
| 1364 |
statusModal.classList.toggle('active');
|
| 1365 |
modalOverlay.classList.toggle('active');
|
| 1366 |
-
|
| 1367 |
-
// Update address in modal
|
| 1368 |
document.getElementById('modalAddress').textContent = window.location.hostname;
|
| 1369 |
}
|
| 1370 |
|
| 1371 |
-
// Close sidebar when clicking outside on mobile
|
| 1372 |
document.addEventListener('click', (e) => {
|
| 1373 |
if (window.innerWidth <= 768 && sidebarOpen) {
|
| 1374 |
if (!sidebar.contains(e.target) && !e.target.closest('.sidebar-toggle')) {
|
|
@@ -1377,14 +1529,12 @@ HTML_TEMPLATE = """
|
|
| 1377 |
}
|
| 1378 |
});
|
| 1379 |
|
| 1380 |
-
// Handle orientation change
|
| 1381 |
window.addEventListener('orientationchange', () => {
|
| 1382 |
setTimeout(() => {
|
| 1383 |
terminal.scrollTop = terminal.scrollHeight;
|
| 1384 |
}, 300);
|
| 1385 |
});
|
| 1386 |
|
| 1387 |
-
// Handle resize
|
| 1388 |
let resizeTimer;
|
| 1389 |
window.addEventListener('resize', () => {
|
| 1390 |
clearTimeout(resizeTimer);
|
|
@@ -1397,7 +1547,6 @@ HTML_TEMPLATE = """
|
|
| 1397 |
}, 250);
|
| 1398 |
});
|
| 1399 |
|
| 1400 |
-
// Prevent zoom on double tap for iOS
|
| 1401 |
let lastTouchEnd = 0;
|
| 1402 |
document.addEventListener('touchend', (e) => {
|
| 1403 |
const now = Date.now();
|
|
@@ -1430,7 +1579,6 @@ def websocket(ws):
|
|
| 1430 |
global server_start_time
|
| 1431 |
print("[WS] Client connected", flush=True)
|
| 1432 |
|
| 1433 |
-
# Send history logs first
|
| 1434 |
try:
|
| 1435 |
if logs_history:
|
| 1436 |
ws.send(json.dumps({
|
|
@@ -1468,7 +1616,6 @@ def websocket(ws):
|
|
| 1468 |
stats_thread = threading.Thread(target=send_stats, daemon=True)
|
| 1469 |
stats_thread.start()
|
| 1470 |
|
| 1471 |
-
# Send output
|
| 1472 |
def send_output():
|
| 1473 |
while True:
|
| 1474 |
try:
|
|
@@ -1485,7 +1632,6 @@ def websocket(ws):
|
|
| 1485 |
output_thread = threading.Thread(target=send_output, daemon=True)
|
| 1486 |
output_thread.start()
|
| 1487 |
|
| 1488 |
-
# Receive commands
|
| 1489 |
while True:
|
| 1490 |
try:
|
| 1491 |
data = ws.receive(timeout=5)
|
|
@@ -1556,14 +1702,15 @@ fi
|
|
| 1556 |
|
| 1557 |
echo ""
|
| 1558 |
echo "=========================================="
|
| 1559 |
-
echo "✅ Minecraft Server
|
| 1560 |
echo "🌐 Access at: https://your-space.hf.space"
|
| 1561 |
echo "📱 Mobile optimized interface enabled"
|
| 1562 |
echo "💾 Logs are now persistent across refreshes"
|
| 1563 |
echo "⏰ Uptime tracking fixed"
|
| 1564 |
echo "⌨️ Real-time command typing preview enabled"
|
| 1565 |
echo "📊 Status modal with heartbeat icon added"
|
| 1566 |
-
echo "
|
|
|
|
| 1567 |
echo "=========================================="
|
| 1568 |
|
| 1569 |
# Keep container alive
|
|
|
|
| 49 |
echo "✅ EULA accepted"
|
| 50 |
fi
|
| 51 |
|
| 52 |
+
# ==========================================
|
| 53 |
+
# GIT CONFIGURATION (VERY IMPORTANT!)
|
| 54 |
+
# ==========================================
|
| 55 |
+
echo "🔧 Setting up Git configuration..."
|
| 56 |
+
git config --global user.email "minecraft@huggingface.co"
|
| 57 |
+
git config --global user.name "Minecraft Bot"
|
| 58 |
+
git config --global init.defaultBranch main
|
| 59 |
+
|
| 60 |
+
# Check if this is a git repo, if not initialize
|
| 61 |
+
if [ ! -d .git ]; then
|
| 62 |
+
echo "📦 Initializing git repository..."
|
| 63 |
+
git init
|
| 64 |
+
git remote add origin https://$GITHUB_TOKEN@github.com/Walukapah/mcserver.git
|
| 65 |
+
fi
|
| 66 |
+
|
| 67 |
+
# Ensure remote URL has token
|
| 68 |
+
git remote set-url origin https://$GITHUB_TOKEN@github.com/Walukapah/mcserver.git
|
| 69 |
+
|
| 70 |
+
# Create .gitignore to avoid unnecessary files
|
| 71 |
+
cat > .gitignore << 'GITIGNORE'
|
| 72 |
+
*.log
|
| 73 |
+
logs/
|
| 74 |
+
cache/
|
| 75 |
+
tmp/
|
| 76 |
+
*.tmp
|
| 77 |
+
GITIGNORE
|
| 78 |
+
|
| 79 |
+
echo "✅ Git configured successfully"
|
| 80 |
+
|
| 81 |
# Create logs directory for persistent storage
|
| 82 |
mkdir -p /app/logs
|
| 83 |
|
| 84 |
+
# ==========================================
|
| 85 |
+
# BACKUP SCRIPT (Fixed Path)
|
| 86 |
+
# ==========================================
|
| 87 |
+
cat > /app/mcserver/backup.sh << 'BACKUPSCRIPT'
|
| 88 |
#!/bin/bash
|
| 89 |
+
set -e
|
| 90 |
+
|
| 91 |
+
echo ""
|
| 92 |
+
echo "💾 [BACKUP] Starting backup at $(date '+%Y-%m-%d %H:%M:%S')"
|
| 93 |
+
echo "📂 Current directory: $(pwd)"
|
| 94 |
+
|
| 95 |
+
# Must be in mcserver directory
|
| 96 |
+
cd /app/mcserver 2>/dev/null || {
|
| 97 |
+
echo "❌ ERROR: Cannot find /app/mcserver directory!"
|
| 98 |
+
exit 1
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
# Check if git is initialized
|
| 102 |
+
if [ ! -d .git ]; then
|
| 103 |
+
echo "❌ ERROR: Not a git repository!"
|
| 104 |
+
exit 1
|
| 105 |
+
fi
|
| 106 |
+
|
| 107 |
+
# Check for changes
|
| 108 |
+
if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
|
| 109 |
+
echo "ℹ️ No changes to backup"
|
| 110 |
+
exit 0
|
| 111 |
+
fi
|
| 112 |
+
|
| 113 |
+
echo "📤 Pushing changes to GitHub..."
|
| 114 |
+
|
| 115 |
+
# Add all files including new ones
|
| 116 |
git add -A
|
| 117 |
+
|
| 118 |
+
# Commit with timestamp
|
| 119 |
+
git commit -m "🎮 Auto backup: $(date '+%Y-%m-%d %H:%M:%S') | Server Data Backup" || {
|
| 120 |
+
echo "ℹ️ Nothing new to commit"
|
| 121 |
+
exit 0
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
# Push to GitHub
|
| 125 |
+
git push origin main 2>&1 || git push origin master 2>&1 || {
|
| 126 |
+
echo "❌ ERROR: Failed to push to GitHub!"
|
| 127 |
+
echo "🔍 Checking remote URL..."
|
| 128 |
+
git remote -v
|
| 129 |
+
exit 1
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
echo "✅ [BACKUP] Successfully pushed to GitHub!"
|
| 133 |
+
echo ""
|
| 134 |
BACKUPSCRIPT
|
| 135 |
|
| 136 |
+
chmod +x /app/mcserver/backup.sh
|
| 137 |
|
| 138 |
+
# ==========================================
|
| 139 |
+
# BACKUP LOOP (Fixed - runs in correct directory)
|
| 140 |
+
# ==========================================
|
| 141 |
(
|
| 142 |
+
echo "⏰ Backup scheduler started (every 5 minutes)"
|
| 143 |
while true; do
|
| 144 |
sleep 300
|
| 145 |
+
/app/mcserver/backup.sh 2>&1 || true
|
|
|
|
|
|
|
|
|
|
| 146 |
done
|
| 147 |
) &
|
| 148 |
|
|
|
|
| 150 |
echo ""
|
| 151 |
|
| 152 |
# ==========================================
|
| 153 |
+
# WEB TERMINAL SERVER
|
| 154 |
# ==========================================
|
| 155 |
cat > /app/web_terminal.py << 'PYTHON_SCRIPT'
|
| 156 |
import os
|
|
|
|
| 343 |
|
| 344 |
threading.Thread(target=process_commands, daemon=True).start()
|
| 345 |
|
| 346 |
+
# ==========================================
|
| 347 |
+
# BACKUP API ENDPOINT (Manual Trigger)
|
| 348 |
+
# ==========================================
|
| 349 |
+
@app.route('/backup', methods=['POST'])
|
| 350 |
+
def manual_backup():
|
| 351 |
+
"""Trigger manual backup"""
|
| 352 |
+
try:
|
| 353 |
+
result = subprocess.run(
|
| 354 |
+
['/app/mcserver/backup.sh'],
|
| 355 |
+
capture_output=True,
|
| 356 |
+
text=True,
|
| 357 |
+
timeout=60,
|
| 358 |
+
cwd='/app/mcserver'
|
| 359 |
+
)
|
| 360 |
+
return {
|
| 361 |
+
'success': result.returncode == 0,
|
| 362 |
+
'output': result.stdout,
|
| 363 |
+
'error': result.stderr if result.stderr else None
|
| 364 |
+
}
|
| 365 |
+
except Exception as e:
|
| 366 |
+
return {'success': False, 'error': str(e)}
|
| 367 |
+
|
| 368 |
+
@app.route('/backup/status')
|
| 369 |
+
def backup_status():
|
| 370 |
+
"""Check backup/git status"""
|
| 371 |
+
try:
|
| 372 |
+
result = subprocess.run(
|
| 373 |
+
['git', 'status', '--short'],
|
| 374 |
+
capture_output=True,
|
| 375 |
+
text=True,
|
| 376 |
+
timeout=10,
|
| 377 |
+
cwd='/app/mcserver'
|
| 378 |
+
)
|
| 379 |
+
return {
|
| 380 |
+
'has_changes': len(result.stdout.strip()) > 0,
|
| 381 |
+
'status': result.stdout,
|
| 382 |
+
'branch': subprocess.run(
|
| 383 |
+
['git', 'branch', '--show-current'],
|
| 384 |
+
capture_output=True,
|
| 385 |
+
text=True,
|
| 386 |
+
cwd='/app/mcserver'
|
| 387 |
+
).stdout.strip()
|
| 388 |
+
}
|
| 389 |
+
except Exception as e:
|
| 390 |
+
return {'error': str(e)}
|
| 391 |
+
|
| 392 |
HTML_TEMPLATE = """
|
| 393 |
<!DOCTYPE html>
|
| 394 |
<html lang="en">
|
|
|
|
| 468 |
|
| 469 |
.nav-btn:hover { color: #fff; background: rgba(255,255,255,0.1); }
|
| 470 |
|
|
|
|
| 471 |
.nav-btn .status-dot {
|
| 472 |
position: absolute;
|
| 473 |
bottom: 6px;
|
|
|
|
| 479 |
border: 2px solid #252a30;
|
| 480 |
}
|
| 481 |
|
| 482 |
+
.nav-btn .status-dot.offline { background: #ef4444; }
|
|
|
|
|
|
|
| 483 |
|
| 484 |
/* Main Layout */
|
| 485 |
.main-container {
|
|
|
|
| 546 |
.btn-secondary:hover { background: #4b5563; }
|
| 547 |
.btn-danger { background: #dc2626; color: white; }
|
| 548 |
.btn-danger:hover { background: #b91c1c; }
|
| 549 |
+
.btn-success { background: #16a34a; color: white; }
|
| 550 |
+
.btn-success:hover { background: #15803d; }
|
| 551 |
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
| 552 |
|
| 553 |
/* Terminal */
|
|
|
|
| 580 |
.terminal-line.success { color: #3fb950; }
|
| 581 |
.terminal-line.system { color: #a855f7; font-weight: 500; }
|
| 582 |
|
|
|
|
| 583 |
.typing-preview {
|
| 584 |
color: #6e7681;
|
| 585 |
font-style: italic;
|
|
|
|
| 777 |
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
|
| 778 |
}
|
| 779 |
|
| 780 |
+
.status-modal.active { display: block; }
|
|
|
|
|
|
|
| 781 |
|
| 782 |
.status-modal-header {
|
| 783 |
display: flex;
|
|
|
|
| 802 |
font-size: 18px;
|
| 803 |
}
|
| 804 |
|
| 805 |
+
.status-modal-close:hover { color: #fff; }
|
|
|
|
|
|
|
| 806 |
|
| 807 |
.status-item {
|
| 808 |
display: flex;
|
|
|
|
| 811 |
border-bottom: 1px solid #2a2f35;
|
| 812 |
}
|
| 813 |
|
| 814 |
+
.status-item:last-child { border-bottom: none; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 815 |
|
| 816 |
+
.status-label-modal { color: #9ca3af; font-size: 13px; }
|
| 817 |
+
.status-value-modal { color: #f0f6fc; font-weight: 500; font-size: 13px; }
|
|
|
|
|
|
|
|
|
|
| 818 |
|
| 819 |
.modal-overlay {
|
| 820 |
display: none;
|
|
|
|
| 827 |
z-index: 250;
|
| 828 |
}
|
| 829 |
|
| 830 |
+
.modal-overlay.active { display: block; }
|
| 831 |
+
|
| 832 |
+
/* Backup Section */
|
| 833 |
+
.backup-section {
|
| 834 |
+
margin-top: 15px;
|
| 835 |
+
padding-top: 15px;
|
| 836 |
+
border-top: 1px solid #363b42;
|
| 837 |
+
}
|
| 838 |
+
|
| 839 |
+
.backup-title {
|
| 840 |
+
font-size: 12px;
|
| 841 |
+
color: #9ca3af;
|
| 842 |
+
text-transform: uppercase;
|
| 843 |
+
letter-spacing: 0.5px;
|
| 844 |
+
margin-bottom: 10px;
|
| 845 |
+
display: flex;
|
| 846 |
+
align-items: center;
|
| 847 |
+
gap: 6px;
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
+
.backup-btn {
|
| 851 |
+
width: 100%;
|
| 852 |
+
padding: 10px;
|
| 853 |
+
background: #1e40af;
|
| 854 |
+
color: white;
|
| 855 |
+
border: none;
|
| 856 |
+
border-radius: 6px;
|
| 857 |
+
font-size: 12px;
|
| 858 |
+
font-weight: 500;
|
| 859 |
+
cursor: pointer;
|
| 860 |
+
display: flex;
|
| 861 |
+
align-items: center;
|
| 862 |
+
justify-content: center;
|
| 863 |
+
gap: 6px;
|
| 864 |
+
transition: all 0.2s;
|
| 865 |
+
margin-bottom: 8px;
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
.backup-btn:hover { background: #1e3a8a; }
|
| 869 |
+
.backup-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
| 870 |
+
|
| 871 |
+
.backup-status {
|
| 872 |
+
font-size: 11px;
|
| 873 |
+
color: #6b7280;
|
| 874 |
+
text-align: center;
|
| 875 |
+
padding: 5px;
|
| 876 |
}
|
| 877 |
|
| 878 |
+
.backup-status.success { color: #22c55e; }
|
| 879 |
+
.backup-status.error { color: #ef4444; }
|
| 880 |
+
|
| 881 |
/* ============================================
|
| 882 |
MOBILE RESPONSIVE BREAKPOINTS
|
| 883 |
============================================ */
|
| 884 |
|
|
|
|
| 885 |
@media screen and (max-width: 768px) {
|
| 886 |
.sidebar {
|
| 887 |
position: fixed;
|
|
|
|
| 894 |
box-shadow: -4px 0 20px rgba(0,0,0,0.5);
|
| 895 |
}
|
| 896 |
|
| 897 |
+
.sidebar.active { right: 0; }
|
| 898 |
+
.connection-bar { right: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 899 |
|
| 900 |
+
.console-header { padding: 10px 12px; }
|
| 901 |
+
.server-title { font-size: 14px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 902 |
|
| 903 |
.btn {
|
| 904 |
padding: 6px 10px;
|
|
|
|
| 906 |
min-height: 32px;
|
| 907 |
}
|
| 908 |
|
| 909 |
+
.btn span { display: none; }
|
|
|
|
|
|
|
| 910 |
|
| 911 |
.terminal {
|
| 912 |
font-size: 11px;
|
| 913 |
padding: 10px;
|
| 914 |
}
|
| 915 |
|
| 916 |
+
.command-area { padding: 8px 10px; }
|
|
|
|
|
|
|
| 917 |
|
| 918 |
.send-btn {
|
| 919 |
padding: 10px 14px;
|
| 920 |
font-size: 12px;
|
| 921 |
}
|
| 922 |
|
| 923 |
+
.send-btn span { display: none; }
|
|
|
|
|
|
|
| 924 |
}
|
| 925 |
|
|
|
|
| 926 |
@media screen and (max-width: 480px) {
|
| 927 |
+
.top-nav { padding: 0 10px; }
|
|
|
|
|
|
|
| 928 |
|
| 929 |
.nav-tab {
|
| 930 |
padding: 0 10px;
|
| 931 |
font-size: 12px;
|
| 932 |
}
|
| 933 |
|
| 934 |
+
.nav-tab i { font-size: 12px; }
|
|
|
|
|
|
|
| 935 |
|
| 936 |
.console-header {
|
| 937 |
flex-direction: column;
|
|
|
|
| 955 |
line-height: 1.4;
|
| 956 |
}
|
| 957 |
|
| 958 |
+
.command-input-wrapper { min-height: 40px; }
|
| 959 |
+
#commandInput { font-size: 16px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 960 |
|
| 961 |
.send-btn {
|
| 962 |
min-height: 40px;
|
|
|
|
| 968 |
right: -100%;
|
| 969 |
}
|
| 970 |
|
| 971 |
+
.stat-card { padding: 10px; }
|
|
|
|
|
|
|
| 972 |
|
| 973 |
.stat-icon {
|
| 974 |
width: 36px;
|
|
|
|
| 976 |
font-size: 16px;
|
| 977 |
}
|
| 978 |
|
| 979 |
+
.stat-value { font-size: 14px; }
|
|
|
|
|
|
|
| 980 |
|
| 981 |
.status-modal {
|
| 982 |
min-width: 90vw;
|
|
|
|
| 984 |
}
|
| 985 |
}
|
| 986 |
|
|
|
|
| 987 |
@media screen and (max-height: 500px) and (orientation: landscape) {
|
| 988 |
+
.console-header { padding: 8px 12px; }
|
| 989 |
+
.command-area { padding: 6px 10px; }
|
| 990 |
+
.sidebar { padding: 10px; }
|
| 991 |
+
.stat-card { padding: 8px; margin-bottom: 6px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 992 |
}
|
| 993 |
|
|
|
|
| 994 |
@media screen and (min-width: 1400px) {
|
| 995 |
+
.sidebar { width: 320px; }
|
| 996 |
+
.terminal { font-size: 13px; }
|
| 997 |
+
.stat-value { font-size: 18px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
}
|
| 999 |
</style>
|
| 1000 |
</head>
|
|
|
|
| 1166 |
<div class="stat-sub">Online players</div>
|
| 1167 |
</div>
|
| 1168 |
</div>
|
| 1169 |
+
|
| 1170 |
+
<!-- Backup Section -->
|
| 1171 |
+
<div class="backup-section">
|
| 1172 |
+
<div class="backup-title">
|
| 1173 |
+
<i class="fas fa-cloud-upload-alt"></i> GitHub Backup
|
| 1174 |
+
</div>
|
| 1175 |
+
<button class="backup-btn" id="backupBtn" onclick="triggerBackup()">
|
| 1176 |
+
<i class="fas fa-save"></i> Backup Now
|
| 1177 |
+
</button>
|
| 1178 |
+
<button class="backup-btn" style="background: #374151;" onclick="checkBackupStatus()">
|
| 1179 |
+
<i class="fas fa-info-circle"></i> Check Status
|
| 1180 |
+
</button>
|
| 1181 |
+
<div class="backup-status" id="backupStatus">Auto-backup every 5 min</div>
|
| 1182 |
+
</div>
|
| 1183 |
</div>
|
| 1184 |
</div>
|
| 1185 |
|
|
|
|
| 1196 |
const statusModal = document.getElementById('statusModal');
|
| 1197 |
const modalOverlay = document.getElementById('modalOverlay');
|
| 1198 |
const navStatusDot = document.getElementById('navStatusDot');
|
| 1199 |
+
const backupBtn = document.getElementById('backupBtn');
|
| 1200 |
+
const backupStatus = document.getElementById('backupStatus');
|
| 1201 |
|
| 1202 |
let ws = null;
|
| 1203 |
let reconnectAttempts = 0;
|
|
|
|
| 1217 |
};
|
| 1218 |
connectionBar.innerHTML = '<i class="fas ' + icons[status] + '"></i><span>' + message + '</span>';
|
| 1219 |
|
|
|
|
| 1220 |
const wsStatus = document.getElementById('modalWsStatus');
|
| 1221 |
if (status === 'connected') {
|
| 1222 |
wsStatus.innerHTML = '<span style="color: #22c55e;">● Connected</span>';
|
|
|
|
| 1259 |
}
|
| 1260 |
|
| 1261 |
function addTerminalLine(text, type = '') {
|
|
|
|
| 1262 |
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1263 |
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1264 |
typingPreviewLine = null;
|
|
|
|
| 1291 |
function updateTypingPreview() {
|
| 1292 |
const text = commandInput.value.trim();
|
| 1293 |
|
|
|
|
| 1294 |
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1295 |
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1296 |
typingPreviewLine = null;
|
| 1297 |
}
|
| 1298 |
|
|
|
|
| 1299 |
if (text && isServerRunning) {
|
| 1300 |
typingPreviewLine = document.createElement('div');
|
| 1301 |
typingPreviewLine.className = 'terminal-line typing-preview';
|
|
|
|
| 1342 |
|
| 1343 |
setInterval(updateUptime, 1000);
|
| 1344 |
|
| 1345 |
+
// ==========================================
|
| 1346 |
+
// BACKUP FUNCTIONS
|
| 1347 |
+
// ==========================================
|
| 1348 |
+
async function triggerBackup() {
|
| 1349 |
+
backupBtn.disabled = true;
|
| 1350 |
+
backupStatus.textContent = 'Backing up...';
|
| 1351 |
+
backupStatus.className = 'backup-status';
|
| 1352 |
+
|
| 1353 |
+
try {
|
| 1354 |
+
const response = await fetch('/backup', { method: 'POST' });
|
| 1355 |
+
const result = await response.json();
|
| 1356 |
+
|
| 1357 |
+
if (result.success) {
|
| 1358 |
+
backupStatus.textContent = '✅ Backup successful!';
|
| 1359 |
+
backupStatus.className = 'backup-status success';
|
| 1360 |
+
addTerminalLine('[SYSTEM] GitHub backup completed successfully!', 'system');
|
| 1361 |
+
} else {
|
| 1362 |
+
backupStatus.textContent = '❌ Backup failed: ' + (result.error || 'Unknown error');
|
| 1363 |
+
backupStatus.className = 'backup-status error';
|
| 1364 |
+
addTerminalLine('[ERROR] Backup failed: ' + (result.error || 'Unknown error'), 'error');
|
| 1365 |
+
}
|
| 1366 |
+
} catch (e) {
|
| 1367 |
+
backupStatus.textContent = '❌ Network error';
|
| 1368 |
+
backupStatus.className = 'backup-status error';
|
| 1369 |
+
}
|
| 1370 |
+
|
| 1371 |
+
setTimeout(() => {
|
| 1372 |
+
backupBtn.disabled = false;
|
| 1373 |
+
}, 5000);
|
| 1374 |
+
}
|
| 1375 |
+
|
| 1376 |
+
async function checkBackupStatus() {
|
| 1377 |
+
backupStatus.textContent = 'Checking...';
|
| 1378 |
+
|
| 1379 |
+
try {
|
| 1380 |
+
const response = await fetch('/backup/status');
|
| 1381 |
+
const result = await response.json();
|
| 1382 |
+
|
| 1383 |
+
if (result.error) {
|
| 1384 |
+
backupStatus.textContent = '❌ Error: ' + result.error;
|
| 1385 |
+
backupStatus.className = 'backup-status error';
|
| 1386 |
+
} else if (result.has_changes) {
|
| 1387 |
+
backupStatus.textContent = '📝 Unsaved changes: ' + result.status.split('\\n').length + ' files';
|
| 1388 |
+
backupStatus.className = 'backup-status';
|
| 1389 |
+
} else {
|
| 1390 |
+
backupStatus.textContent = '✅ All changes synced';
|
| 1391 |
+
backupStatus.className = 'backup-status success';
|
| 1392 |
+
}
|
| 1393 |
+
} catch (e) {
|
| 1394 |
+
backupStatus.textContent = '❌ Network error';
|
| 1395 |
+
backupStatus.className = 'backup-status error';
|
| 1396 |
+
}
|
| 1397 |
+
}
|
| 1398 |
+
|
| 1399 |
function connect() {
|
| 1400 |
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
| 1401 |
ws = new WebSocket(protocol + '//' + window.location.host + '/ws');
|
|
|
|
| 1420 |
const memUsed = ((data.memoryTotal * data.memory / 100) / 1024 / 1024 / 1024).toFixed(2);
|
| 1421 |
document.getElementById('memorySub').textContent = memUsed + ' / ' + memTotal + ' GB';
|
| 1422 |
|
|
|
|
| 1423 |
document.getElementById('modalCpu').textContent = data.cpu.toFixed(1) + '%';
|
| 1424 |
document.getElementById('modalMemory').textContent = data.memory.toFixed(1) + '%';
|
| 1425 |
|
|
|
|
| 1456 |
function sendInput() {
|
| 1457 |
const cmd = commandInput.value.trim();
|
| 1458 |
if (cmd && ws && ws.readyState === WebSocket.OPEN) {
|
|
|
|
| 1459 |
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1460 |
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1461 |
typingPreviewLine = null;
|
|
|
|
| 1471 |
}
|
| 1472 |
}
|
| 1473 |
|
|
|
|
| 1474 |
commandInput.addEventListener('input', updateTypingPreview);
|
| 1475 |
commandInput.addEventListener('keyup', updateTypingPreview);
|
| 1476 |
|
| 1477 |
commandInput.addEventListener('keypress', (e) => {
|
| 1478 |
if (e.key === 'Enter') {
|
|
|
|
| 1479 |
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1480 |
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
| 1481 |
typingPreviewLine = null;
|
|
|
|
| 1484 |
}
|
| 1485 |
});
|
| 1486 |
|
|
|
|
| 1487 |
commandInput.addEventListener('blur', () => {
|
| 1488 |
if (typingPreviewLine && typingPreviewLine.parentNode) {
|
| 1489 |
typingPreviewLine.parentNode.removeChild(typingPreviewLine);
|
|
|
|
| 1518 |
function toggleStatusModal() {
|
| 1519 |
statusModal.classList.toggle('active');
|
| 1520 |
modalOverlay.classList.toggle('active');
|
|
|
|
|
|
|
| 1521 |
document.getElementById('modalAddress').textContent = window.location.hostname;
|
| 1522 |
}
|
| 1523 |
|
|
|
|
| 1524 |
document.addEventListener('click', (e) => {
|
| 1525 |
if (window.innerWidth <= 768 && sidebarOpen) {
|
| 1526 |
if (!sidebar.contains(e.target) && !e.target.closest('.sidebar-toggle')) {
|
|
|
|
| 1529 |
}
|
| 1530 |
});
|
| 1531 |
|
|
|
|
| 1532 |
window.addEventListener('orientationchange', () => {
|
| 1533 |
setTimeout(() => {
|
| 1534 |
terminal.scrollTop = terminal.scrollHeight;
|
| 1535 |
}, 300);
|
| 1536 |
});
|
| 1537 |
|
|
|
|
| 1538 |
let resizeTimer;
|
| 1539 |
window.addEventListener('resize', () => {
|
| 1540 |
clearTimeout(resizeTimer);
|
|
|
|
| 1547 |
}, 250);
|
| 1548 |
});
|
| 1549 |
|
|
|
|
| 1550 |
let lastTouchEnd = 0;
|
| 1551 |
document.addEventListener('touchend', (e) => {
|
| 1552 |
const now = Date.now();
|
|
|
|
| 1579 |
global server_start_time
|
| 1580 |
print("[WS] Client connected", flush=True)
|
| 1581 |
|
|
|
|
| 1582 |
try:
|
| 1583 |
if logs_history:
|
| 1584 |
ws.send(json.dumps({
|
|
|
|
| 1616 |
stats_thread = threading.Thread(target=send_stats, daemon=True)
|
| 1617 |
stats_thread.start()
|
| 1618 |
|
|
|
|
| 1619 |
def send_output():
|
| 1620 |
while True:
|
| 1621 |
try:
|
|
|
|
| 1632 |
output_thread = threading.Thread(target=send_output, daemon=True)
|
| 1633 |
output_thread.start()
|
| 1634 |
|
|
|
|
| 1635 |
while True:
|
| 1636 |
try:
|
| 1637 |
data = ws.receive(timeout=5)
|
|
|
|
| 1702 |
|
| 1703 |
echo ""
|
| 1704 |
echo "=========================================="
|
| 1705 |
+
echo "✅ Minecraft Server with GitHub Backup is LIVE!"
|
| 1706 |
echo "🌐 Access at: https://your-space.hf.space"
|
| 1707 |
echo "📱 Mobile optimized interface enabled"
|
| 1708 |
echo "💾 Logs are now persistent across refreshes"
|
| 1709 |
echo "⏰ Uptime tracking fixed"
|
| 1710 |
echo "⌨️ Real-time command typing preview enabled"
|
| 1711 |
echo "📊 Status modal with heartbeat icon added"
|
| 1712 |
+
echo "☁️ GitHub Auto-backup every 5 minutes"
|
| 1713 |
+
echo "🔧 Manual backup button added to sidebar"
|
| 1714 |
echo "=========================================="
|
| 1715 |
|
| 1716 |
# Keep container alive
|