lovyone commited on
Commit
ea2a165
·
verified ·
1 Parent(s): 83f55db

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +52 -0
  2. requirements.txt +3 -0
  3. static/index.html +615 -0
  4. xmrig_launcher.py +343 -0
Dockerfile ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use official Python runtime as base image
2
+ FROM python:3.13
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies and build tools
8
+ RUN apt-get update && apt-get install -y \
9
+ build-essential \
10
+ curl \
11
+ wget \
12
+ unzip \
13
+ gcc \
14
+ g++ \
15
+ && rm -rf /var/lib/apt/lists/*
16
+
17
+ # Copy requirements.txt
18
+ COPY requirements.txt .
19
+
20
+ # Install Python dependencies using pre-built wheels only
21
+ RUN pip install --no-cache-dir --only-binary :all: -r requirements.txt
22
+
23
+ # Copy the launcher/server application
24
+ COPY xmrig_launcher.py .
25
+
26
+ # Copy static files for the web UI
27
+ COPY static ./static
28
+
29
+ # Create xmrig directory with write permissions
30
+ RUN mkdir -p /app/xmrig
31
+
32
+ # Download and extract XMRig (Linux version)
33
+ # RUN cd /app/xmrig && \
34
+ # wget -q https://github.com/xmrig/xmrig/releases/download/v6.21.0/xmrig-6.21.0-linux-static-x64.tar.gz -O xmrig.tar.gz && \
35
+ # tar -xzf xmrig.tar.gz && \
36
+ # rm xmrig.tar.gz && \
37
+ # chmod +x xmrig-6.21.0/xmrig && \
38
+ # ls -la xmrig-6.21.0/ | head -20
39
+
40
+ # Set permissions for all app files
41
+ RUN chmod -R 777 /app
42
+
43
+ # Expose port 8000 for the web server
44
+ EXPOSE 8000
45
+
46
+ # Run as root (default in Docker) for admin privileges
47
+ USER root
48
+
49
+ # Health check
50
+
51
+ # Start the server with uvicorn
52
+ CMD ["python", "-m", "uvicorn", "xmrig_launcher:app", "--host", "0.0.0.0", "--port", "8000"]
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ fastapi>=0.100.0
2
+ uvicorn[standard]>=0.24.0
3
+ pydantic>=2.0.0
static/index.html ADDED
@@ -0,0 +1,615 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>XMRig Mining Server</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
19
+ padding: 20px;
20
+ }
21
+
22
+ .container-main {
23
+ max-width: 1000px;
24
+ margin: 0 auto;
25
+ }
26
+
27
+ .header {
28
+ text-align: center;
29
+ color: white;
30
+ margin-bottom: 40px;
31
+ animation: slideDown 0.6s ease-out;
32
+ }
33
+
34
+ @keyframes slideDown {
35
+ from {
36
+ opacity: 0;
37
+ transform: translateY(-30px);
38
+ }
39
+ to {
40
+ opacity: 1;
41
+ transform: translateY(0);
42
+ }
43
+ }
44
+
45
+ .header h1 {
46
+ font-size: 3em;
47
+ font-weight: 700;
48
+ margin-bottom: 10px;
49
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
50
+ }
51
+
52
+ .header p {
53
+ font-size: 1.2em;
54
+ opacity: 0.95;
55
+ }
56
+
57
+ .card-custom {
58
+ background: rgba(255, 255, 255, 0.95);
59
+ border: none;
60
+ border-radius: 15px;
61
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
62
+ backdrop-filter: blur(10px);
63
+ margin-bottom: 25px;
64
+ animation: fadeIn 0.6s ease-out;
65
+ }
66
+
67
+ @keyframes fadeIn {
68
+ from {
69
+ opacity: 0;
70
+ transform: translateY(20px);
71
+ }
72
+ to {
73
+ opacity: 1;
74
+ transform: translateY(0);
75
+ }
76
+ }
77
+
78
+ .card-header-custom {
79
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
80
+ color: white;
81
+ font-weight: 600;
82
+ font-size: 1.2em;
83
+ padding: 20px;
84
+ border-radius: 15px 15px 0 0;
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 10px;
88
+ }
89
+
90
+ .card-body-custom {
91
+ padding: 25px;
92
+ }
93
+
94
+ .status-badge {
95
+ display: inline-block;
96
+ padding: 10px 20px;
97
+ border-radius: 25px;
98
+ font-weight: 600;
99
+ font-size: 0.95em;
100
+ }
101
+
102
+ .status-running {
103
+ background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
104
+ color: white;
105
+ animation: pulse 1.5s infinite;
106
+ }
107
+
108
+ @keyframes pulse {
109
+ 0%, 100% { opacity: 1; }
110
+ 50% { opacity: 0.7; }
111
+ }
112
+
113
+ .status-stopped {
114
+ background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
115
+ color: white;
116
+ }
117
+
118
+ .status-admin {
119
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
120
+ color: white;
121
+ }
122
+
123
+ .info-row {
124
+ display: grid;
125
+ grid-template-columns: 1fr 1fr;
126
+ gap: 20px;
127
+ margin-bottom: 15px;
128
+ }
129
+
130
+ .info-item {
131
+ padding: 15px;
132
+ background: #f8f9fa;
133
+ border-radius: 10px;
134
+ border-left: 4px solid #667eea;
135
+ }
136
+
137
+ .info-item label {
138
+ font-weight: 600;
139
+ color: #333;
140
+ display: block;
141
+ margin-bottom: 5px;
142
+ }
143
+
144
+ .info-item value {
145
+ font-size: 1.1em;
146
+ color: #667eea;
147
+ font-weight: 600;
148
+ }
149
+
150
+ .form-group {
151
+ margin-bottom: 20px;
152
+ }
153
+
154
+ .form-group label {
155
+ font-weight: 600;
156
+ color: #333;
157
+ margin-bottom: 8px;
158
+ display: block;
159
+ }
160
+
161
+ .form-control-custom {
162
+ border: 2px solid #e0e0e0;
163
+ border-radius: 10px;
164
+ padding: 12px 15px;
165
+ font-size: 1em;
166
+ transition: all 0.3s ease;
167
+ }
168
+
169
+ .form-control-custom:focus {
170
+ border-color: #667eea;
171
+ box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.1);
172
+ outline: none;
173
+ }
174
+
175
+ .btn-custom {
176
+ font-weight: 600;
177
+ padding: 12px 25px;
178
+ border-radius: 10px;
179
+ border: none;
180
+ transition: all 0.3s ease;
181
+ font-size: 1em;
182
+ }
183
+
184
+ .btn-start {
185
+ background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
186
+ color: white;
187
+ }
188
+
189
+ .btn-start:hover {
190
+ transform: translateY(-2px);
191
+ box-shadow: 0 10px 25px rgba(17, 153, 142, 0.3);
192
+ color: white;
193
+ }
194
+
195
+ .btn-start:disabled {
196
+ opacity: 0.6;
197
+ cursor: not-allowed;
198
+ }
199
+
200
+ .btn-stop {
201
+ background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
202
+ color: white;
203
+ }
204
+
205
+ .btn-stop:hover {
206
+ transform: translateY(-2px);
207
+ box-shadow: 0 10px 25px rgba(235, 51, 73, 0.3);
208
+ color: white;
209
+ }
210
+
211
+ .btn-stop:disabled {
212
+ opacity: 0.5;
213
+ cursor: not-allowed;
214
+ }
215
+
216
+ .btn-group-custom {
217
+ display: grid;
218
+ grid-template-columns: 1fr 1fr;
219
+ gap: 15px;
220
+ margin-top: 20px;
221
+ }
222
+
223
+ .logs-container {
224
+ background: #1e1e1e;
225
+ color: #0fc700;
226
+ padding: 20px;
227
+ border-radius: 10px;
228
+ font-family: 'Courier New', monospace;
229
+ font-size: 0.9em;
230
+ line-height: 1.6;
231
+ max-height: 500px;
232
+ overflow-y: auto;
233
+ border: 2px solid #333;
234
+ }
235
+
236
+ .logs-container::-webkit-scrollbar {
237
+ width: 8px;
238
+ }
239
+
240
+ .logs-container::-webkit-scrollbar-track {
241
+ background: #2d2d2d;
242
+ border-radius: 5px;
243
+ }
244
+
245
+ .logs-container::-webkit-scrollbar-thumb {
246
+ background: #667eea;
247
+ border-radius: 5px;
248
+ }
249
+
250
+ .log-line {
251
+ margin: 2px 0;
252
+ word-break: break-word;
253
+ }
254
+
255
+ .log-error {
256
+ color: #ff6b6b;
257
+ }
258
+
259
+ .log-success {
260
+ color: #51cf66;
261
+ }
262
+
263
+ .log-warning {
264
+ color: #ffd700;
265
+ }
266
+
267
+ .log-info {
268
+ color: #74c0fc;
269
+ }
270
+
271
+ .spinner {
272
+ display: none;
273
+ width: 18px;
274
+ height: 18px;
275
+ border: 3px solid rgba(255, 255, 255, 0.3);
276
+ border-top-color: white;
277
+ border-radius: 50%;
278
+ animation: spin 0.8s linear infinite;
279
+ margin-right: 8px;
280
+ }
281
+
282
+ @keyframes spin {
283
+ to { transform: rotate(360deg); }
284
+ }
285
+
286
+ .spinner.active {
287
+ display: inline-block;
288
+ }
289
+
290
+ .stats-grid {
291
+ display: grid;
292
+ grid-template-columns: 1fr 1fr;
293
+ gap: 15px;
294
+ }
295
+
296
+ .stat-box {
297
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
298
+ color: white;
299
+ padding: 20px;
300
+ border-radius: 10px;
301
+ text-align: center;
302
+ }
303
+
304
+ .stat-label {
305
+ font-size: 0.9em;
306
+ opacity: 0.9;
307
+ margin-bottom: 5px;
308
+ }
309
+
310
+ .stat-value {
311
+ font-size: 1.8em;
312
+ font-weight: 700;
313
+ }
314
+
315
+ .warning-box {
316
+ background: #fff3cd;
317
+ border: 2px solid #ffc107;
318
+ border-radius: 10px;
319
+ padding: 15px;
320
+ margin-bottom: 20px;
321
+ display: none;
322
+ }
323
+
324
+ .warning-box.show {
325
+ display: block;
326
+ }
327
+
328
+ .warning-box strong {
329
+ color: #856404;
330
+ }
331
+
332
+ @media (max-width: 768px) {
333
+ .info-row, .stats-grid {
334
+ grid-template-columns: 1fr;
335
+ }
336
+
337
+ .btn-group-custom {
338
+ grid-template-columns: 1fr;
339
+ }
340
+
341
+ .header h1 {
342
+ font-size: 2em;
343
+ }
344
+ }
345
+ </style>
346
+ </head>
347
+ <body>
348
+ <div class="container-main">
349
+ <!-- Header -->
350
+ <div class="header">
351
+ <h1>⛏️ XMRig Mining</h1>
352
+ <p>RandomX Benchmarking Server</p>
353
+ </div>
354
+
355
+ <!-- System Status -->
356
+ <div class="card-custom">
357
+ <div class="card-header-custom">
358
+ 📊 System Status
359
+ </div>
360
+ <div class="card-body-custom">
361
+ <div class="stats-grid">
362
+ <div class="stat-box">
363
+ <div class="stat-label">Platform</div>
364
+ <div class="stat-value" id="platform">Loading...</div>
365
+ </div>
366
+ <div class="stat-box">
367
+ <div class="stat-label">CPU Cores</div>
368
+ <div class="stat-value" id="cpu-count">Loading...</div>
369
+ </div>
370
+ </div>
371
+ <div style="margin-top: 15px;">
372
+ <div id="admin-status" style="text-align: center;"></div>
373
+ <div style="text-align: center; margin-top: 10px; color: #666;">
374
+ <strong>XMRig:</strong> <span id="binary-status">Loading...</span>
375
+ </div>
376
+ </div>
377
+ </div>
378
+ </div>
379
+
380
+ <!-- Mining Control -->
381
+ <div class="card-custom">
382
+ <div class="card-header-custom">
383
+ 🚀 Mining Control
384
+ </div>
385
+ <div class="card-body-custom">
386
+ <div style="text-align: center; margin-bottom: 20px;">
387
+ <span id="status-badge" class="status-badge status-stopped">⏹️ Stopped</span>
388
+ </div>
389
+
390
+ <div class="warning-box" id="admin-warning">
391
+ <strong>⚠️ Warning:</strong> Not running as Administrator. Performance will be limited. Run as Admin for 2-3x better hash rates.
392
+ </div>
393
+
394
+ <!-- Benchmark Type -->
395
+ <div class="form-group">
396
+ <label>Benchmark Type</label>
397
+ <select id="benchmark-type" class="form-control-custom w-100">
398
+ <option value="1M">1M Hash (Fast ~1 min)</option>
399
+ <option value="10M" selected>10M Hash (Accurate ~10 min)</option>
400
+ </select>
401
+ <small style="color: #999; display: block; margin-top: 5px;">Choose benchmark size</small>
402
+ </div>
403
+
404
+ <!-- Threads -->
405
+ <div class="form-group">
406
+ <label>Threads</label>
407
+ <input type="number" id="threads" class="form-control-custom w-100" value="8" min="1" max="64">
408
+ <small style="color: #999; display: block; margin-top: 5px;">Number of threads (default: 8)</small>
409
+ </div>
410
+
411
+ <!-- Control Buttons -->
412
+ <div class="btn-group-custom">
413
+ <button class="btn btn-custom btn-start" id="start-btn" onclick="startMining()">
414
+ <span class="spinner" id="start-spinner"></span>
415
+ ▶️ Start Mining
416
+ </button>
417
+ <button class="btn btn-custom btn-stop" id="stop-btn" onclick="stopMining()" disabled>
418
+ ⏹️ Stop Mining
419
+ </button>
420
+ </div>
421
+ </div>
422
+ </div>
423
+
424
+ <!-- Mining Logs -->
425
+ <div class="card-custom">
426
+ <div class="card-header-custom">
427
+ 📋 Live Mining Logs
428
+ </div>
429
+ <div class="card-body-custom">
430
+ <div class="logs-container" id="logs-container">
431
+ <div class="log-line log-info">[*] Waiting for mining to start...</div>
432
+ </div>
433
+ </div>
434
+ </div>
435
+ </div>
436
+
437
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
438
+ <script>
439
+ const API_URL = '/';
440
+ let statusCheckInterval = null;
441
+ let lastLogCount = 0;
442
+
443
+ async function getStatus() {
444
+ try {
445
+ const response = await fetch(API_URL + 'status');
446
+ const data = await response.json();
447
+ updateUI(data);
448
+ } catch (error) {
449
+ console.error('Error fetching status:', error);
450
+ }
451
+ }
452
+
453
+ async function getLogs() {
454
+ try {
455
+ const response = await fetch(API_URL + 'logs');
456
+ const data = await response.json();
457
+ updateLogs(data.logs);
458
+ } catch (error) {
459
+ console.error('Error fetching logs:', error);
460
+ }
461
+ }
462
+
463
+ function updateLogs(logs) {
464
+ const logsContainer = document.getElementById('logs-container');
465
+
466
+ if (logs.length === 0) {
467
+ logsContainer.innerHTML = '<div class="log-line log-info">[*] Waiting for logs...</div>';
468
+ return;
469
+ }
470
+
471
+ if (logs.length > lastLogCount) {
472
+ const newLogs = logs.slice(lastLogCount);
473
+ newLogs.forEach(log => {
474
+ const logLine = document.createElement('div');
475
+ logLine.className = 'log-line';
476
+
477
+ if (log.message.includes('[!]') || log.message.includes('FAILED')) {
478
+ logLine.classList.add('log-error');
479
+ } else if (log.message.includes('[+]')) {
480
+ logLine.classList.add('log-success');
481
+ } else if (log.message.includes('[*]')) {
482
+ logLine.classList.add('log-info');
483
+ } else if (log.message.includes('H/s') || log.message.includes('speed')) {
484
+ logLine.classList.add('log-success');
485
+ } else {
486
+ logLine.classList.add('log-info');
487
+ }
488
+
489
+ logLine.textContent = log.message;
490
+ logsContainer.appendChild(logLine);
491
+ });
492
+
493
+ logsContainer.scrollTop = logsContainer.scrollHeight;
494
+ }
495
+
496
+ lastLogCount = logs.length;
497
+ }
498
+
499
+ function updateUI(data) {
500
+ // System Info
501
+ document.getElementById('platform').textContent = data.system.platform;
502
+ document.getElementById('cpu-count').textContent = data.system.cpu_count;
503
+
504
+ // Admin Status
505
+ const adminStatus = document.getElementById('admin-status');
506
+ if (data.system.admin) {
507
+ adminStatus.innerHTML = '<span class="status-badge status-admin">✓ Administrator</span>';
508
+ document.getElementById('admin-warning').classList.remove('show');
509
+ } else {
510
+ adminStatus.innerHTML = '<span class="status-badge" style="background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); color: white;">✗ Standard User</span>';
511
+ document.getElementById('admin-warning').classList.add('show');
512
+ }
513
+
514
+ // Binary Status
515
+ if (data.xmrig.binary_exists) {
516
+ document.getElementById('binary-status').textContent = '✓ Ready';
517
+ document.getElementById('start-btn').disabled = false;
518
+ } else {
519
+ document.getElementById('binary-status').textContent = '✗ Missing';
520
+ document.getElementById('start-btn').disabled = true;
521
+ }
522
+
523
+ // Mining Status
524
+ if (data.benchmark.running) {
525
+ document.getElementById('status-badge').textContent = '▶️ Running';
526
+ document.getElementById('status-badge').className = 'status-badge status-running';
527
+ document.getElementById('start-btn').disabled = true;
528
+ document.getElementById('stop-btn').disabled = false;
529
+ } else {
530
+ document.getElementById('status-badge').textContent = '⏹️ Stopped';
531
+ document.getElementById('status-badge').className = 'status-badge status-stopped';
532
+ document.getElementById('start-btn').disabled = false;
533
+ document.getElementById('stop-btn').disabled = true;
534
+ }
535
+ }
536
+
537
+ async function startMining() {
538
+ const benchmark = document.getElementById('benchmark-type').value;
539
+ const threads = parseInt(document.getElementById('threads').value) || 8;
540
+ const btn = document.getElementById('start-btn');
541
+ const spinner = document.getElementById('start-spinner');
542
+
543
+ btn.disabled = true;
544
+ spinner.classList.add('active');
545
+
546
+ try {
547
+ const response = await fetch(API_URL + 'benchmark/start', {
548
+ method: 'POST',
549
+ headers: { 'Content-Type': 'application/json' },
550
+ body: JSON.stringify({ benchmark, threads })
551
+ });
552
+
553
+ if (response.ok) {
554
+ const data = await response.json();
555
+ console.log('Mining started:', data);
556
+ lastLogCount = 0;
557
+ startStatusPolling();
558
+ await getStatus();
559
+ } else {
560
+ const error = await response.json();
561
+ alert('Error: ' + error.detail);
562
+ btn.disabled = false;
563
+ spinner.classList.remove('active');
564
+ }
565
+ } catch (error) {
566
+ alert('Error: ' + error.message);
567
+ btn.disabled = false;
568
+ spinner.classList.remove('active');
569
+ }
570
+ }
571
+
572
+ async function stopMining() {
573
+ const btn = document.getElementById('stop-btn');
574
+ btn.disabled = true;
575
+
576
+ try {
577
+ const response = await fetch(API_URL + 'benchmark/stop', { method: 'POST' });
578
+ if (response.ok) {
579
+ stopStatusPolling();
580
+ await getStatus();
581
+ } else {
582
+ alert('Failed to stop mining');
583
+ }
584
+ } catch (error) {
585
+ alert('Error: ' + error.message);
586
+ }
587
+ }
588
+
589
+ function startStatusPolling() {
590
+ if (statusCheckInterval) return;
591
+ statusCheckInterval = setInterval(() => {
592
+ getStatus();
593
+ getLogs();
594
+ }, 1000);
595
+ }
596
+
597
+ function stopStatusPolling() {
598
+ if (statusCheckInterval) {
599
+ clearInterval(statusCheckInterval);
600
+ statusCheckInterval = null;
601
+ }
602
+ }
603
+
604
+ // Initial load
605
+ getStatus();
606
+ getLogs();
607
+
608
+ // Refresh every 2 seconds
609
+ setInterval(() => {
610
+ getStatus();
611
+ getLogs();
612
+ }, 2000);
613
+ </script>
614
+ </body>
615
+ </html>
xmrig_launcher.py ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ XMRig FastAPI Server - Web interface for RandomX benchmarking
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import subprocess
9
+ import json
10
+ import platform
11
+ import ctypes
12
+ from pathlib import Path
13
+ from typing import Optional
14
+ from datetime import datetime
15
+ from contextlib import asynccontextmanager
16
+
17
+ from fastapi import FastAPI, HTTPException, BackgroundTasks
18
+ from fastapi.responses import JSONResponse, HTMLResponse
19
+ from fastapi.staticfiles import StaticFiles
20
+ from pydantic import BaseModel
21
+ import uvicorn
22
+ import threading
23
+
24
+ # ==================== Admin Detection ====================
25
+
26
+ def is_admin():
27
+ """Check if running with admin privileges"""
28
+ try:
29
+ return ctypes.windll.shell.IsUserAnAdmin()
30
+ except:
31
+ return False
32
+
33
+
34
+ # ==================== Pydantic Models ====================
35
+
36
+ class BenchmarkRequest(BaseModel):
37
+ benchmark: str = "1M"
38
+ threads: Optional[int] = None
39
+
40
+
41
+ class BenchmarkResponse(BaseModel):
42
+ status: str
43
+ benchmark: str
44
+ threads: int
45
+ running: bool
46
+ started_at: Optional[str] = None
47
+
48
+
49
+ # ==================== XMRig Launcher Class ====================
50
+
51
+ class XMRigLauncher:
52
+ def __init__(self, base_dir=None):
53
+ if base_dir is None:
54
+ base_dir = Path(__file__).parent.absolute()
55
+
56
+ self.base_dir = Path(base_dir)
57
+ self.xmrig_dir = self.base_dir / "xmrig"
58
+ self.xmrig_extracted = self.xmrig_dir / "xmrig-6.21.0"
59
+ self.xmrig_bin = self.xmrig_extracted / ("xmrig.exe" if platform.system() == "Windows" else "xmrig")
60
+ self.process = None
61
+ self.benchmark_config = {}
62
+ self.logs = []
63
+ self.log_reader_thread = None
64
+
65
+
66
+ def start_benchmark(self, benchmark="1M", threads=None):
67
+ """Start XMRig benchmark process"""
68
+ if not self.xmrig_bin.exists():
69
+ raise HTTPException(status_code=400, detail="XMRig binary not found. Please install it manually.")
70
+
71
+ if self.process and self.process.poll() is None:
72
+ raise HTTPException(status_code=400, detail="Benchmark already running")
73
+
74
+ if threads is None:
75
+ threads = os.cpu_count() or 4
76
+
77
+ # Clear previous logs
78
+ self.logs = []
79
+
80
+ # Build command
81
+ cmd = [str(self.xmrig_bin), f"--bench={benchmark}", "--threads", str(threads)]
82
+
83
+ # Store config
84
+ self.benchmark_config = {
85
+ "benchmark": benchmark,
86
+ "threads": threads,
87
+ "started_at": datetime.now().isoformat(),
88
+ "status": "running"
89
+ }
90
+
91
+ try:
92
+ # Start process with output capture
93
+ self.process = subprocess.Popen(
94
+ cmd,
95
+ cwd=str(self.xmrig_extracted),
96
+ stdout=subprocess.PIPE,
97
+ stderr=subprocess.STDOUT,
98
+ text=True,
99
+ bufsize=1,
100
+ universal_newlines=True
101
+ )
102
+
103
+ # Start log reader thread
104
+ self.log_reader_thread = threading.Thread(target=self._read_logs, daemon=True)
105
+ self.log_reader_thread.start()
106
+
107
+ return True
108
+ except Exception as e:
109
+ raise HTTPException(status_code=500, detail=f"Failed to start benchmark: {e}")
110
+
111
+ def _read_logs(self):
112
+ """Read logs from process in real-time (daemon thread)"""
113
+ try:
114
+ if self.process and self.process.stdout:
115
+ # This runs in a daemon thread, so blocking readline is fine
116
+ while self.process and self.process.poll() is None:
117
+ try:
118
+ line = self.process.stdout.readline()
119
+ if line:
120
+ log_entry = {
121
+ "timestamp": datetime.now().isoformat(),
122
+ "message": line.rstrip()
123
+ }
124
+ self.logs.append(log_entry)
125
+ print(line.rstrip())
126
+ else:
127
+ # EOF reached
128
+ break
129
+ except Exception as e:
130
+ print(f"[!] Error reading line: {e}")
131
+ break
132
+ except Exception as e:
133
+ print(f"[!] Error in log reader: {e}")
134
+
135
+ def stop_benchmark(self):
136
+ """Stop running benchmark"""
137
+ if self.process and self.process.poll() is None:
138
+ self.process.terminate()
139
+ try:
140
+ self.process.wait(timeout=5)
141
+ except subprocess.TimeoutExpired:
142
+ self.process.kill()
143
+ self.benchmark_config["status"] = "stopped"
144
+ return True
145
+ return False
146
+
147
+ def is_running(self):
148
+ """Check if benchmark is running"""
149
+ return self.process and self.process.poll() is None
150
+
151
+ def get_output(self):
152
+ """Get benchmark output"""
153
+ if not self.process:
154
+ return ""
155
+
156
+ output = ""
157
+ try:
158
+ # Non-blocking read
159
+ if self.process.stdout:
160
+ import select
161
+ import io
162
+ ready = select.select([self.process.stdout], [], [], 0)[0]
163
+ if ready:
164
+ output = self.process.stdout.read()
165
+ except:
166
+ pass
167
+
168
+ return output
169
+
170
+
171
+ from contextlib import asynccontextmanager
172
+
173
+ # Global launcher instance
174
+ launcher = XMRigLauncher()
175
+
176
+ @asynccontextmanager
177
+ async def lifespan(app: FastAPI):
178
+ # Startup
179
+ print("\n[*] Server starting...")
180
+
181
+ # Check if XMRig binary exists
182
+ if not launcher.xmrig_bin.exists():
183
+ print(f"[!] XMRig binary not found at {launcher.xmrig_bin}")
184
+ print("[!] Please ensure XMRig is installed manually")
185
+ else:
186
+ print("[+] XMRig binary found")
187
+
188
+ print("[+] Server ready - click Start Mining button to begin")
189
+
190
+ yield
191
+
192
+ # Shutdown
193
+ print("\n[*] Server shutting down...")
194
+ launcher.stop_benchmark()
195
+
196
+
197
+ # ==================== FastAPI App ====================
198
+
199
+ app = FastAPI(
200
+ title="XMRig Server",
201
+ description="FastAPI server for RandomX benchmarking with XMRig",
202
+ version="1.0.0",
203
+ lifespan=lifespan
204
+ )
205
+
206
+ # Mount static files (if directory exists)
207
+ static_dir = Path(__file__).parent / "static"
208
+ if static_dir.exists():
209
+ app.mount("/static", StaticFiles(directory="static"), name="static")
210
+ else:
211
+ print("[!] Warning: static/ directory not found, UI will not be available")
212
+
213
+
214
+ @app.get("/api")
215
+ async def api_info():
216
+ """API endpoints info"""
217
+ return {
218
+ "name": "XMRig Server",
219
+ "version": "1.0.0",
220
+ "endpoints": {
221
+ "GET /health": "Health check",
222
+ "GET /status": "Get system and benchmark status",
223
+ "POST /benchmark/start": "Start benchmark",
224
+ "POST /benchmark/stop": "Stop benchmark",
225
+ "GET /benchmark/status": "Get benchmark status"
226
+ }
227
+ }
228
+
229
+
230
+ @app.get("/")
231
+ async def root():
232
+ """Root endpoint - serve static HTML UI"""
233
+ try:
234
+ with open("static/index.html", "r", encoding="utf-8") as f:
235
+ return HTMLResponse(content=f.read())
236
+ except FileNotFoundError:
237
+ return HTMLResponse(
238
+ content="<h1>404</h1><p>static/index.html not found</p>",
239
+ status_code=404
240
+ )
241
+
242
+
243
+ @app.get("/health")
244
+ async def health():
245
+ """Health check endpoint"""
246
+ return {
247
+ "status": "healthy",
248
+ "timestamp": datetime.now().isoformat(),
249
+ "admin": is_admin()
250
+ }
251
+
252
+
253
+ @app.get("/status")
254
+ async def status():
255
+ """Get system and benchmark status"""
256
+ return {
257
+ "system": {
258
+ "platform": platform.system(),
259
+ "admin": is_admin(),
260
+ "cpu_count": os.cpu_count() or 4
261
+ },
262
+ "xmrig": {
263
+ "binary_exists": launcher.xmrig_bin.exists(),
264
+ "binary_path": str(launcher.xmrig_bin),
265
+ "base_dir": str(launcher.base_dir)
266
+ },
267
+ "benchmark": {
268
+ "running": launcher.is_running(),
269
+ "config": launcher.benchmark_config if launcher.benchmark_config else None
270
+ }
271
+ }
272
+
273
+
274
+ @app.post("/benchmark/start")
275
+ async def start_benchmark(request: BenchmarkRequest):
276
+ """Start a benchmark"""
277
+ if launcher.is_running():
278
+ raise HTTPException(status_code=400, detail="Benchmark already running")
279
+
280
+ launcher.start_benchmark(request.benchmark, request.threads)
281
+
282
+ return {
283
+ "status": "started",
284
+ "benchmark": request.benchmark,
285
+ "threads": request.threads or os.cpu_count() or 4,
286
+ "admin": is_admin(),
287
+ "admin_warning": "Run with admin for 2-3x better performance" if not is_admin() else None
288
+ }
289
+
290
+
291
+ @app.post("/benchmark/stop")
292
+ async def stop_benchmark():
293
+ """Stop running benchmark"""
294
+ if launcher.stop_benchmark():
295
+ return {"status": "stopped"}
296
+ else:
297
+ raise HTTPException(status_code=400, detail="No benchmark running")
298
+
299
+
300
+ @app.get("/benchmark/status")
301
+ async def benchmark_status():
302
+ """Get benchmark status"""
303
+ return {
304
+ "running": launcher.is_running(),
305
+ "config": launcher.benchmark_config if launcher.benchmark_config else None,
306
+ "pid": launcher.process.pid if launcher.process else None
307
+ }
308
+
309
+
310
+ @app.get("/logs")
311
+ async def get_logs():
312
+ """Get benchmark logs"""
313
+ return {
314
+ "logs": launcher.logs[-100:] if launcher.logs else [], # Last 100 logs
315
+ "total_logs": len(launcher.logs)
316
+ }
317
+
318
+
319
+ # ==================== Main ====================
320
+
321
+ def main():
322
+ """Run the FastAPI server"""
323
+ print("\n" + "="*60)
324
+ print("XMRig FastAPI Server")
325
+ print("="*60)
326
+ print("[*] Starting server on http://127.0.0.1:8000")
327
+ print("[*] Web UI: http://127.0.0.1:8000/ui")
328
+ print("[*] API docs: http://127.0.0.1:8000/docs")
329
+ print("[*] ReDoc: http://127.0.0.1:8000/redoc")
330
+
331
+ if is_admin():
332
+ print("[+] Running as Administrator ✓")
333
+ else:
334
+ print("[!] NOT running as Administrator")
335
+ print(" → Run as Admin for 2-3x better performance")
336
+
337
+ print("="*60 + "\n")
338
+
339
+ uvicorn.run(app, host="127.0.0.1", port=8000, log_level="info")
340
+
341
+
342
+ if __name__ == "__main__":
343
+ main()