Initial commit
This commit is contained in:
82
app/ssh_control.py
Normal file
82
app/ssh_control.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import shlex
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import paramiko
|
||||
|
||||
|
||||
class SSHControlError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def _derive_host_from_url(rpc_url: str) -> str:
|
||||
try:
|
||||
parsed = urlparse(rpc_url)
|
||||
return parsed.hostname or ""
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
|
||||
|
||||
def run_remote_command(settings: dict, command: str) -> dict:
|
||||
ssh_host = (settings.get("ssh_host") or "").strip() or _derive_host_from_url(settings.get("rpc_url", ""))
|
||||
ssh_username = (settings.get("ssh_username") or "").strip()
|
||||
ssh_password = settings.get("ssh_password") or ""
|
||||
ssh_key_path = (settings.get("ssh_key_path") or "").strip()
|
||||
ssh_port = int(settings.get("ssh_port") or 22)
|
||||
|
||||
if not ssh_host:
|
||||
raise SSHControlError("SSH host is required to run start/restart commands")
|
||||
if not ssh_username:
|
||||
raise SSHControlError("SSH username is required to run start/restart commands")
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
connect_args = {
|
||||
"hostname": ssh_host,
|
||||
"port": ssh_port,
|
||||
"username": ssh_username,
|
||||
"timeout": 10,
|
||||
"look_for_keys": False,
|
||||
"allow_agent": False,
|
||||
}
|
||||
|
||||
if ssh_key_path:
|
||||
connect_args["key_filename"] = ssh_key_path
|
||||
if ssh_password:
|
||||
connect_args["passphrase"] = ssh_password
|
||||
elif ssh_password:
|
||||
connect_args["password"] = ssh_password
|
||||
else:
|
||||
connect_args["look_for_keys"] = True
|
||||
connect_args["allow_agent"] = True
|
||||
|
||||
try:
|
||||
client.connect(**connect_args)
|
||||
_, stdout, stderr = client.exec_command(command, timeout=20)
|
||||
exit_code = stdout.channel.recv_exit_status()
|
||||
out = stdout.read().decode("utf-8", errors="replace").strip()
|
||||
err = stderr.read().decode("utf-8", errors="replace").strip()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
raise SSHControlError(f"SSH command failed: {exc}") from exc
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
return {
|
||||
"exit_code": exit_code,
|
||||
"stdout": out,
|
||||
"stderr": err,
|
||||
"host": ssh_host,
|
||||
}
|
||||
|
||||
|
||||
|
||||
def build_start_command(settings: dict) -> str:
|
||||
config_path = (settings.get("config_path") or "").strip()
|
||||
bitcoin_binary = (settings.get("bitcoin_binary") or "bitcoind").strip() or "bitcoind"
|
||||
|
||||
if not config_path:
|
||||
raise SSHControlError("Config file path is required to start bitcoind")
|
||||
|
||||
return f"{shlex.quote(bitcoin_binary)} -daemon -conf={shlex.quote(config_path)}"
|
||||
Reference in New Issue
Block a user