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

Upload 5 files

Browse files
Files changed (4) hide show
  1. Dockerfile +7 -2
  2. static/index.html +2 -2
  3. xmrig_launcher.py +95 -297
  4. xmrig_server.py +313 -0
Dockerfile CHANGED
@@ -20,7 +20,10 @@ COPY requirements.txt .
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
@@ -47,6 +50,8 @@ EXPOSE 8000
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"]
 
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 server application
24
+ COPY xmrig_server.py .
25
+
26
+ # Copy the launcher script
27
  COPY xmrig_launcher.py .
28
 
29
  # Copy static files for the web UI
 
50
  USER root
51
 
52
  # Health check
53
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
54
+ CMD curl -f http://localhost:8000/health || exit 1
55
 
56
  # Start the server with uvicorn
57
+ CMD ["uvicorn", "xmrig_server:app", "--host", "0.0.0.0", "--port", "8000"]
static/index.html CHANGED
@@ -544,7 +544,7 @@
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 })
@@ -574,7 +574,7 @@
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();
 
544
  spinner.classList.add('active');
545
 
546
  try {
547
+ const response = await fetch(API_URL + 'mining/start', {
548
  method: 'POST',
549
  headers: { 'Content-Type': 'application/json' },
550
  body: JSON.stringify({ benchmark, threads })
 
574
  btn.disabled = true;
575
 
576
  try {
577
+ const response = await fetch(API_URL + 'mining/stop', { method: 'POST' });
578
  if (response.ok) {
579
  stopStatusPolling();
580
  await getStatus();
xmrig_launcher.py CHANGED
@@ -1,96 +1,99 @@
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,
@@ -100,243 +103,38 @@ class XMRigLauncher:
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__":
 
1
  #!/usr/bin/env python3
2
  """
3
+ Standalone XMRig Launcher
4
+ Downloads XMRig if needed, then starts benchmarking
5
+ Usage: python xmrig_launcher.py --bench 1M --threads 4
6
  """
7
 
 
8
  import sys
9
+ import os
10
  import subprocess
11
+ import argparse
12
  import platform
13
+ import shutil
14
+ import urllib.request
15
+ import zipfile
16
  from pathlib import Path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  class XMRigLauncher:
19
+ def __init__(self):
20
+ self.base_dir = Path(__file__).parent.absolute()
 
 
 
21
  self.xmrig_dir = self.base_dir / "xmrig"
22
  self.xmrig_extracted = self.xmrig_dir / "xmrig-6.21.0"
23
  self.xmrig_bin = self.xmrig_extracted / ("xmrig.exe" if platform.system() == "Windows" else "xmrig")
24
+
25
+ def download_xmrig(self):
26
+ """Download and extract XMRig"""
27
+ print("[*] Downloading XMRig...")
28
 
29
+ try:
30
+ if platform.system() == "Windows":
31
+ url = "https://github.com/xmrig/xmrig/releases/download/v6.21.0/xmrig-6.21.0-msvc-win64.zip"
32
+ elif platform.system() == "Linux":
33
+ url = "https://github.com/xmrig/xmrig/releases/download/v6.21.0/xmrig-6.21.0-linux-static-x64.tar.gz"
34
+ else:
35
+ print("[!] Unsupported platform")
36
+ return False
37
+
38
+ # Create directory
39
+ self.xmrig_dir.mkdir(parents=True, exist_ok=True)
40
+
41
+ # Download
42
+ if platform.system() == "Windows":
43
+ archive_path = self.xmrig_dir / "xmrig.zip"
44
+ else:
45
+ archive_path = self.xmrig_dir / "xmrig.tar.gz"
46
+
47
+ print(f"[*] Downloading from {url}...")
48
+ urllib.request.urlretrieve(url, archive_path)
49
+ print(f"[+] Downloaded to {archive_path}")
50
+
51
+ # Extract
52
+ if platform.system() == "Windows":
53
+ with zipfile.ZipFile(archive_path, 'r') as zip_ref:
54
+ zip_ref.extractall(self.xmrig_dir)
55
+ else:
56
+ import tarfile
57
+ with tarfile.open(archive_path, 'r:gz') as tar_ref:
58
+ tar_ref.extractall(self.xmrig_dir)
59
+
60
+ # Find and verify binary
61
+ if self.xmrig_bin.exists():
62
+ if platform.system() != "Windows":
63
+ os.chmod(self.xmrig_bin, 0o755)
64
+ print(f"[+] XMRig extracted to {self.xmrig_bin}")
65
+ return True
66
+ else:
67
+ print(f"[!] XMRig binary not found at {self.xmrig_bin}")
68
+ return False
69
+
70
+ except Exception as e:
71
+ print(f"[!] Download failed: {e}")
72
+ return False
73
+
74
  def start_benchmark(self, benchmark="1M", threads=None):
75
+ """Start XMRig benchmark"""
76
  if not self.xmrig_bin.exists():
77
+ print("[!] XMRig binary not found at " + str(self.xmrig_bin))
78
+ print("[*] Attempting to download...")
79
+ if not self.download_xmrig():
80
+ print("[!] Failed to download XMRig")
81
+ return False
82
 
83
  if threads is None:
84
  threads = os.cpu_count() or 4
85
 
 
 
 
86
  # Build command
87
  cmd = [str(self.xmrig_bin), f"--bench={benchmark}", "--threads", str(threads)]
88
 
89
+ print(f"[+] Starting XMRig benchmark: {benchmark} with {threads} threads")
90
+ print(f"[*] Command: {' '.join(cmd)}")
91
+ print("[*] Output:")
92
+ print("-" * 60)
 
 
 
93
 
94
  try:
95
+ # Start process with live output streaming
96
+ process = subprocess.Popen(
97
  cmd,
98
  cwd=str(self.xmrig_extracted),
99
  stdout=subprocess.PIPE,
 
103
  universal_newlines=True
104
  )
105
 
106
+ # Stream output in real-time
107
+ for line in iter(process.stdout.readline, ''):
108
+ if line:
109
+ print(line.rstrip())
110
+
111
+ process.wait()
112
+ return_code = process.returncode
113
+
114
+ print("-" * 60)
115
+ if return_code == 0:
116
+ print("[+] Benchmark completed successfully")
117
+ else:
118
+ print(f"[!] Benchmark exited with code {return_code}")
119
+
120
+ return return_code == 0
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  except Exception as e:
123
+ print(f"[!] Error running benchmark: {e}")
124
+ return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
 
126
 
127
  def main():
128
+ parser = argparse.ArgumentParser(description="XMRig Launcher")
129
+ parser.add_argument("--bench", type=str, default="1M", help="Benchmark type (1M or 10M)")
130
+ parser.add_argument("--threads", type=int, default=None, help="Number of threads")
 
 
 
 
 
131
 
132
+ args = parser.parse_args()
 
 
 
 
133
 
134
+ launcher = XMRigLauncher()
135
+ success = launcher.start_benchmark(args.bench, args.threads)
136
 
137
+ sys.exit(0 if success else 1)
138
 
139
 
140
  if __name__ == "__main__":
xmrig_server.py ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ XMRig FastAPI Server - Web interface for RandomX benchmarking
4
+ Spawns xmrig_launcher.py as subprocess for mining operations
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import subprocess
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
18
+ from fastapi.responses import 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
+ # ==================== Mining Manager Class ====================
42
+
43
+ class MiningManager:
44
+ def __init__(self, base_dir=None):
45
+ if base_dir is None:
46
+ base_dir = Path(__file__).parent.absolute()
47
+
48
+ self.base_dir = Path(base_dir)
49
+ self.launcher_script = self.base_dir / "xmrig_launcher.py"
50
+ self.process = None
51
+ self.benchmark_config = {}
52
+ self.logs = []
53
+ self.log_reader_thread = None
54
+
55
+ def start_mining(self, benchmark="1M", threads=None):
56
+ """Start mining by spawning xmrig_launcher.py"""
57
+ if self.process and self.process.poll() is None:
58
+ raise HTTPException(status_code=400, detail="Mining already running")
59
+
60
+ if threads is None:
61
+ threads = os.cpu_count() or 4
62
+
63
+ # Clear previous logs
64
+ self.logs = []
65
+
66
+ # Build command to run launcher script
67
+ cmd = [
68
+ sys.executable,
69
+ str(self.launcher_script),
70
+ "--bench", benchmark,
71
+ "--threads", str(threads)
72
+ ]
73
+
74
+ # Store config
75
+ self.benchmark_config = {
76
+ "benchmark": benchmark,
77
+ "threads": threads,
78
+ "started_at": datetime.now().isoformat(),
79
+ "status": "running"
80
+ }
81
+
82
+ try:
83
+ # Start process with output capture
84
+ self.process = subprocess.Popen(
85
+ cmd,
86
+ stdout=subprocess.PIPE,
87
+ stderr=subprocess.STDOUT,
88
+ text=True,
89
+ bufsize=1,
90
+ universal_newlines=True
91
+ )
92
+
93
+ # Start log reader thread
94
+ self.log_reader_thread = threading.Thread(target=self._read_logs, daemon=True)
95
+ self.log_reader_thread.start()
96
+
97
+ return True
98
+ except Exception as e:
99
+ raise HTTPException(status_code=500, detail=f"Failed to start mining: {e}")
100
+
101
+ def _read_logs(self):
102
+ """Read logs from process in real-time (daemon thread)"""
103
+ try:
104
+ if self.process and self.process.stdout:
105
+ # This runs in a daemon thread, so blocking readline is fine
106
+ while self.process and self.process.poll() is None:
107
+ try:
108
+ line = self.process.stdout.readline()
109
+ if line:
110
+ log_entry = {
111
+ "timestamp": datetime.now().isoformat(),
112
+ "message": line.rstrip()
113
+ }
114
+ self.logs.append(log_entry)
115
+ print(line.rstrip())
116
+ else:
117
+ # EOF reached
118
+ break
119
+ except Exception as e:
120
+ print(f"[!] Error reading line: {e}")
121
+ break
122
+ except Exception as e:
123
+ print(f"[!] Error in log reader: {e}")
124
+
125
+ def stop_mining(self):
126
+ """Stop running mining"""
127
+ if self.process and self.process.poll() is None:
128
+ self.process.terminate()
129
+ try:
130
+ self.process.wait(timeout=5)
131
+ except subprocess.TimeoutExpired:
132
+ self.process.kill()
133
+ self.benchmark_config["status"] = "stopped"
134
+ return True
135
+ return False
136
+
137
+ def is_running(self):
138
+ """Check if mining is running"""
139
+ return self.process and self.process.poll() is None
140
+
141
+ def get_xmrig_status(self):
142
+ """Check if launcher script is available"""
143
+ return {
144
+ "launcher_available": self.launcher_script.exists(),
145
+ "launcher_path": str(self.launcher_script)
146
+ }
147
+
148
+
149
+ # Global manager instance
150
+ manager = MiningManager()
151
+
152
+
153
+ @asynccontextmanager
154
+ async def lifespan(app: FastAPI):
155
+ # Startup
156
+ print("\n[*] Server starting...")
157
+
158
+ # Check if launcher script exists
159
+ if not manager.launcher_script.exists():
160
+ print(f"[!] Warning: xmrig_launcher.py not found at {manager.launcher_script}")
161
+ else:
162
+ print(f"[+] Launcher script found")
163
+
164
+ print("[+] Server ready - click Start Mining button to begin")
165
+
166
+ yield
167
+
168
+ # Shutdown
169
+ print("\n[*] Server shutting down...")
170
+ manager.stop_mining()
171
+
172
+
173
+ # ==================== FastAPI App ====================
174
+
175
+ app = FastAPI(
176
+ title="XMRig Mining Server",
177
+ description="FastAPI server for RandomX benchmarking with XMRig launcher",
178
+ version="1.0.0",
179
+ lifespan=lifespan
180
+ )
181
+
182
+ # Mount static files (if directory exists)
183
+ static_dir = Path(__file__).parent / "static"
184
+ if static_dir.exists():
185
+ app.mount("/static", StaticFiles(directory="static"), name="static")
186
+
187
+
188
+ @app.get("/api")
189
+ async def api_info():
190
+ """API endpoints info"""
191
+ return {
192
+ "name": "XMRig Mining Server",
193
+ "version": "1.0.0",
194
+ "endpoints": {
195
+ "GET /health": "Health check",
196
+ "GET /status": "Get system and mining status",
197
+ "POST /mining/start": "Start mining with XMRig launcher",
198
+ "POST /mining/stop": "Stop mining",
199
+ "GET /mining/status": "Get mining status",
200
+ "GET /logs": "Get mining logs"
201
+ }
202
+ }
203
+
204
+
205
+ @app.get("/")
206
+ async def root():
207
+ """Root endpoint - serve static HTML UI"""
208
+ try:
209
+ with open("static/index.html", "r", encoding="utf-8") as f:
210
+ return HTMLResponse(content=f.read())
211
+ except FileNotFoundError:
212
+ return HTMLResponse(
213
+ content="<h1>404</h1><p>static/index.html not found</p>",
214
+ status_code=404
215
+ )
216
+
217
+
218
+ @app.get("/health")
219
+ async def health():
220
+ """Health check endpoint"""
221
+ return {
222
+ "status": "healthy",
223
+ "timestamp": datetime.now().isoformat(),
224
+ "admin": is_admin()
225
+ }
226
+
227
+
228
+ @app.get("/status")
229
+ async def status():
230
+ """Get system and mining status"""
231
+ return {
232
+ "system": {
233
+ "platform": platform.system(),
234
+ "admin": is_admin(),
235
+ "cpu_count": os.cpu_count() or 4
236
+ },
237
+ "xmrig": manager.get_xmrig_status(),
238
+ "mining": {
239
+ "running": manager.is_running(),
240
+ "config": manager.benchmark_config if manager.benchmark_config else None
241
+ }
242
+ }
243
+
244
+
245
+ @app.post("/mining/start")
246
+ async def start_mining(request: BenchmarkRequest):
247
+ """Start mining using xmrig_launcher.py"""
248
+ if manager.is_running():
249
+ raise HTTPException(status_code=400, detail="Mining already running")
250
+
251
+ manager.start_mining(request.benchmark, request.threads)
252
+
253
+ return {
254
+ "status": "started",
255
+ "benchmark": request.benchmark,
256
+ "threads": request.threads or os.cpu_count() or 4,
257
+ "admin": is_admin(),
258
+ "admin_warning": "Run with admin for 2-3x better performance" if not is_admin() else None
259
+ }
260
+
261
+
262
+ @app.post("/mining/stop")
263
+ async def stop_mining():
264
+ """Stop running mining"""
265
+ if manager.stop_mining():
266
+ return {"status": "stopped"}
267
+ else:
268
+ raise HTTPException(status_code=400, detail="No mining running")
269
+
270
+
271
+ @app.get("/mining/status")
272
+ async def mining_status():
273
+ """Get mining status"""
274
+ return {
275
+ "running": manager.is_running(),
276
+ "config": manager.benchmark_config if manager.benchmark_config else None,
277
+ "pid": manager.process.pid if manager.process else None
278
+ }
279
+
280
+
281
+ @app.get("/logs")
282
+ async def get_logs():
283
+ """Get mining logs"""
284
+ return {
285
+ "logs": manager.logs[-100:] if manager.logs else [], # Last 100 logs
286
+ "total_logs": len(manager.logs)
287
+ }
288
+
289
+
290
+ # ==================== Main ====================
291
+
292
+ def main():
293
+ """Run the FastAPI server"""
294
+ print("\n" + "="*60)
295
+ print("XMRig Mining Server")
296
+ print("="*60)
297
+ print("[*] Starting server on http://127.0.0.1:8000")
298
+ print("[*] Web UI: http://127.0.0.1:8000/")
299
+ print("[*] API docs: http://127.0.0.1:8000/docs")
300
+
301
+ if is_admin():
302
+ print("[+] Running as Administrator ✓")
303
+ else:
304
+ print("[!] NOT running as Administrator")
305
+ print(" → Run as Admin for 2-3x better performance")
306
+
307
+ print("="*60 + "\n")
308
+
309
+ uvicorn.run(app, host="127.0.0.1", port=8000, log_level="info")
310
+
311
+
312
+ if __name__ == "__main__":
313
+ main()