按显卡世代和创世编号结算回本,机房批量接入需审核网段并用硬件指纹防克隆。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+160
-6
@@ -72,23 +72,172 @@ def list_orders(session: Session, user_id: int | None = None) -> list[LicenseOrd
|
||||
return list(session.scalars(query.order_by(pending_first, LicenseOrder.id.desc())).all())
|
||||
|
||||
|
||||
def render_client(sn: str, token: str, base_url: str) -> str:
|
||||
config = json.dumps({"sn": sn, "token": token, "base": base_url.rstrip("/")}, ensure_ascii=True)
|
||||
return f"""# HyperNode 挂机客户端。这个文件里有节点 Token,不要发给别人。
|
||||
_CLIENT = """# HyperNode 挂机客户端。这个文件里有节点 Token,不要发给别人。
|
||||
import ctypes
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import uuid
|
||||
|
||||
CONFIG = json.loads({config!r})
|
||||
CONFIG = json.loads(__CONFIG__)
|
||||
|
||||
|
||||
def hardware():
|
||||
gpu_model, vram_gb, gpu_uuid = _gpu()
|
||||
return {
|
||||
"sn": CONFIG["sn"],
|
||||
"token": CONFIG["token"],
|
||||
"gpu_model": gpu_model,
|
||||
"vram_gb": vram_gb,
|
||||
"cpu_count": os.cpu_count(),
|
||||
"memory_gb": _memory_gb(),
|
||||
"hardware_fingerprint": _fingerprint(gpu_uuid),
|
||||
}
|
||||
|
||||
|
||||
def _fingerprint(gpu_uuid):
|
||||
board = _board_serial() or ""
|
||||
mac = f"{uuid.getnode():012x}"
|
||||
material = "|".join([gpu_uuid or "", board, mac])
|
||||
if not material.strip("|"):
|
||||
return None
|
||||
return hashlib.sha256(material.encode()).hexdigest()
|
||||
|
||||
|
||||
def _board_serial():
|
||||
if os.name == "nt":
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
["wmic", "baseboard", "get", "serialnumber"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
timeout=8,
|
||||
text=True,
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return None
|
||||
lines = [line.strip() for line in output.splitlines() if line.strip() and "SerialNumber" not in line]
|
||||
return lines[0] if lines else None
|
||||
for path in ("/sys/class/dmi/id/board_serial", "/sys/class/dmi/id/product_uuid"):
|
||||
try:
|
||||
with open(path, encoding="utf-8") as handle:
|
||||
value = handle.read().strip()
|
||||
except OSError:
|
||||
continue
|
||||
if value and value.lower() not in {"none", "to be filled by o.e.m."}:
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def _gpu():
|
||||
probed = _pynvml()
|
||||
if probed[0]:
|
||||
return probed
|
||||
return _nvidia_smi()
|
||||
|
||||
|
||||
def _pynvml():
|
||||
try:
|
||||
import pynvml
|
||||
except ImportError:
|
||||
return None, None, None
|
||||
try:
|
||||
pynvml.nvmlInit()
|
||||
best_name = None
|
||||
best_bytes = -1
|
||||
best_uuid = None
|
||||
for index in range(pynvml.nvmlDeviceGetCount()):
|
||||
handle = pynvml.nvmlDeviceGetHandleByIndex(index)
|
||||
name = pynvml.nvmlDeviceGetName(handle)
|
||||
if isinstance(name, bytes):
|
||||
name = name.decode()
|
||||
total = int(pynvml.nvmlDeviceGetMemoryInfo(handle).total)
|
||||
if total > best_bytes:
|
||||
uid = pynvml.nvmlDeviceGetUUID(handle)
|
||||
if isinstance(uid, bytes):
|
||||
uid = uid.decode()
|
||||
best_name = str(name)
|
||||
best_bytes = total
|
||||
best_uuid = str(uid)
|
||||
pynvml.nvmlShutdown()
|
||||
except Exception:
|
||||
return None, None, None
|
||||
if not best_name or best_bytes < 0:
|
||||
return None, None, None
|
||||
return best_name, round(best_bytes / (1024 ** 3), 2), best_uuid
|
||||
|
||||
|
||||
def _nvidia_smi():
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
["nvidia-smi", "--query-gpu=name,memory.total,uuid", "--format=csv,noheader,nounits"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
timeout=8,
|
||||
text=True,
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return None, None, None
|
||||
best_name = None
|
||||
best_mib = -1.0
|
||||
best_uuid = None
|
||||
for line in output.splitlines():
|
||||
parts = [part.strip() for part in line.split(",", 2)]
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
name, memory = parts[0], parts[1]
|
||||
gpu_uuid = parts[2] if len(parts) > 2 else None
|
||||
try:
|
||||
mib = float(memory)
|
||||
except ValueError:
|
||||
continue
|
||||
if mib > best_mib:
|
||||
best_name = name or None
|
||||
best_mib = mib
|
||||
best_uuid = gpu_uuid or None
|
||||
if best_name is None:
|
||||
return None, None, None
|
||||
return best_name, round(best_mib / 1024, 2), best_uuid
|
||||
|
||||
|
||||
def _memory_gb():
|
||||
if os.name == "nt":
|
||||
class MemoryStatus(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("dwLength", ctypes.c_ulong),
|
||||
("dwMemoryLoad", ctypes.c_ulong),
|
||||
("ullTotalPhys", ctypes.c_ulonglong),
|
||||
("ullAvailPhys", ctypes.c_ulonglong),
|
||||
("ullTotalPageFile", ctypes.c_ulonglong),
|
||||
("ullAvailPageFile", ctypes.c_ulonglong),
|
||||
("ullTotalVirtual", ctypes.c_ulonglong),
|
||||
("ullAvailVirtual", ctypes.c_ulonglong),
|
||||
("ullAvailExtendedVirtual", ctypes.c_ulonglong),
|
||||
]
|
||||
|
||||
status = MemoryStatus()
|
||||
status.dwLength = ctypes.sizeof(MemoryStatus)
|
||||
if ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(status)):
|
||||
return round(status.ullTotalPhys / (1024 ** 3), 2)
|
||||
return None
|
||||
try:
|
||||
with open("/proc/meminfo", encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
if line.startswith("MemTotal:"):
|
||||
return round(int(line.split()[1]) / 1024 / 1024, 2)
|
||||
except OSError:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def beat():
|
||||
body = json.dumps({{"sn": CONFIG["sn"], "token": CONFIG["token"]}}).encode()
|
||||
body = json.dumps(hardware()).encode()
|
||||
request = urllib.request.Request(
|
||||
CONFIG["base"] + "/api/v1/node/heartbeat",
|
||||
data=body,
|
||||
headers={{"Content-Type": "application/json"}},
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(request, timeout=20) as response:
|
||||
print(response.read().decode())
|
||||
@@ -104,6 +253,11 @@ if __name__ == "__main__":
|
||||
"""
|
||||
|
||||
|
||||
def render_client(sn: str, token: str, base_url: str) -> str:
|
||||
config = json.dumps({"sn": sn, "token": token, "base": base_url.rstrip("/")}, ensure_ascii=True)
|
||||
return _CLIENT.replace("__CONFIG__", repr(config))
|
||||
|
||||
|
||||
def _pending_order(session: Session, order_id: int) -> LicenseOrder:
|
||||
order = session.scalar(
|
||||
select(LicenseOrder)
|
||||
|
||||
Reference in New Issue
Block a user