srinetwork commited on
Commit
bd7effc
·
verified ·
1 Parent(s): 9eda2e3

Update Dockerfile

Browse files
Files changed (1) hide show
  1. 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
- # Create backup script
56
- cat > /app/backup.sh << 'BACKUPSCRIPT'
 
 
57
  #!/bin/bash
58
- cd /app/mcserver 2>/dev/null || exit 0
59
- git config user.email "minecraft@huggingface.co"
60
- git config user.name "Minecraft Bot"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  git add -A
62
- git commit -m "Auto backup: $(date '+%Y-%m-%d %H:%M:%S')" || true
63
- git push origin main 2>/dev/null || git push origin master 2>/dev/null || true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  BACKUPSCRIPT
65
 
66
- chmod +x /app/backup.sh
67
 
68
- # Start backup loop in background
 
 
69
  (
 
70
  while true; do
71
  sleep 300
72
- echo ""
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 (No Blue Button)
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-value-modal {
714
- color: #f0f6fc;
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
- display: block;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- right: 0;
753
- }
754
-
755
- .connection-bar {
756
- right: 0;
757
- }
758
 
759
- .console-header {
760
- padding: 10px 12px;
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
- min-height: 40px;
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
- padding: 8px 12px;
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
- width: 320px;
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 (No Blue Button Version) is LIVE!"
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 "🔵 Blue floating button REMOVED"
 
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