refactor: move ssh to miner.ssh
This commit is contained in:
@@ -29,6 +29,7 @@ from pyasic.miners.base import (
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.rpc import APIError
|
||||
from pyasic.ssh.antminer import AntminerModernSSH
|
||||
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||
|
||||
ANTMINER_MODERN_DATA_LOC = DataLocations(
|
||||
@@ -85,6 +86,10 @@ class AntminerModern(BMMiner):
|
||||
"""Handler for AntMiners with the modern web interface, such as S19"""
|
||||
|
||||
_web_cls = AntminerModernWebAPI
|
||||
web: AntminerModernWebAPI
|
||||
|
||||
_ssh_cls = AntminerModernSSH
|
||||
ssh: AntminerModernSSH
|
||||
|
||||
data_locations = ANTMINER_MODERN_DATA_LOC
|
||||
|
||||
@@ -391,6 +396,7 @@ class AntminerOld(CGMiner):
|
||||
"""Handler for AntMiners with the old web interface, such as S17"""
|
||||
|
||||
_web_cls = AntminerOldWebAPI
|
||||
web: AntminerOldWebAPI
|
||||
|
||||
data_locations = ANTMINER_OLD_DATA_LOC
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ class BFGMiner(BaseMiner):
|
||||
"""Base handler for BFGMiner based miners."""
|
||||
|
||||
_api_cls = BFGMinerRPCAPI
|
||||
api: BFGMinerRPCAPI
|
||||
|
||||
data_locations = BFGMINER_DATA_LOC
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ class BMMiner(BaseMiner):
|
||||
"""Base handler for BMMiner based miners."""
|
||||
|
||||
_api_cls = BMMinerRPCAPI
|
||||
api: BMMinerRPCAPI
|
||||
|
||||
data_locations = BMMINER_DATA_LOC
|
||||
|
||||
@@ -80,14 +81,6 @@ class BMMiner(BaseMiner):
|
||||
self.config = MinerConfig.from_api(pools)
|
||||
return self.config
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
logging.debug(f"{self}: Sending reboot command.")
|
||||
ret = await self.send_ssh_command("reboot")
|
||||
logging.debug(f"{self}: Reboot command completed.")
|
||||
if ret is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
##################################################
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
@@ -122,9 +115,6 @@ class BMMiner(BaseMiner):
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_hostname(self) -> Optional[str]:
|
||||
hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname")
|
||||
return hn
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
|
||||
@@ -34,6 +34,7 @@ from pyasic.miners.base import (
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.rpc.bosminer import BOSMinerRPCAPI
|
||||
from pyasic.ssh.braiins_os import BOSMinerSSH
|
||||
from pyasic.web.braiins_os import BOSerWebAPI, BOSMinerWebAPI
|
||||
|
||||
BOSMINER_DATA_LOC = DataLocations(
|
||||
@@ -98,7 +99,11 @@ class BOSMiner(BaseMiner):
|
||||
"""Handler for old versions of BraiinsOS+ (pre-gRPC)"""
|
||||
|
||||
_api_cls = BOSMinerRPCAPI
|
||||
api: BOSMinerRPCAPI
|
||||
_web_cls = BOSMinerWebAPI
|
||||
web: BOSMinerWebAPI
|
||||
_ssh_cls = BOSMinerSSH
|
||||
ssh: BOSMinerSSH
|
||||
|
||||
firmware = "BOS+"
|
||||
|
||||
@@ -108,7 +113,7 @@ class BOSMiner(BaseMiner):
|
||||
supports_autotuning = True
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
ret = await self.send_ssh_command("miner fault_light on")
|
||||
ret = await self.ssh.fault_light_on()
|
||||
|
||||
if isinstance(ret, str):
|
||||
self.light = True
|
||||
@@ -116,7 +121,7 @@ class BOSMiner(BaseMiner):
|
||||
return False
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
ret = await self.send_ssh_command("miner fault_light off")
|
||||
ret = await self.ssh.fault_light_off()
|
||||
|
||||
if isinstance(ret, str):
|
||||
self.light = False
|
||||
@@ -127,7 +132,7 @@ class BOSMiner(BaseMiner):
|
||||
return await self.restart_bosminer()
|
||||
|
||||
async def restart_bosminer(self) -> bool:
|
||||
ret = await self.send_ssh_command("/etc/init.d/bosminer restart")
|
||||
ret = await self.ssh.restart_bosminer()
|
||||
|
||||
if isinstance(ret, str):
|
||||
return True
|
||||
@@ -156,14 +161,14 @@ class BOSMiner(BaseMiner):
|
||||
return False
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
ret = await self.send_ssh_command("/sbin/reboot")
|
||||
ret = await self.ssh.reboot()
|
||||
|
||||
if isinstance(ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
raw_data = await self.send_ssh_command("cat /etc/bosminer.toml")
|
||||
raw_data = await self.ssh.get_config_file()
|
||||
|
||||
try:
|
||||
toml_data = toml.loads(raw_data)
|
||||
@@ -189,7 +194,7 @@ class BOSMiner(BaseMiner):
|
||||
}
|
||||
)
|
||||
try:
|
||||
conn = await self._get_ssh_connection()
|
||||
conn = await self.ssh._get_connection()
|
||||
except ConnectionError as e:
|
||||
raise APIError("SSH connection failed when sending config.") from e
|
||||
|
||||
@@ -246,7 +251,7 @@ class BOSMiner(BaseMiner):
|
||||
f"option dns '{dns}'",
|
||||
]
|
||||
)
|
||||
data = await self.send_ssh_command("cat /etc/config/network")
|
||||
data = await self.ssh.get_network_config()
|
||||
|
||||
split_data = data.split("\n\n")
|
||||
for idx, val in enumerate(split_data):
|
||||
@@ -254,7 +259,7 @@ class BOSMiner(BaseMiner):
|
||||
split_data[idx] = cfg_data_lan
|
||||
config = "\n\n".join(split_data)
|
||||
|
||||
conn = await self._get_ssh_connection()
|
||||
conn = await self.ssh._get_connection()
|
||||
|
||||
async with conn:
|
||||
await conn.run("echo '" + config + "' > /etc/config/network")
|
||||
@@ -268,7 +273,7 @@ class BOSMiner(BaseMiner):
|
||||
"option proto 'dhcp'",
|
||||
]
|
||||
)
|
||||
data = await self.send_ssh_command("cat /etc/config/network")
|
||||
data = await self.ssh.get_network_config()
|
||||
|
||||
split_data = data.split("\n\n")
|
||||
for idx, val in enumerate(split_data):
|
||||
@@ -276,7 +281,7 @@ class BOSMiner(BaseMiner):
|
||||
split_data[idx] = cfg_data_lan
|
||||
config = "\n\n".join(split_data)
|
||||
|
||||
conn = await self._get_ssh_connection()
|
||||
conn = await self.ssh._get_connection()
|
||||
|
||||
async with conn:
|
||||
await conn.run("echo '" + config + "' > /etc/config/network")
|
||||
@@ -346,9 +351,7 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
async def _get_hostname(self) -> Union[str, None]:
|
||||
try:
|
||||
hostname = (
|
||||
await self.send_ssh_command("cat /proc/sys/kernel/hostname")
|
||||
).strip()
|
||||
hostname = (await self.ssh.get_hostname()).strip()
|
||||
except Exception as e:
|
||||
logging.error(f"{self} - Getting hostname failed: {e}")
|
||||
return None
|
||||
@@ -520,7 +523,7 @@ class BOSMiner(BaseMiner):
|
||||
return self.light
|
||||
try:
|
||||
data = (
|
||||
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
|
||||
await self.ssh.get_led_status()
|
||||
).strip()
|
||||
self.light = False
|
||||
if data == "50":
|
||||
@@ -652,7 +655,9 @@ class BOSer(BaseMiner):
|
||||
"""Handler for new versions of BraiinsOS+ (post-gRPC)"""
|
||||
|
||||
_api_cls = BOSMinerRPCAPI
|
||||
web: BOSMinerRPCAPI
|
||||
_web_cls = BOSerWebAPI
|
||||
web: BOSerWebAPI
|
||||
|
||||
data_locations = BOSER_DATA_LOC
|
||||
|
||||
|
||||
@@ -116,7 +116,10 @@ BTMINER_DATA_LOC = DataLocations(
|
||||
|
||||
|
||||
class BTMiner(BaseMiner):
|
||||
"""Base handler for BTMiner based miners."""
|
||||
|
||||
_api_cls = BTMinerRPCAPI
|
||||
api: BTMinerRPCAPI
|
||||
|
||||
data_locations = BTMINER_DATA_LOC
|
||||
|
||||
|
||||
@@ -65,15 +65,10 @@ class CGMiner(BaseMiner):
|
||||
"""Base handler for CGMiner based miners"""
|
||||
|
||||
_api_cls = CGMinerRPCAPI
|
||||
api: CGMinerRPCAPI
|
||||
|
||||
data_locations = CGMINER_DATA_LOC
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
ret = await self.send_ssh_command("reboot")
|
||||
if ret is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
# get pool data
|
||||
try:
|
||||
|
||||
@@ -84,7 +84,10 @@ EPIC_DATA_LOC = DataLocations(
|
||||
|
||||
|
||||
class ePIC(BaseMiner):
|
||||
"""Handler for miners with the ePIC board"""
|
||||
|
||||
_web_cls = ePICWebAPI
|
||||
web: ePICWebAPI
|
||||
|
||||
firmware = "ePIC"
|
||||
|
||||
|
||||
@@ -67,7 +67,10 @@ GOLDSHELL_DATA_LOC = DataLocations(
|
||||
|
||||
|
||||
class GoldshellMiner(BFGMiner):
|
||||
"""Handler for goldshell miners"""
|
||||
|
||||
_web_cls = GoldshellWebAPI
|
||||
web: GoldshellWebAPI
|
||||
|
||||
data_locations = GOLDSHELL_DATA_LOC
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ class Innosilicon(CGMiner):
|
||||
"""Base handler for Innosilicon miners"""
|
||||
|
||||
_web_cls = InnosiliconWebAPI
|
||||
web: InnosiliconWebAPI
|
||||
|
||||
data_locations = INNOSILICON_DATA_LOC
|
||||
|
||||
|
||||
@@ -61,8 +61,11 @@ LUXMINER_DATA_LOC = DataLocations(
|
||||
|
||||
|
||||
class LUXMiner(BaseMiner):
|
||||
_api_cls = LUXMinerRPCAPI
|
||||
"""Handler for LuxOS miners"""
|
||||
|
||||
_api_cls = LUXMinerRPCAPI
|
||||
api: LUXMinerRPCAPI
|
||||
|
||||
firmware = "LuxOS"
|
||||
|
||||
data_locations = LUXMINER_DATA_LOC
|
||||
|
||||
@@ -75,7 +75,10 @@ VNISH_DATA_LOC = DataLocations(
|
||||
|
||||
|
||||
class VNish(BMMiner):
|
||||
"""Handler for VNish miners"""
|
||||
|
||||
_web_cls = VNishWebAPI
|
||||
web: VNishWebAPI
|
||||
|
||||
firmware = "VNish"
|
||||
|
||||
|
||||
@@ -15,13 +15,10 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import ipaddress
|
||||
import logging
|
||||
import warnings
|
||||
from dataclasses import dataclass, field, make_dataclass
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Optional, Protocol, Tuple, Type, TypeVar, Union
|
||||
|
||||
import asyncssh
|
||||
from typing import List, Optional, Protocol, Tuple, Type, TypeVar, Union
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, MinerData
|
||||
@@ -109,9 +106,12 @@ DataLocations = make_dataclass(
|
||||
class MinerProtocol(Protocol):
|
||||
_api_cls: Type = None
|
||||
_web_cls: Type = None
|
||||
_ssh_cls: Type = None
|
||||
|
||||
api = None
|
||||
web = None
|
||||
ip: str = None
|
||||
api: _api_cls = None
|
||||
web: _web_cls = None
|
||||
ssh: _ssh_cls = None
|
||||
|
||||
make: str = None
|
||||
raw_model: str = None
|
||||
@@ -150,69 +150,6 @@ class MinerProtocol(Protocol):
|
||||
model_data.append(f"({self.firmware})")
|
||||
return " ".join(model_data)
|
||||
|
||||
@property
|
||||
def pwd(self) -> Dict[str, str]:
|
||||
data = {}
|
||||
if self.web is not None:
|
||||
data["web"] = self.web.pwd
|
||||
if self.api is not None:
|
||||
data["api"] = self.api.pwd
|
||||
return data
|
||||
|
||||
@pwd.setter
|
||||
def pwd(self, val: str) -> None:
|
||||
if self.web is not None:
|
||||
self.web.pwd = val
|
||||
if self.api is not None:
|
||||
self.api.pwd = val
|
||||
|
||||
@property
|
||||
def username(self) -> Dict[str, str]:
|
||||
data = {}
|
||||
if self.web is not None:
|
||||
data["web"] = self.web.pwd
|
||||
if self.api is not None:
|
||||
data["api"] = self.api.pwd
|
||||
return data
|
||||
|
||||
@username.setter
|
||||
def username(self, val) -> None:
|
||||
if self.web is not None:
|
||||
self.web.username = val
|
||||
|
||||
async def _get_ssh_connection(self) -> asyncssh.connect:
|
||||
"""Create a new asyncssh connection"""
|
||||
try:
|
||||
conn = await asyncssh.connect(
|
||||
str(self.ip),
|
||||
known_hosts=None,
|
||||
username="root",
|
||||
password=self.pwd,
|
||||
server_host_key_algs=["ssh-rsa"],
|
||||
)
|
||||
return conn
|
||||
except asyncssh.misc.PermissionDenied as e:
|
||||
raise ConnectionRefusedError from e
|
||||
except Exception as e:
|
||||
raise ConnectionError from e
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
||||
"""Send an ssh command to the miner"""
|
||||
try:
|
||||
conn = await asyncio.wait_for(self._get_ssh_connection(), timeout=10)
|
||||
except (ConnectionError, asyncio.TimeoutError):
|
||||
return None
|
||||
|
||||
try:
|
||||
async with conn:
|
||||
resp = await conn.run(cmd)
|
||||
result = str(max(resp.stdout, resp.stderr, key=len))
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logging.error(f"{self} command {cmd} error: {e}")
|
||||
return None
|
||||
|
||||
async def check_light(self) -> bool:
|
||||
return await self.get_fault_light()
|
||||
|
||||
@@ -597,9 +534,9 @@ class MinerProtocol(Protocol):
|
||||
ip=str(self.ip),
|
||||
make=self.make,
|
||||
model=self.model,
|
||||
expected_chips=self.expected_chips
|
||||
expected_chips=self.expected_chips * self.expected_hashboards
|
||||
if self.expected_chips is not None
|
||||
else 0 * self.expected_hashboards,
|
||||
else 0,
|
||||
expected_hashboards=self.expected_hashboards,
|
||||
hashboards=[
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
@@ -632,6 +569,8 @@ class BaseMiner(MinerProtocol):
|
||||
self.api = self._api_cls(ip)
|
||||
if self._web_cls is not None:
|
||||
self.web = self._web_cls(ip)
|
||||
if self._ssh_cls is not None:
|
||||
self.ssh = self._ssh_cls(ip)
|
||||
|
||||
|
||||
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
|
||||
|
||||
@@ -28,13 +28,15 @@ _settings = { # defaults
|
||||
"factory_get_timeout": 3,
|
||||
"get_data_retries": 1,
|
||||
"api_function_timeout": 5,
|
||||
"default_whatsminer_password": "admin",
|
||||
"default_innosilicon_password": "admin",
|
||||
"default_antminer_password": "root",
|
||||
"default_bosminer_password": "root",
|
||||
"default_vnish_password": "admin",
|
||||
"default_goldshell_password": "123456789",
|
||||
"default_hive_password": "admin",
|
||||
"default_whatsminer_rpc_password": "admin",
|
||||
"default_innosilicon_web_password": "admin",
|
||||
"default_antminer_web_password": "root",
|
||||
"default_bosminer_web_password": "root",
|
||||
"default_vnish_web_password": "admin",
|
||||
"default_goldshell_web_password": "123456789",
|
||||
"default_hive_web_password": "admin",
|
||||
"default_antminer_ssh_password": "miner",
|
||||
"default_bosminer_ssh_password": "root",
|
||||
"socket_linger_time": 1000,
|
||||
}
|
||||
|
||||
|
||||
47
pyasic/ssh/__init__.py
Normal file
47
pyasic/ssh/__init__.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import asyncssh
|
||||
|
||||
|
||||
class MinerSSH:
|
||||
def __init__(self, ip: str) -> None:
|
||||
self.ip = ip
|
||||
self.pwd = None
|
||||
self.username = "root"
|
||||
self.port = 22
|
||||
|
||||
async def _get_connection(self) -> asyncssh.connect:
|
||||
"""Create a new asyncssh connection"""
|
||||
try:
|
||||
conn = await asyncssh.connect(
|
||||
str(self.ip),
|
||||
port=self.port,
|
||||
known_hosts=None,
|
||||
username=self.username,
|
||||
password=self.pwd,
|
||||
server_host_key_algs=["ssh-rsa"],
|
||||
)
|
||||
return conn
|
||||
except asyncssh.misc.PermissionDenied as e:
|
||||
raise ConnectionRefusedError from e
|
||||
except Exception as e:
|
||||
raise ConnectionError from e
|
||||
|
||||
async def send_command(self, cmd: str) -> Optional[str]:
|
||||
"""Send an ssh command to the miner"""
|
||||
try:
|
||||
conn = await asyncio.wait_for(self._get_connection(), timeout=10)
|
||||
except (ConnectionError, asyncio.TimeoutError):
|
||||
return None
|
||||
|
||||
try:
|
||||
async with conn:
|
||||
resp = await conn.run(cmd)
|
||||
result = str(max(resp.stdout, resp.stderr, key=len))
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logging.error(f"{self} command {cmd} error: {e}")
|
||||
return None
|
||||
9
pyasic/ssh/antminer.py
Normal file
9
pyasic/ssh/antminer.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from pyasic import settings
|
||||
from pyasic.ssh import MinerSSH
|
||||
|
||||
|
||||
class AntminerModernSSH(MinerSSH):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__(ip)
|
||||
self.pwd = settings.get("default_antminer_ssh_password", "root")
|
||||
self.username = "miner"
|
||||
35
pyasic/ssh/braiins_os.py
Normal file
35
pyasic/ssh/braiins_os.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from pyasic import settings
|
||||
from pyasic.ssh import MinerSSH
|
||||
|
||||
|
||||
class BOSMinerSSH(MinerSSH):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__(ip)
|
||||
self.pwd = settings.get("default_bosminer_ssh_password", "root")
|
||||
|
||||
async def get_board_info(self):
|
||||
return await self.send_command("bosminer model -d")
|
||||
|
||||
async def fault_light_on(self):
|
||||
return await self.send_command("miner fault_light on")
|
||||
|
||||
async def fault_light_off(self):
|
||||
return await self.send_command("miner fault_light off")
|
||||
|
||||
async def restart_bosminer(self):
|
||||
return await self.send_command("/etc/init.d/bosminer restart")
|
||||
|
||||
async def reboot(self):
|
||||
return await self.send_command("/sbin/reboot")
|
||||
|
||||
async def get_config_file(self):
|
||||
return await self.send_command("cat /etc/bosminer.toml")
|
||||
|
||||
async def get_network_config(self):
|
||||
return await self.send_command("cat /etc/config/network")
|
||||
|
||||
async def get_hostname(self):
|
||||
return await self.send_command("cat /proc/sys/kernel/hostname")
|
||||
|
||||
async def get_led_status(self):
|
||||
return await self.send_command("cat /sys/class/leds/'Red LED'/delay_off")
|
||||
Reference in New Issue
Block a user