Add support for GPU mining via GMiner from GitHub (Runs alongside XMRig CPU mining using same pool)

This commit is contained in:
JustinOros
2026-01-13 21:35:37 -07:00
parent a695eeabee
commit 8561f6b0a7
+385 -4
View File
@@ -17,8 +17,10 @@ import tarfile
import zipfile
import re
import threading
import tempfile
import shutil
from pathlib import Path
from typing import Dict, Optional, Tuple
from typing import Dict, Optional, Tuple, List
from datetime import datetime
class MoneroMiner:
@@ -27,8 +29,10 @@ class MoneroMiner:
self.script_dir = os.path.dirname(os.path.abspath(__file__))
self.config = None
self.process: Optional[subprocess.Popen] = None
self.gpu_process: Optional[subprocess.Popen] = None
self.stats_thread = None
self.stop_stats = False
self.has_nvidia_gpu = False
# Known mining pools (with minimum payout amounts)
self.known_pools = [
@@ -64,6 +68,45 @@ class MoneroMiner:
else:
return machine
def detect_nvidia_gpu(self) -> bool:
"""Detect if an NVIDIA GPU is present"""
os_type = self.detect_os()
try:
if os_type == 'windows':
# Try nvidia-smi on Windows
result = subprocess.run(
['nvidia-smi', '--query-gpu=name', '--format=csv,noheader'],
capture_output=True,
text=True,
timeout=10,
creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, 'CREATE_NO_WINDOW') else 0
)
if result.returncode == 0 and result.stdout.strip():
gpu_name = result.stdout.strip().split('\n')[0]
print(f"NVIDIA GPU detected: {gpu_name}")
return True
else:
# Linux/macOS - try nvidia-smi
result = subprocess.run(
['nvidia-smi', '--query-gpu=name', '--format=csv,noheader'],
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0 and result.stdout.strip():
gpu_name = result.stdout.strip().split('\n')[0]
print(f"NVIDIA GPU detected: {gpu_name}")
return True
except FileNotFoundError:
pass
except subprocess.TimeoutExpired:
pass
except Exception as e:
print(f"Error detecting NVIDIA GPU: {e}")
return False
def get_latest_xmrig_release(self) -> Optional[Tuple[str, list]]:
"""Fetch the latest XMRig version and available files from GitHub"""
try:
@@ -83,6 +126,30 @@ class MoneroMiner:
print(f"Error fetching latest release info: {e}")
return None
def get_latest_gminer_release(self) -> Optional[Tuple[str, List[dict]]]:
"""Fetch the latest GMiner version and available files from GitHub"""
try:
url = "https://api.github.com/repos/develsoftware/GMinerRelease/releases/latest"
req = urllib.request.Request(url)
req.add_header('User-Agent', 'Mozilla/5.0')
with urllib.request.urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode())
version = data['tag_name'].lstrip('v')
# Extract assets with download URLs
assets = []
for asset in data.get('assets', []):
assets.append({
'name': asset['name'],
'url': asset['browser_download_url']
})
return version, assets
except Exception as e:
print(f"Error fetching latest GMiner release info: {e}")
return None
def find_matching_file(self, assets: list, os_type: str, arch: str) -> Optional[str]:
"""Find the matching file for the given OS and architecture"""
# Build search patterns based on OS and architecture
@@ -118,6 +185,16 @@ class MoneroMiner:
return None
def find_matching_gminer_file(self, assets: List[dict], os_type: str) -> Optional[dict]:
"""Find the matching GMiner file for the given OS"""
for asset in assets:
name = asset['name'].lower()
if os_type == 'windows' and 'windows' in name and name.endswith('.zip'):
return asset
elif os_type == 'linux' and 'linux' in name and name.endswith('.tar.xz'):
return asset
return None
def check_xmrig_installed(self) -> bool:
"""Check if xmrig is installed"""
# Check in script directory first
@@ -155,6 +232,17 @@ class MoneroMiner:
return False
def check_gminer_installed(self) -> bool:
"""Check if GMiner is installed in script directory"""
os_type = self.detect_os()
if os_type == 'windows':
gminer_path = os.path.join(self.script_dir, 'miner.exe')
else:
gminer_path = os.path.join(self.script_dir, 'miner')
return os.path.exists(gminer_path) and os.access(gminer_path, os.X_OK if os.name != 'nt' else os.F_OK)
def download_file_with_progress(self, url: str, local_filename: str, max_retries: int = 3) -> bool:
"""Download a file with progress indication and retry logic"""
for attempt in range(max_retries):
@@ -385,6 +473,126 @@ class MoneroMiner:
print(f"Error extracting archive: {e}")
return False
def download_and_install_gminer(self) -> bool:
"""Download and install GMiner for GPU mining"""
os_type = self.detect_os()
# GMiner only supports Windows and Linux
if os_type not in ['windows', 'linux']:
print(f"\nGMiner is not available for {os_type}.")
print("GPU mining with GMiner is only supported on Windows and Linux.")
return False
print("\n" + "="*60)
print("GMiner Installation (GPU Mining)")
print("="*60)
# Get latest release info
print("\nFetching latest GMiner release from GitHub...")
release_info = self.get_latest_gminer_release()
if not release_info:
print("Could not fetch release information from GitHub.")
print("Please install GMiner manually from: https://github.com/develsoftware/GMinerRelease/releases")
return False
version, assets = release_info
print(f"Latest version: {version}")
print(f"Found {len(assets)} release files")
# Find matching file for this OS
asset = self.find_matching_gminer_file(assets, os_type)
if not asset:
print(f"\nCould not find a compatible GMiner release for {os_type}")
print("Available files:")
for a in assets:
print(f" - {a['name']}")
print("\nPlease download manually from: https://github.com/develsoftware/GMinerRelease/releases")
return False
filename = asset['name']
download_url = asset['url']
print(f"Found matching file: {filename}")
print(f"\nReady to download: {filename}")
# Prompt user (default to Yes)
while True:
response = input("\nDownload and install GMiner for GPU mining? (Y/N) [Y]: ").strip().upper()
if response == '':
response = 'Y'
if response in ['Y', 'N']:
break
print("Please enter Y or N (or press ENTER for Yes)")
if response == 'N':
print("GMiner installation cancelled. GPU mining will not be available.")
return False
# Download to temp directory
print(f"\nDownloading {filename}...")
temp_dir = tempfile.mkdtemp()
local_filename = os.path.join(temp_dir, filename)
download_success = self.download_file_with_progress(download_url, local_filename)
if not download_success:
print(f"\nDownload failed.")
print("Please download GMiner manually from: https://github.com/develsoftware/GMinerRelease/releases")
shutil.rmtree(temp_dir, ignore_errors=True)
return False
# Extract just the miner executable
print("Extracting GMiner...")
try:
if os_type == 'windows':
# Windows ZIP file
with zipfile.ZipFile(local_filename, 'r') as zip_ref:
# Find miner.exe in the archive
for member in zip_ref.namelist():
if member.endswith('miner.exe'):
# Extract to temp, then copy just the exe
zip_ref.extract(member, temp_dir)
src_path = os.path.join(temp_dir, member)
dst_path = os.path.join(self.script_dir, 'miner.exe')
shutil.copy2(src_path, dst_path)
print(f" ✓ miner.exe")
break
gminer_path = os.path.join(self.script_dir, 'miner.exe')
else:
# Linux tar.xz file
import lzma
with lzma.open(local_filename) as xz:
with tarfile.open(fileobj=xz) as tar:
# Find the miner binary in the archive
for member in tar.getmembers():
if member.name.endswith('/miner') or member.name == 'miner':
# Extract to temp
tar.extract(member, temp_dir)
src_path = os.path.join(temp_dir, member.name)
dst_path = os.path.join(self.script_dir, 'miner')
shutil.copy2(src_path, dst_path)
os.chmod(dst_path, 0o755)
print(f" ✓ miner")
break
gminer_path = os.path.join(self.script_dir, 'miner')
print(f"\nGMiner installed successfully at: {gminer_path}")
# Cleanup temp directory
shutil.rmtree(temp_dir, ignore_errors=True)
return True
except Exception as e:
print(f"Error extracting GMiner: {e}")
shutil.rmtree(temp_dir, ignore_errors=True)
return False
def prompt_wallet_address(self) -> str:
"""Prompt user for wallet address"""
default_wallet = "85NGrygcwq36kjLaNKZevT53H4EeHRQYeje5Y3gq3GYkKsEpwKpGjqd1tGxAVEjDfxEha6SYtyNYPbmPXduYqy47Q6JwbuW"
@@ -555,6 +763,31 @@ class MoneroMiner:
print(" 1. Run: sudo modprobe msr")
print(" 2. Start miner with: sudo python3 miner-monero.py")
def prompt_gpu_mining(self) -> bool:
"""Prompt user to enable GPU mining if NVIDIA GPU is detected"""
print("\n" + "="*60)
print("GPU Mining Configuration")
print("="*60)
print("\nAn NVIDIA GPU was detected on your system.")
print("GPU mining can significantly increase your earnings by mining")
print("GPU-friendly algorithms (like KAWPOW) while your CPU mines RandomX.")
print("Both will be paid out in XMR via MoneroOcean.")
while True:
response = input("\nEnable GPU mining? (Y/N) [Y]: ").strip().upper()
if response == '':
response = 'Y'
if response in ['Y', 'N']:
break
print("Please enter Y or N (or press ENTER for Yes)")
if response == 'Y':
print("GPU mining will be enabled.")
return True
else:
print("GPU mining disabled.")
return False
def setup_initial_config(self):
"""Setup initial configuration with user input"""
os_type = self.detect_os()
@@ -571,6 +804,11 @@ class MoneroMiner:
# Prompt for MSR MOD
enable_msr = self.prompt_msr_mod(os_type)
# Check for NVIDIA GPU and prompt for GPU mining
enable_gpu_mining = False
if self.has_nvidia_gpu:
enable_gpu_mining = self.prompt_gpu_mining()
# Determine xmrig path based on OS
if os_type == 'windows':
miner_executable = "xmrig.exe"
@@ -594,6 +832,9 @@ class MoneroMiner:
"coin": coin,
"nicehash": False,
"msr_mod": enable_msr,
"gpu_mining": enable_gpu_mining,
"gpu_algo": "kawpow",
"gpu_worker_name": "gpu-worker",
"extra_args": [],
"log_file": "monero-miner.log",
"api_port": 0,
@@ -983,6 +1224,12 @@ class MoneroMiner:
def initialize(self):
"""Initialize the miner - check for xmrig and setup config"""
# Check for NVIDIA GPU first
print("\nChecking for NVIDIA GPU...")
self.has_nvidia_gpu = self.detect_nvidia_gpu()
if not self.has_nvidia_gpu:
print("No NVIDIA GPU detected. CPU mining only.")
# Check if xmrig is installed
xmrig_was_just_installed = False
@@ -1007,6 +1254,51 @@ class MoneroMiner:
# If config doesn't exist OR xmrig was just installed, setup new config with wallet prompt
if self.config is None or xmrig_was_just_installed:
self.config = self.setup_initial_config()
# Check if GPU mining is enabled and GMiner needs to be installed
if self.config.get('gpu_mining', False) and self.has_nvidia_gpu:
if not self.check_gminer_installed():
print("\n" + "="*60)
print("GMiner Not Detected")
print("="*60)
print("\nGMiner is required for GPU mining.")
if not self.download_and_install_gminer():
print("\nGPU mining will be disabled.")
self.config['gpu_mining'] = False
else:
print("GMiner detected ✓")
# If NVIDIA GPU detected but not in config, offer to enable GPU mining
if self.has_nvidia_gpu and not self.config.get('gpu_mining', False):
if self.is_moneroocean_pool(): # Only MoneroOcean supports multi-algo
print("\n" + "="*60)
print("GPU Mining Available")
print("="*60)
print("\nAn NVIDIA GPU was detected but GPU mining is not enabled.")
while True:
response = input("\nWould you like to enable GPU mining? (Y/N) [Y]: ").strip().upper()
if response == '':
response = 'Y'
if response in ['Y', 'N']:
break
print("Please enter Y or N")
if response == 'Y':
self.config['gpu_mining'] = True
self.config['gpu_algo'] = 'kawpow'
self.config['gpu_worker_name'] = 'gpu-worker'
# Save updated config
with open(self.config_file, 'w') as f:
json.dump(self.config, f, indent=4)
# Install GMiner if needed
if not self.check_gminer_installed():
if not self.download_and_install_gminer():
print("\nGPU mining will be disabled.")
self.config['gpu_mining'] = False
def build_command(self) -> list:
"""Build the XMRig mining command based on configuration"""
@@ -1094,6 +1386,88 @@ class MoneroMiner:
return cmd
def build_gminer_command(self) -> list:
"""Build the GMiner command for GPU mining"""
os_type = self.detect_os()
if os_type == 'windows':
gminer_path = os.path.join(self.script_dir, 'miner.exe')
else:
gminer_path = os.path.join(self.script_dir, 'miner')
wallet = self.config['wallet_address']
gpu_algo = self.config.get('gpu_algo', 'kawpow')
gpu_worker = self.config.get('gpu_worker_name', 'gpu-worker')
# MoneroOcean GPU mining endpoint
cmd = [
gminer_path,
'--server', 'gulf.moneroocean.stream:10128',
'--user', wallet,
'--pass', f'{gpu_worker}~{gpu_algo}',
'--algo', gpu_algo,
'--proto', 'stratum'
]
return cmd
def start_gpu_mining(self):
"""Start the GPU mining process with GMiner"""
if not self.config.get('gpu_mining', False):
return
if not self.has_nvidia_gpu:
print("GPU mining enabled but no NVIDIA GPU detected. Skipping GPU miner.")
return
if not self.check_gminer_installed():
print("GPU mining enabled but GMiner not installed. Skipping GPU miner.")
return
cmd = self.build_gminer_command()
print("\n" + "="*60)
print("Starting GPU Miner (GMiner)")
print("="*60)
print(f"Algorithm: {self.config.get('gpu_algo', 'kawpow')}")
print(f"Worker: {self.config.get('gpu_worker_name', 'gpu-worker')}")
print(f"Command: {' '.join(cmd)}")
print("="*60 + "\n")
try:
# Start GMiner process
if self.detect_os() == 'windows':
self.gpu_process = subprocess.Popen(
cmd,
creationflags=subprocess.CREATE_NEW_CONSOLE
)
else:
self.gpu_process = subprocess.Popen(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
print("GPU miner started successfully.")
except FileNotFoundError:
print(f"\nError: GMiner executable not found.")
print("GPU mining will not be available.")
except Exception as e:
print(f"Error starting GPU miner: {e}")
def stop_gpu_mining(self):
"""Stop the GPU mining process"""
if self.gpu_process:
print("Stopping GPU miner...")
self.gpu_process.terminate()
try:
self.gpu_process.wait(timeout=10)
except subprocess.TimeoutExpired:
print("Force killing GPU miner process...")
self.gpu_process.kill()
print("GPU miner stopped.")
def start_mining(self):
"""Start the mining process"""
cmd = self.build_command()
@@ -1130,9 +1504,14 @@ class MoneroMiner:
print(f"Wallet: {self.config['wallet_address'][:20]}...{self.config['wallet_address'][-10:]}")
print(f"Mode: {self.config['mining_mode'].upper()}")
print(f"Platform: {platform.system()} ({platform.machine()})")
print(f"CPU Mining: Enabled (XMRig)")
print(f"GPU Mining: {'Enabled (GMiner)' if self.config.get('gpu_mining', False) and self.has_nvidia_gpu else 'Disabled'}")
print(f"Command: {' '.join(cmd)}")
print("="*60 + "\n")
# Start GPU mining first if enabled
self.start_gpu_mining()
# Start stats monitoring if using SupportXMR or MoneroOcean
self.start_stats_monitor()
@@ -1183,20 +1562,22 @@ class MoneroMiner:
if log_handle:
log_handle.close()
self.stop_stats_monitor()
self.stop_gpu_mining()
def stop_mining(self):
"""Stop the mining process"""
self.stop_stats_monitor()
self.stop_gpu_mining()
if self.process:
print("Stopping miner...")
print("Stopping CPU miner...")
self.process.terminate()
try:
self.process.wait(timeout=10)
except subprocess.TimeoutExpired:
print("Force killing miner process...")
print("Force killing CPU miner process...")
self.process.kill()
print("Miner stopped.")
print("CPU miner stopped.")
def show_stats(self):
"""Display mining statistics if API is enabled"""