import hashlib
import html
import io
import ipaddress
import json
import os
import re
import shutil
import sys
import threading
import time
import socket
import platform
from uuid import uuid4
import math
import requests
import simple_websocket
import websocket
from werkzeug.datastructures import FileStorage
from datetime import datetime
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
import public
from typing import Optional, Tuple, List, Callable, Union, Any, Dict
from .one_panel_api import OnePanelApiClient
from mod.project.node.dbutil import Node, NodeAPPKey
try:
from YakPanel import cache
except:
class _Cache:
def __init__(self):
self._data = {}
def get(self, key):
return self._data.get(key, None)
def set(self, key, value, timeout):
self._data[key] = value
cache = _Cache()
def _get_os_info() -> Tuple[str, str]:
os_data = public.readFile("/etc/os-release")
if not os_data:
return "", ""
version = ["", ""]
for line in os_data.split("\n"):
if line.startswith("NAME="):
name = line.split("=")[1].replace('"', "").strip()
name_arr = name.split(" ")
if len(name_arr) > 1:
name = name_arr[0]
version[0] = name
elif line.startswith("VERSION="):
ver = line.split("=")[1].replace('"', "").strip()
ver_arr = ver.split(" ")
if len(ver_arr) > 1:
ver = ver_arr[0]
version[1] = ver
version = " ".join(version)
return version, platform.machine()
class ServerNode:
is_local = False
node_db_file = public.get_panel_path() + "/data/db/node.db"
def __init__(self, origin: str, api_key: str, app_key: str, remarks: str = "", timeout: int = 20):
self.origin = origin
self.api_key = api_key
self.app_key: Optional[NodeAPPKey] = Node.parse_app_key(app_key)
self.remarks = remarks
self.timeout = timeout
@property
def node_server_ip(self):
from urllib.parse import urlparse
host = urlparse(self.origin).hostname
try:
if isinstance(host, str) and ipaddress.ip_address(host):
return host
except:
pass
try:
ip_address = socket.gethostbyname(host)
return ip_address
except socket.gaierror as e:
print(f"Error: {e}")
return host
def app_bind(self) -> Optional[str]:
if self.app_key is None:
return "App key parsing failed, please confirm key information again"
version, arch = _get_os_info()
# public.print_log(self.app_key.app_token)
pdata = {
"bind_token": self.app_key.app_token,
"client_brand": "Yak-Panel",
"client_model": "Linux" if not version else "Linux {} {}".format(version, arch)
}
header = {
'Content-Type': 'application/x-www-form-urlencoded',
"User-Agent": "Yak-Panel/Node Manager"
}
bind_url = self.app_key.origin + "/check_bind"
try:
res = requests.post(bind_url, data=pdata, headers=header, verify=False, timeout=self.timeout)
if res.status_code != 200:
return "Binding request failed, returned status code:%s,响应信息为:%s" % (res.status_code, res.text)
res_str = res.text.strip()
if res_str == "0":
return "Binding failed, please check if your key is correct"
elif res_str == "1":
return None
else:
return "Binding failed, error message is:{}".format(json.loads(res_str))
except Exception as e:
return "Network connection failed, unable to request to the target server"
def app_bind_status(self) -> Optional[str]:
if self.app_key is None:
return "App key parsing failed, please confirm key information again"
pdata = {"bind_token": self.app_key.app_token}
header = {
'Content-Type': 'application/x-www-form-urlencoded',
"User-Agent": "Yak-Panel/Node Manager"
}
bind_url = self.app_key.origin + "/get_app_bind_status"
try:
resp = requests.post(bind_url, data=pdata, headers=header, verify=False, timeout=self.timeout)
if not resp.status_code == 200:
return "Error in obtaining binding status response status code. Please check if the node address and API are correct. The current status code is {},The return information is {}".format(
resp.status_code, resp.text)
res_str = resp.text.strip()
if res_str == "0":
return "Your device is not bound, please try to rebind it"
elif res_str == "1":
return None
else:
return "Request binding status failed with error message:{}".format(json.loads(res_str))
except Exception as e:
return "Failed to obtain binding status. Please check if the node app key is correct. The error message is:{}".format(str(e))
@classmethod
def new_by_id(cls, node_id: int) -> Optional[Union['ServerNode', 'LocalNode', 'LPanelNode']]:
data = public.S('node', cls.node_db_file).where("id = ?", (node_id,)).find()
if not data or not isinstance(data, dict):
return None
return cls.new_by_data(data)
@classmethod
def new_by_data(cls, node_data: dict):
if node_data["api_key"] == "local" and node_data["app_key"] == "local":
return LocalNode()
if node_data['lpver']:
lp = LPanelNode(
address=node_data['address'],
api_key=node_data['api_key'],
lpver=node_data['lpver'],
timeout=node_data.get('timeout', 20)
)
lp.remarks = node_data['remarks']
return lp
if not node_data["api_key"] and not node_data["app_key"]:
return None
return cls(
origin=node_data['address'],
api_key=node_data['api_key'],
app_key=node_data['app_key'],
remarks=node_data['remarks'],
timeout=node_data.get('timeout', 20)
)
def show_name(self) -> str:
if self.remarks:
return "{}({})".format(self.remarks, self.origin)
return self.origin
@staticmethod
def _get_node_ip(node_id: int) -> str:
data = public.S('node', public.get_panel_path() + "/data/db/node.db").where("id = ?", (node_id,)).find()
if not data or not isinstance(data, dict):
return ""
if data['address'].startswith("http"):
from urllib.parse import urlparse
host = urlparse(data['address']).hostname
else:
host = data['address']
try:
if isinstance(host, str) and ipaddress.ip_address(host):
return host
except:
pass
try:
ip_address = socket.gethostbyname(host)
return ip_address
except socket.gaierror as e:
print(f"Error: {e}")
return ""
@classmethod
def get_node_ip(cls, node_id: int) -> Optional[str]:
if public.S('node', cls.node_db_file).where("id=? AND app_key = 'local' AND api_key = 'local'", (node_id,)).count() > 0:
return "127.0.0.1"
cache_key = "mod_node_server_ip_{}".format(node_id)
c_ip = cache.get(cache_key)
if c_ip:
return c_ip
else:
ip = cls._get_node_ip(node_id)
if ip:
cache.set(cache_key, ip, 86400)
return ip
return None
@classmethod
def check_api_key(cls, node: Node) -> str:
if not LPanelNode.check_api_key(node): # 如果1panel检查可以连接上,则标记为1panel并直接返回
return ""
node = cls(node.address, node.api_key, node.app_key)
data, err = node._request("/system", "GetSystemTotal")
if err:
return err
if isinstance(data, dict) and "version" in data:
return ""
if isinstance(data, dict) and not data:return "Validation error:Please check if the node information is correct"
return "Validation error:%s" % str(data)
def test_conn(self) -> str:
data, err = self._request("/system", "GetSystemTotal")
if err:
return err
if isinstance(data, dict) and "version" in data:
return ""
return "Validation error:%s" % str(data)
def version(self) -> Optional[str]:
data, err = self._request("/system", "GetSystemTotal")
if err:
return None
if isinstance(data, dict) and "version" in data:
return data["version"]
return None
def get_net_work(self) -> Tuple[Optional[dict], str]:
data, err = self._request("/system", "GetNetWork")
if err:
return None, err
if isinstance(data, dict) and "cpu" in data and "mem" in data:
return data, ""
return None, "data in wrong format: %s" % str(data)
def get_tmp_token(self) -> Tuple[str, str]:
data, err = self._request("/config", "get_tmp_token")
if err:
return "", err
if isinstance(data, dict) and "status" in data:
if data["status"] and "msg" in data:
return data["msg"], ""
return "", data.get("msg", str(data))
if isinstance(data, dict) and "result" in data:
return data["result"], ""
return "", "data in wrong format: %s" % str(data)
def get_bt_params(self, other_data: dict = None) -> Dict:
now = int(time.time() * 1000)
other_data = other_data or {}
if self.app_key:
md5_panel_key = hashlib.md5(self.app_key.request_token.encode()).hexdigest()
request_token = hashlib.md5("{}{}".format(now, md5_panel_key).encode()).hexdigest()
other_data["request_token"] = request_token
other_data["request_time"] = str(now)
form_data = json.dumps(other_data)
return {
"client_bind_token": self.app_key.app_token,
"form_data": public.aes_encrypt(form_data, self.app_key.app_key)
}
else:
md5_panel_key = hashlib.md5(self.api_key.encode()).hexdigest()
request_token = hashlib.md5("{}{}".format(now, md5_panel_key).encode()).hexdigest()
other_data["request_token"] = request_token
other_data["request_time"] = str(now)
return other_data
def _request(self, path: str, action: str, pdata: dict = None) -> Tuple[Optional[any], str]:
url = "{}/v2{}".format(self.origin, path)
pdata = pdata or {}
bt_p = self.get_bt_params({"action": action, **pdata})
header = {
'Content-Type': 'application/x-www-form-urlencoded',
"User-Agent": "Yak-Panel/Node Manager"
}
source_action=["DeleteDir","DeleteFile","CreateDir", "get_path_size"]
try:
resp = requests.post(url, data=bt_p, headers=header, verify=False, timeout=self.timeout)
if not resp.status_code == 200:
return None, "The response status code is incorrect. Please check if the node address and API are correct. The current response status code is {}".format(
resp.status_code)
if self.app_key:
real_data = public.aes_decrypt(resp.text, self.app_key.app_key)
if action in source_action:return json.loads(real_data), ""
return json.loads(real_data).get('message',{}), ""
if action in source_action:return resp.json(), ""
return resp.json().get('message',{}), ""
except Exception as e:
# public.print_error()
# return None, "请求节点失败,请检查节点地址和api是否正确,错误信息为:{}".format(str(e))
if self.remarks:
return None, "Request node [{}] failed, please check if the node address and API are correct".format(self.remarks)
return None, "Node request failed, please check if the node address and API are correct"
def get_file_body(self, path: str) -> Tuple[Optional[str], str]:
data, err = self._request("/files", "GetFileBody", pdata={"path": path})
if err:
return None, err
if not isinstance(data, dict):
if isinstance(data, dict) and "msg" in data:
return None, data["msg"]
return None, "data in wrong format"
return data["data"], ""
def php_site_list(self) -> Tuple[Optional[List[dict]], str]:
data, err = self._request("/mod/node/node/php_site_list", "", pdata={
"type": -1,
"p": 1,
"limit": 1000,
"table": "sites",
"search": "",
})
if err:
return None, err
if not isinstance(data, list):
if isinstance(data, dict) and "msg" in data:
return None, data["msg"]
return None, "data in wrong format"
return data, ""
def create_php_site(self, site_name: str, port: int, **kwargs) -> Tuple[Optional[int], str]:
path = "/www/wwwroot/{}".format(site_name)
if port not in (80, 443):
path = "/www/wwwroot/{}_{}".format(site_name, port)
webname = {
"domain": site_name if port in (80, 443) else "{}:{}".format(site_name, port),
"domainlist": [],
"count": 0
}
data, err = self._request("/site", "AddSite", pdata={
"path": path,
"ftp": "false",
"type": "PHP",
"type_id": "0",
"ps": "{}【Load balancing node】".format(site_name),
"port": str(port),
"version": "00",
"sql": "false",
"webname": json.dumps(webname)
})
if err:
return None, err
if not isinstance(data, dict):
return None, "data in wrong format"
if isinstance(data, dict) and "msg" in data and not data.get("status", True):
return None, data["msg"]
site_id = data.get("siteId", None)
if not isinstance(site_id, int):
return None, "Data parsing error"
return site_id, ""
def set_firewall_open(self, port: int, protocol: str = "tcp") -> Tuple[bool, str]:
data, err = self._request("/firewall/com/set_port_rule", "", pdata={
"protocol": "udp" if protocol == "udp" else "tcp",
"port": str(port),
"choose": "all",
"types": "accept",
"chain": "INPUT",
"operation": "add",
"strategy": "accept",
})
if err:
return False, err
if isinstance(data, dict) and "msg" in data:
msg = data["msg"]
if "Port {} already exists".format(port) in msg:
return True, ""
elif "Setup successful" in msg:
return True, ""
return False, "Setup failed"
def add_domain(self, site_id: int, site_name: str, domain: str, port: int) -> Tuple[bool, str]:
data, err = self._request("/site", "AddDomain", pdata={
"domain": "{}:{}".format(domain, port),
"webname": site_name,
"id": str(site_id)
})
if err:
return False, err
if isinstance(data, dict) and "domains" in data:
for d in data["domains"]:
if d["name"] == domain:
if "Already bound" in d["msg"]:
return True, ""
elif "Added successfully" in d["msg"]:
return True, ""
return False, "Add failed, the target machine returned information as:{}".format(
data.get("msg", str(data)) if isinstance(data, dict) else str(data))
def has_domain(self, site_id: int, domain: str) -> bool:
data, err = self._request("/data", "getData", pdata={
"table": "domain",
"list": "True",
"search": str(site_id)
})
if err:
return False
if not isinstance(data, list):
return False
for d in data:
if d["name"] == domain:
return True
return False
# mode -> cover: 覆盖,ignore: 跳过,rename:重命名
def upload_file(self, filename: str, target_path: str, mode: str = "cover",
call_log: Callable[[int, str], None] = None) -> str:
if not os.path.isfile(filename):
return "File: {} does not exist".format(filename)
target_file = os.path.join(target_path, os.path.basename(filename))
exits, err = self.target_file_exits(target_file)
if err:
return err
if exits and mode == "ignore":
call_log(0, "File upload:{} -> {},The target file already exists, skip uploading".format(filename, target_file))
return ""
if exits and mode == "rename":
upload_name = "{}_{}".format(os.path.basename(filename), public.md5(filename))
call_log(0, "File upload:{} -> {},The target file already exists, it will be renamed to {}".format(filename, target_file, upload_name))
else:
upload_name = os.path.basename(filename)
if os.path.getsize(filename) > 1024 * 1024 * 5:
return self._upload_big_file(filename, target_path, upload_name, call_log)
else:
return self._upload_little_file(filename, target_path, upload_name, call_log)
def target_file_exits(self, target_file: str) -> Tuple[bool, str]:
data, err = self._request("/files", "upload_files_exists", pdata={
"files": target_file,
})
if err:
return False, err
if not isinstance(data, list):
return False, "data in wrong format: %s" % str(data)
for f in data:
if f["filename"] == target_file and f["exists"]:
return True, ""
return False, ""
def upload_check(self, target_file_list: List[str]) -> Tuple[List[dict], str]:
data, err = self._request("/files", "upload_files_exists", pdata={
"files": "\n".join(target_file_list),
})
if err:
return [], err
if not isinstance(data, list):
if isinstance(data, dict):
return [], data.get("msg", "data in wrong format")
return [], "data in wrong format: %s" % str(data)
return data, ""
def _upload_big_file(self, filename: str, target_path: str, upload_name: str,
call_log: Callable[[int, str], None] = None) -> str:
url = "{}/files".format(self.origin)
bt_p = self.get_bt_params({"action": "upload"})
header = {"User-Agent": "Yak-Panel/Node Manager"}
try:
fb = open(filename, 'rb')
except Exception as e:
public.print_error()
return "File {} failed to open, please check file permissions, error message is:{}".format(filename, str(e))
file_size = os.path.getsize(filename)
for i in range(0, file_size, 1024 * 1024 * 5):
file_data = fb.read(1024 * 1024 * 5)
files = {'blob': ('blob', file_data, 'application/octet-stream')}
data = {
'f_path': target_path,
'f_name': upload_name,
'f_size': file_size,
'f_start': i,
}
data.update(bt_p)
try:
resp = requests.post(url, data=data, files=files, headers=header, verify=False, timeout=self.timeout)
if not resp.status_code == 200:
return "The response status code for uploading the file is incorrect. Please check if the node address and API are correct. The current status code is {}, and the return message is:{}".format(
resp.status_code, resp.text)
resp_text = resp.text
resp_json = {}
if self.app_key:
resp_text = public.aes_decrypt(resp_text, self.app_key.app_key).strip()
if not resp_text.isdecimal():
resp_json = json.loads(resp_text)
else:
resp_json = resp.json()
if resp_text.isdecimal():
up_p = int(resp_text)
if up_p != i + len(file_data):
return "Upload file failed, file block size is inconsistent"
else:
call_log(up_p * 100 // file_size, "File upload:{} -> {},The uploaded size is:{}".format(
filename, upload_name, public.to_size(up_p)))
elif "status" in resp_json:
if not resp_json["status"]:
return "Upload file failed with error message:{}".format(resp_json["msg"])
else:
call_log(100, "File upload:{} -> {},Upload successful".format(filename, upload_name))
return ""
else:
return "Upload file failed, response message is:{}".format(resp_text)
except Exception as e:
public.print_error()
return "Upload file: {} failed with error message:{}".format(filename, str(e))
return ""
def _upload_little_file(self, filename: str, target_path: str, upload_name: str,
call_log: Callable[[int, str], None] = None) -> str:
url = "{}/files".format(self.origin)
bt_p = self.get_bt_params({"action": "upload"})
header = {"User-Agent": "Yak-Panel/Node Manager"}
try:
with open(filename, 'rb') as f:
file_data = f.read()
except Exception as e:
public.print_error()
return "File {} failed to open, please check file permissions, error message is:{}".format(filename, str(e))
file_size = os.path.getsize(filename)
files = {'blob': ('blob', file_data, 'application/octet-stream')}
data = {
'f_path': target_path,
'f_name': upload_name,
'f_size': file_size,
'f_start': 0
}
data.update(bt_p)
try:
resp = requests.post(url, data=data, files=files, headers=header, verify=False, timeout=self.timeout)
if not resp.status_code == 200:
return "The response status code for uploading the file is incorrect. Please check if the node address and API are correct. The current status code is {}, and the return message is:{}".format(
resp.status_code, resp.text)
resp_json = {}
if self.app_key:
resp_text = public.aes_decrypt(resp.text, self.app_key.app_key).strip()
if not resp_text.isdecimal():
resp_json = json.loads(resp_text)
else:
resp_json = resp.json()
if not resp_json["status"]:
return "Upload file failed with error message:{}".format(resp_json["msg"])
return ""
except Exception as e:
public.print_error()
return "Upload file: {} failed with error message:{}".format(filename, str(e))
def upload_proxy(self):
try:
from YakPanel import request
f_name = request.form.get('f_name')
f_path = request.form.get('f_path')
f_size = request.form.get('f_size')
f_start = request.form.get('f_start')
blob_file: FileStorage = request.files.getlist('blob')[0]
url = "{}/files".format(self.origin)
bt_p = self.get_bt_params({"action": "upload"})
header = {"User-Agent": "Yak-Panel/Node Manager"}
files = {'blob': ('blob', blob_file.stream, 'application/octet-stream')}
data = {
'f_path': f_path,
'f_name': f_name,
'f_size': f_size,
'f_start': f_start
}
data.update(bt_p)
resp = requests.post(url, data=data, files=files, headers=header, verify=False, timeout=self.timeout)
if not resp.status_code == 200:
return "The response status code for uploading the file is incorrect. Please check if the node address and API are correct. The current status code is {}, and the return message is:{}".format(
resp.status_code, resp.text)
resp_text = resp.text
resp_json = {}
if self.app_key:
resp_text = public.aes_decrypt(resp_text, self.app_key.app_key).strip()
if not resp_text.isdecimal():
resp_json = json.loads(resp_text)
else:
resp_json = resp.json()
if resp_text.isdecimal():
return int(resp_text)
elif "status" in resp_json:
return resp_json
else:
return {"status": False, "msg": "Upload file failed, response message is:{}".format(resp_text)}
except Exception as e:
public.print_error()
return {"status": False, "msg": "Upload file failed with error message:{}".format(str(e))}
def remove_file(self, filename: str, is_dir: bool) -> dict:
action = "DeleteDir" if is_dir else "DeleteFile"
data, err = self._request("/files", action, pdata={
"path": filename,
})
if err:
return {"status": False, "msg": "Failed to delete file, error message is:{}".format(err)}
if isinstance(data, dict):
return data
return {"status": False, "msg": "Failed to delete file, response message is:{}".format(data)}
def create_dir(self, path: str) -> Tuple[Optional[Dict], str]:
data, err = self._request("/files", action="CreateDir", pdata={
"path": path,
})
if err:
return None, "Failed to create directory, error message is:{}".format(err)
if isinstance(data, dict):
return data, ""
return None, "Failed to create directory, response message is:{}".format(data)
def dir_walk(self, path: str) -> Tuple[List[dict], str]:
data, err = self._request("/mod/node/file_transfer/dir_walk", "", pdata={
"path": path,
})
if err:
return [], err
if not isinstance(data, list):
if isinstance(data, dict) and "msg" in data:
return [], data["msg"]
return [], "data in wrong format: %s" + str(data)
return data, ""
def filetransfer_version_check(self) -> str:
ver = self.version()
if not ver:
return "Node {} connection error".format(self.show_name())
try:
ver_list = [int(i) for i in ver.split(".")]
# if self.app_key: # todo: 版本号检测
# if ver_list[0] > 11 or (ver_list[0] == 11 and ver_list[1] >= 1):
# return "Node {} version is lower than [11.1.0], unable to use app link, please upgrade node version".format(self.show_name())
if ver_list[0] >=8:
return ""
elif ver_list[0] == 7 and ver_list[1] >= 65:
return ""
elif ver_list[0] == 7 and ver_list[1] == 0 and ver_list[2] >= 30:
return ""
elif ver_list[0] ==2 and ver_list[1] >= 15:
return ""
return "Please upgrade the panel version to 【8.0.0/7.65.0/7.0.30/2.15.0】 or above before using it"
except:
pass
return "Please upgrade the panel version to 【8.0.0/7.65.0/7.0.30/2.15.0】 or above before using it"
def node_create_filetransfer_task(self, source_node: dict,
target_node: dict,
source_path_list: List[dict],
target_path: str,
created_by: str,
default_mode: str = "cover") -> dict:
data, err = self._request("/mod/node/file_transfer/node_create_filetransfer_task", "", pdata={
"source_node": json.dumps(source_node),
"target_node": json.dumps(target_node),
"source_path_list": json.dumps(source_path_list),
"target_path": target_path,
"created_by": created_by,
"default_mode": default_mode,
})
if err:
return {"status": False, "msg": "Failed to create file transfer task, error message is:{}".format(err)}
if isinstance(data, dict):
return data
else:
return {"status": False, "msg": "Failed to create file transfer task, response message is:{}".format(data)}
def file_list(self, path: str, p: int, row: int, search: str) -> Tuple[Dict, str]:
data, err = self._request("/files", "GetDirNew", pdata={
"path": path,
"p": p,
"showRow": row,
"search": search,
})
if err:
return {}, err
if not isinstance(data, dict):
return {}, "data in wrong format: %s" + str(data)
return data, ""
def get_transfer_status(self, task_id: int) -> dict:
data, err = self._request("/mod/node/file_transfer/get_transfer_status", "", pdata={
"task_id": task_id,
})
if err:
return {"status": False, "msg": "Failed to retrieve the status of the file transfer task, error message is:{}".format(err)}
if isinstance(data, dict):
return data
return {"status": False, "msg": "Failed to obtain file transfer task status, response message is:{}".format(data)}
def proxy_transfer_status(self, task_id: int, ws: simple_websocket.Server):
err = self._proxy_websocket(
call_data={
"mod_name": "node",
"sub_mod_name": "file_transfer",
"def_name": "node_proxy_transfer_status",
"callback": "node_proxy_transfer_status",
"data": {
"task_id": task_id,
}
},
uri="ws_modsoc",
call_back=ws.send
)
if err:
ws.send(json.dumps({
"type": "error",
"msg": "Failed to retrieve the status of the file transfer task, error message is:{}".format(err)
}))
def _proxy_websocket(self, call_data: dict, uri: str, call_back: Callable[[Any],None]) -> str:
from urllib.parse import urlencode, urlparse
from ssl import CERT_NONE
try:
bt_p = self.get_bt_params()
header = {"User-Agent": "Yak-Panel/Node Manager"}
u = urlparse(self.origin)
url = (
"{}://{}/{}?{}".format(
"ws" if u.scheme == "http" else "wss",
u.netloc, uri, urlencode(bt_p)
)
)
ws_req = websocket.WebSocket(sslopt={"cert_reqs": CERT_NONE}) # 忽略证书
ws_req.connect(url, header=header)
ws_req.send("{}") # 跳过x-http-tokn 验证
ws_req.send(json.dumps(call_data)) # 发送调用数据
while True:
data = ws_req.recv()
if data == "{}":
break
if data:
call_back(data)
else:
break
ws_req.close()
return ""
except Exception as e:
return str(e)
def proxy_transferfile_status(self, task_id: int, exclude_nodes: List[int], the_log_id: int, call_back: Callable[[Any],None]) -> str:
return self._proxy_websocket(
call_data={
"mod_name": "node",
"sub_mod_name": "executor",
"def_name": "node_proxy_transferfile_status",
"callback": "node_proxy_transferfile_status",
"data": {
"task_id": task_id,
"exclude_nodes": exclude_nodes,
"the_log_id": the_log_id
}
},
uri="ws_modsoc",
call_back=call_back
)
def node_create_transfer_task(self, transfer_task_data:dict) -> dict:
data, err = self._request(
"/mod/node/executor/node_create_transfer_task", "", pdata={
"transfer_task_data": json.dumps(transfer_task_data),
}
)
if err:
return {"status": False, "msg": "Failed to create file transfer task, error message is:{}".format(err)}
if isinstance(data, dict):
return data
else:
return {"status": False, "msg": "Failed to create file transfer task, response message is:{}".format(data)}
def node_transferfile_status_history(self, transfer_task_id: int, only_error=True):
data, err = self._request(
"/mod/node/executor/node_transferfile_status_history", "", pdata={
"task_id": transfer_task_id,
"only_error": 1 if only_error else 0
}
)
if err:
return {"status": False, "msg": "Failed to create file transfer task, error message is:{}".format(err)}
if isinstance(data, dict):
return data
else:
return {"status": False, "msg": "Failed to create file transfer task, response message is:{}".format(data)}
def download_proxy(self, filename: str):
url = "{}/download".format(self.origin)
bt_p = self.get_bt_params()
header = {"User-Agent": "Yak-Panel/Node Manager"}
try:
resp = requests.get(url, params={"filename": filename}, data=bt_p, headers=header, stream=True, verify=False, timeout=self.timeout)
if not resp.status_code == 200:
return "The response status code for downloading the file is incorrect. Please check if the node address and API are correct. The current status code is {}, and the return message is:{}".format(
resp.status_code, resp.text)
from flask import send_file, stream_with_context, Response
filename = os.path.basename(filename)
if resp.headers.get("Content-Disposition", "").find("filename=") != -1:
filename = resp.headers.get("Content-Disposition", "").split("filename=")[1]
def generate():
for chunk in resp.iter_content(chunk_size=1024 * 1024 * 5):
if chunk:
yield chunk
# 设置响应头
headers = {
'Content-Type': resp.headers.get('Content-Type', 'application/octet-stream'),
'Content-Disposition': 'attachment; filename="{}"'.format(filename),
'Content-Length': resp.headers.get('Content-Length', ''),
'Accept-Ranges': 'bytes'
}
# 使用 stream_with_context 确保请求上下文在生成器运行时保持活跃
return Response(
stream_with_context(generate()),
headers=headers,
direct_passthrough=True
)
except Exception as e:
public.print_error()
return "Download file: {} failed with error message:{}".format(filename, str(e))
def download_file(self, filename: str, target_path: str, mode: str,
call_log: Callable[[int, str], None] = None) -> str:
target_file = os.path.join(target_path, os.path.basename(filename))
exits = os.path.exists(target_file)
if exits and mode == "ignore":
call_log(0, "File Download:{} -> {},The target file already exists, skip download".format(filename, target_file))
return ""
if exits and mode == "rename":
download_name = "{}_{}".format(os.path.basename(filename), public.md5(filename))
call_log(0, "File Download:{} -> {},The target file already exists, it will be renamed to {}".format(filename, target_file, download_name))
else:
download_name = os.path.basename(filename)
return self._download_file(filename, target_path, download_name, call_log)
def _download_file(self, filename: str, target_path: str, download_name: str,
call_log: Callable[[int, str], None] = None) -> str:
data, err = self.upload_check([filename])
if err:
return "Request file: {} status failed with error message:{}".format(filename, err)
file_size: Optional[int] = None
for i in data:
if i["filename"] == filename and i["isfile"] == True:
file_size = i["size"]
break
if file_size is None:
return "File {} does not exist, skip download".format(filename)
try:
if not os.path.isdir(target_path):
os.makedirs(target_path)
except Exception as e:
return "Failed to create folder {}, please check folder permissions, error message is:{}".format(target_path, str(e))
if file_size == 0:
fp = open(os.path.join(target_path, download_name), "w")
fp.close()
return ""
tmp_file = os.path.join(target_path, "{}.{}".format(download_name, uuid4().hex))
try:
if not os.path.exists(target_path):
os.makedirs(target_path)
fb = open(tmp_file, 'wb')
except Exception as e:
return "Failed to create temporary file {}, please check folder permissions, error message is:{}".format(tmp_file, str(e))
url = "{}/download".format(self.origin)
bt_p = self.get_bt_params()
header = {"User-Agent": "Yak-Panel/Node Manager"}
try:
resp = requests.get(url, params={"filename": filename}, data=bt_p, headers=header, stream=True, verify=False, timeout=self.timeout)
if not resp.status_code == 200:
return "The response status code for downloading the file is incorrect. Please check if the node address and API are correct. The current status code is {}, and the return message is:{}".format(
resp.status_code, resp.text)
now_size = 0
for chunk in resp.iter_content(chunk_size=1024 * 1024 * 3):
if chunk:
now_size += len(chunk)
fb.write(chunk)
fb.flush()
call_log(now_size * 100 // file_size,
"Download file {}, Downloaded: {}".format(filename, public.to_size(now_size)))
if fb.tell() != file_size:
return "Download file {} failed, file size check error".format(filename)
fb.close()
shutil.move(tmp_file, os.path.join(target_path, download_name))
return ""
except Exception as e:
return "Download file {} failed,The error message is:{}".format(filename, str(e))
finally:
if not fb.closed:
fb.close()
if os.path.exists(tmp_file):
os.remove(tmp_file)
def dir_size(self, path: str) -> Tuple[Optional[int], str]:
data, err = self._request("/files", "get_path_size", pdata={
"path": path,
})
if err:
return None, err
if isinstance(data, dict) and "size" in data:
return data["size"], ""
return None, "Failed to retrieve directory size, response message is:{}".format(data)
def get_sshd_port(self) -> Optional[int]:
data, err = self._request("/safe/ssh/GetSshInfo", "", pdata={})
if err:
return None
if isinstance(data, dict) and "port" in data:
return int(data["port"])
return None
def restart_bt_panel(self) -> Dict[str, Any]:
res, err = self._request("/system", "ReWeb", pdata={})
if err:
return {"status": False, "msg": err}
return {"status": True, "msg": "Restart successful"}
def server_reboot(self):
res, err = self._request("/system", "ServiceAdmin", pdata={
"name": "nginx",
"type": "stop",
})
if err:
return {"status": False, "msg": err}
if res.get('result','')!='Executed successfully!':
return {"status": False, "msg": "Nginx failed to stop, unable to continue executing server restart:" + res["msg"]}
res, err = self._request("/system", "ServiceAdmin", pdata={
"name": "mysqld",
"type": "stop",
})
if err:
return {"status": False, "msg": err}
if res.get('result','')!='Executed successfully!':
return {"status": False, "msg": "MySQL service failed to stop, unable to continue executing server restart:" + res["msg"]}
res, _ = self._request("/system", "RestartServer", pdata={})
if res.get('result','')!='Command sent successfully!':
return {"status": False, "msg": "Server restart failed:" + res["msg"]}
from mod.project.node.dbutil import ServerMonitorRepo
repo = ServerMonitorRepo()
repo.set_wait_reboot(self.node_server_ip, True)
def wait_for_reboot():
# wait 等待服务器重启成功, 超时时间默认为 10 分钟
wait_for = time.time() + 600
time.sleep(3)
while time.time() < wait_for:
if self.test_conn() == "": # 无错误时表示重启成功
repo.set_wait_reboot(self.node_server_ip, False)
from mod.project.node.dbutil import ServerNodeDB
if self.app_key !=None:self.app_key=self.app_key.to_string()
node_data = ServerNodeDB().find_node(api_key=self.api_key, app_key=self.app_key)
if node_data:
monitor_node_once(node_data)
return {"status": True, "msg": "Server restart successful"}
time.sleep(3)
# public.print_log("Waiting for server restart... {}".format(wait_for - time.time()))
repo.set_wait_reboot(self.node_server_ip, False)
return {"status": False, "msg": "Restarting server failed, server information has not been detected for more than 10 minutes"}
t = threading.Thread(target=wait_for_reboot, daemon=True)
t.start()
return {"status": True, "msg": "The server restart has started, please wait patiently for the successful restart"}
def read_ssh_key(self) -> Tuple[Optional[str], str]:
data, err = self._request("/ssh_security", "get_key", pdata={})
if err:
return None, err
if not isinstance(data, dict) or "status" not in data:
return None, "Failed to obtain SSH key, please check if the node address and API are correct"
if not data["status"]:
return None, data["msg"]
return data["msg"], ""
def get_dir(self, path: str, search: str, disk: str):
data, err = self._request("/files", "GetDir", pdata={
"path": path, "disk": disk, "search": search
})
if err:
return {"status": False, "msg": err}
if not isinstance(data, dict):
return {"status": False, "msg": "Failed to retrieve directory, please check if the node address and API are correct"}
return data
def upload_dir_check(self, target_file: str) -> str:
data, err = self._request("/files", "upload_files_exists", pdata={"files": target_file})
if err:
return err
if not isinstance(data, list):
return "data in wrong format: %s" % str(data)
for f in data:
if f["filename"] == target_file and f["exists"]:
if f["isfile"]:
return "The name path is not a directory"
else:
return ""
return ""
def monitor_all_node_status():
all_nodes = public.S('node', public.get_panel_path() + "/data/db/node.db").select()
if not isinstance(all_nodes, list) or not all_nodes:
return
import threading
threads = []
for tmp_node_data in all_nodes:
t = threading.Thread(target=monitor_node_once, args=(tmp_node_data,))
t.start()
threads.append(t)
# 等待所有线程完成(可选)
for t in threads:
t.join()
def monitor_node_once(node_data: dict):
from mod.project.node.dbutil import Node, ServerNodeDB, ServerMonitorRepo
from mod.project.node.nodeutil.ssh_wrap import SSHApi
from mod.base.ssh_executor import test_ssh_config
try:
if node_data["app_key"] == "local" and node_data["api_key"] == "local":
return
node_data["error"] = json.loads(node_data["error"])
node_data["ssh_conf"] = json.loads(node_data["ssh_conf"])
node, err = Node.from_dict(node_data)
if err:
# public.print_log("Node data parsing error:{}".format(err))
return
if node_data["lpver"]:
srv = LPanelNode(node.address, node.api_key, node.lpver)
elif node.api_key or node.app_key:
srv = ServerNode(node.address, node.api_key, node.app_key)
else:
srv =SSHApi(**node_data["ssh_conf"])
data, err = srv.get_net_work()
if err:
node.error_num += 1
node.error = {
"msg": err,
"time": int(time.time())
}
if node.error_num >= 2:
node.status = 0
ServerMonitorRepo().remove_cache(node.id)
ServerNodeDB().update_node(node)
else:
if node.error_num > 0:
node.error_num = 0
node.error = {}
node.status = 1
ServerNodeDB().update_node(node)
if data:
repo = ServerMonitorRepo()
repo.save_server_status(node.id, data)
if repo.is_reboot_wait(node.server_ip):
repo.set_wait_reboot(node.server_ip, False)
if not node_data["ssh_test"] and not node_data["ssh_conf"] and not isinstance(srv, SSHApi):
ssh_key, err = srv.read_ssh_key()
if err:
# public.print_log("Failed to obtain SSH key:{}".format(err))
return
port = srv.get_sshd_port()
if not port:
# public.print_log("Failed to obtain SSH service port")
return
conf = {
"host": srv.node_server_ip,
"pkey": ssh_key,
"port": port,
"username": "root",
"password": "",
"pkey_passwd": "",
}
err = test_ssh_config(**conf)
if err:
# public.print_log("SSH key test failed:{}".format(err))
ServerNodeDB().set_node_ssh_conf(node.id, {}, ssh_test=1)
return
else:
ServerNodeDB().set_node_ssh_conf(node.id, conf, ssh_test=1)
except Exception as e:
if public.is_debug():
public.print_error()
def monitor_node_once_with_timeout(node_data: dict, timeout: int = 5):
if node_data["app_key"] == "local" and node_data["api_key"] == "local":
return
from mod.project.node.dbutil import Node, ServerNodeDB, ServerMonitorRepo
from mod.project.node.nodeutil.ssh_wrap import SSHApi
try:
node_data["error"] = json.loads(node_data["error"])
node_data["ssh_conf"] = json.loads(node_data["ssh_conf"])
node, err = Node.from_dict(node_data)
if err:
# public.print_log("Node data parsing error:{}".format(err))
return
if node_data["app_key"] or node_data["api_key"]:
node_data["timeout"] = timeout
srv = ServerNode.new_by_data(node_data)
node_data.pop("timeout")
elif node_data["ssh_conf"]:
srv = SSHApi(**node_data["ssh_conf"], timeout=timeout)
else:
return
data, err = srv.get_net_work()
if err:
node.error_num += 1
node.error = {
"msg": err,
"time": int(time.time())
}
if node.error_num >= 2:
node.status = 0
ServerMonitorRepo().remove_cache(node.id)
ServerNodeDB().update_node(node)
elif node.error_num > 0:
node.error_num = 0
node.error = {}
node.status = 1
ServerNodeDB().update_node(node)
if data:
repo = ServerMonitorRepo()
repo.save_server_status(node.id, data)
if repo.is_reboot_wait(node.server_ip):
repo.set_wait_reboot(node.server_ip, False)
except Exception as e:
if public.is_debug():
public.print_error()
class LocalNode(ServerNode):
is_local = True
def __init__(self):
super().__init__("", "", "")
def create_php_site(self, site_name: str, port: int = 80, **kwargs) -> Tuple[Optional[int], str]:
path = "/www/wwwroot/{}".format(site_name)
webname = {
"domain": site_name if port in (80, 443) else "{}:{}".format(site_name, port),
"domainlist": [],
"count": 0
}
pdata = {
"path": path,
"ftp": "false",
"type": "PHP",
"type_id": "0",
"ps": kwargs.get("ps") if kwargs.get("ps", None) else "{}【Load balancing site】".format(site_name),
"port": str(port),
"version": "00",
"sql": "false",
"webname": json.dumps(webname)
}
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
from panelSite import panelSite
res = panelSite().AddSite(public.to_dict_obj(pdata))
if "siteId" in res:
return res["siteId"], ""
if "status" in res and not res["status"]:
return 0, res.get("msg", "Unknown error")
return 0, "Unknown error"
@staticmethod
def site_proxy_list(site_name: str) -> List[dict]:
from mod.base.web_conf.proxy import RealProxy
data = RealProxy(config_prefix="").get_proxy_list(public.to_dict_obj({
"sitename": site_name
}))
if isinstance(data, list) and data:
return data
from panelSite import panelSite
data = panelSite().GetProxyList(public.to_dict_obj({
"sitename": site_name
}))
if isinstance(data, list) and data:
return data
return []
def show_name(self) -> str:
return "Local node"
def php_site_list(self) -> Tuple[List[dict], str]:
from mod.base.web_conf.ssl import RealSSLManger
ssl_m = RealSSLManger()
all_sites = public.M("sites").where("project_type=? and status = 1", "PHP").select()
res = []
for i in all_sites:
domain = public.M("domain").where("pid=?", i["id"]).select()
domains = list(set([x["name"] for x in domain]))
port = list(set([int(x["port"]) for x in domain]))
ssl = ssl_m.get_site_ssl_info(i["name"]) is not None
if ssl and 443 not in port:
port.append(443)
res.append({
"site_id": i["id"],
"site_name": i["name"],
"ports": port,
"domains": domains,
"ssl": ssl
})
return res, ""
def add_domain(self, site_id: int, site_name: str, domain: str, port: int) -> Tuple[bool, str]:
from panelSite import panelSite
res = panelSite().AddDomain(public.to_dict_obj({
"domain": "{}:{}".format(domain, port),
"webname": site_name,
"id": str(site_id)
}))
if isinstance(res, dict) and res.get("status", False):
return True, ""
return False, res.get("msg", "Unknown error")
def has_domain(self, site_id: int, domain: str):
return public.M("domain").where("pid=? and name=?", site_id, domain).count() > 0
def dir_walk(self, path: str) -> Tuple[List[dict], str]:
if not os.path.isdir(path):
return [], "{} Not a directory".format(path)
res_file = []
count = 0
empty_dir = []
for root, dirs, files in os.walk(path):
if not files:
empty_dir.append(root)
for f in files:
if count > 1000:
return [], "The number of directory files exceeds 1000, please compress before operating"
count += 1
try:
res_file.append({
"path": os.path.join(root, f),
"size": os.path.getsize(os.path.join(root, f)),
"is_dir": 0
})
except:
pass
return [{"path": d, "size": 0, "is_dir": 1} for d in empty_dir] + res_file, ""
def remove_file(self, filename: str, is_dir: bool) -> dict:
from files_v2 import files
if is_dir:
return files().DeleteDir(public.to_dict_obj({"path": filename}))
else:
return files().DeleteFile(public.to_dict_obj({"path": filename}))
def file_list(self, path: str, p: int, row: int, search: str) -> Tuple[Dict, str]:
from files_v2 import files
return files().GetDirNew(public.to_dict_obj({
"path": path,
"p": p,
"showRow": row,
"search": search
})), ""
def upload_proxy(self):
from files_v2 import files
return files().upload(args=public.to_dict_obj({}))
def upload_check(self, target_file_list: List[str]) -> Tuple[List[dict], str]:
from files_v2 import files
return files().upload_files_exists(args=public.to_dict_obj({
"files": "\n".join(target_file_list),
})), ""
def dir_size(self, path: str) -> Tuple[Optional[int], str]:
return public.get_path_size(path), ""
def get_sshd_port(self) -> Optional[int]:
return public.get_sshd_port()
def create_dir(self, path: str) -> Tuple[Optional[Dict], str]:
from files_v2 import files
return files().CreateDir(public.to_dict_obj({"path": path})), ""
def get_file_body(self, path: str) -> Tuple[Optional[str], str]:
res = public.readFile(path)
if isinstance(res, str):
return res, ""
return None, "fail to read file"
def read_ssh_key(self) -> Tuple[Optional[str], str]:
from ssh_security import ssh_security
data = ssh_security().get_key(None)
if data["status"]:
return data["msg"], ""
return None, data["msg"]
def get_dir(self, path: str, search: str, disk: str):
from files_v2 import files
return files().GetDir(public.to_dict_obj({"path": path, "search":search, "disk": disk}))
class LPanelNode(ServerNode):
_TIME_REGEXP = re.compile(r":\d{2}(?P\.\d{6,})[+-]\d{2}:\d{2}")
def __init__(self, address: str, api_key: str, lpver: str = "v2", timeout: int = 20):
super().__init__(address, api_key, "", timeout=timeout)
self.ver = lpver
self.lpc = OnePanelApiClient(address, api_key, lpver, self.timeout)
@classmethod
def check_api_key(cls, node: Node) -> str:
# public.print_log(node.address, node.api_key)
lpc = OnePanelApiClient(node.address, node.api_key)
if lpc.test_ver():
node.lpver = lpc.ver
return ""
return "1 Panel node test connection failed"
def test_conn(self) -> str:
if self.lpc.test_ver():
return ""
return "1 Panel node test connection failed"
def get_net_work(self) -> Tuple[Optional[dict], str]:
"""{
"code": 200,
"message": "",
"data": {
"path": "/",
"name": "/",
"user": "root",
"group": "root",
"uid": "0",
"gid": "0",
"extension": "",
"content": "",
"size": 4096,
"isDir": true,
"isSymlink": false,
"isHidden": false,
"linkPath": "",
"type": "",
"mode": "0555",
"mimeType": "",
"updateTime": "0001-01-01T00:00:00Z",
"modTime": "2023-12-16T18:21:10.369198018+08:00",
"items": [],
"itemTotal": 22,
"favoriteID": 0,
"isDetail": true
}
}
API Response Status: 200
{
"code": 200,
"message": "",
"data": {
"uptime": 8060394,
"timeSinceUptime": "2025-03-05 10:02:04",
"procs": 173,
"load1": 1.05,
"load5": 1.05,
"load15": 1.05,
"loadUsagePercent": 17.5,
"cpuPercent": [
100,
0,
0,
0
],
"cpuUsedPercent": 25.000000232830644,
"cpuUsed": 1.0000000093132257,
"cpuTotal": 4,
"memoryTotal": 1038336000,
"memoryAvailable": 298635264,
"memoryUsed": 572526592,
"memoryUsedPercent": 55.13885601577909,
"swapMemoryTotal": 4160745472,
"swapMemoryAvailable": 3816017920,
"swapMemoryUsed": 344727552,
"swapMemoryUsedPercent": 8.285235285836778,
"ioReadBytes": 0,
"ioWriteBytes": 0,
"ioCount": 0,
"ioReadTime": 0,
"ioWriteTime": 0,
"diskData": [
{
"path": "/",
"type": "xfs",
"device": "/dev/mapper/centos-root",
"total": 37688381440,
"free": 9805086720,
"used": 27883294720,
"usedPercent": 73.98379461954417,
"inodesTotal": 18411520,
"inodesUsed": 729745,
"inodesFree": 17681775,
"inodesUsedPercent": 3.963523924151836
},
{
"path": "/www/monitor_data",
"type": "ext4",
"device": "/dev/vdb1",
"total": 10432565248,
"free": 8118497280,
"used": 1760526336,
"usedPercent": 17.82085360286682,
"inodesTotal": 655360,
"inodesUsed": 10029,
"inodesFree": 645331,
"inodesUsedPercent": 1.530303955078125
}
],
"netBytesSent": 0,
"netBytesRecv": 0,
"gpuData": null,
"xpuData": null,
"shotTime": "2025-06-06T17:01:59.156659566+08:00"
}
}"""
try:
data = self.lpc.system_status()
system_data = data.get("data", {})
return {
"cpu": [round(system_data["cpuUsedPercent"], 2), system_data["cpuTotal"]],
"mem": {
"memRealUsed": system_data["memoryUsed"] / 1024 / 1024,
"memTotal": system_data["memoryTotal"] / 1024 / 1024,
"memNewTotal": public.to_size(system_data["memoryTotal"])
},
"version": "1Panel"
}, ""
except Exception as e:
public.print_error()
return None, str(e)
def get_tmp_token(self) -> Tuple[Optional[str], str]:
return None, "1Panel does not support temporary API access"
def php_site_list(self) -> Tuple[List[dict], str]:
"""{
"code": 200,
"message": "",
"data": [
{
"id": 2,
"createdAt": "2025-06-06T14:49:00.687860227+08:00",
"updatedAt": "2025-06-06T14:49:00.687860227+08:00",
"protocol": "HTTP",
"primaryDomain": "www.halotest.com",
"type": "deployment",
"alias": "www.halotest.com",
"remark": "",
"status": "Running",
"httpConfig": "",
"expireDate": "9999-12-31T00:00:00Z",
"proxy": "127.0.0.1:8090",
"proxyType": "",
"errorLog": true,
"accessLog": true,
"defaultServer": false,
"IPV6": false,
"rewrite": "",
"webSiteGroupId": 1,
"webSiteSSLId": 0,
"runtimeID": 0,
"appInstallId": 5,
"ftpId": 0,
"parentWebsiteID": 0,
"user": "",
"group": "",
"dbType": "",
"dbID": 0,
"favorite": false,
"domains": [
{
"id": 2,
"createdAt": "2025-06-06T14:49:00.69011631+08:00",
"updatedAt": "2025-06-06T14:49:00.69011631+08:00",
"websiteId": 2,
"domain": "www.halotest.com",
"ssl": false,
"port": 7080
},
{
"id": 3,
"createdAt": "2025-06-06T14:51:47.932141706+08:00",
"updatedAt": "2025-06-06T14:51:47.932141706+08:00",
"websiteId": 2,
"domain": "qwww.com",
"ssl": false,
"port": 7080
}
],
"webSiteSSL": {
"id": 0,
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"primaryDomain": "",
"privateKey": "",
"pem": "",
"domains": "",
"certURL": "",
"type": "",
"provider": "",
"organization": "",
"dnsAccountId": 0,
"acmeAccountId": 0,
"caId": 0,
"autoRenew": false,
"expireDate": "0001-01-01T00:00:00Z",
"startDate": "0001-01-01T00:00:00Z",
"status": "",
"message": "",
"keyType": "",
"pushDir": false,
"dir": "",
"description": "",
"skipDNS": false,
"nameserver1": "",
"nameserver2": "",
"disableCNAME": false,
"execShell": false,
"shell": "",
"acmeAccount": {
"id": 0,
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"email": "",
"url": "",
"type": "",
"eabKid": "",
"eabHmacKey": "",
"keyType": "",
"useProxy": false,
"caDirURL": ""
},
"dnsAccount": {
"id": 0,
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"name": "",
"type": ""
},
"websites": null
},
"errorLogPath": "",
"accessLogPath": "",
"sitePath": "",
"appName": "",
"runtimeName": "",
"runtimeType": "",
"siteDir": ""
}
]
}"""
try:
data = self.lpc.get_websites()
res = []
for i in data["data"]:
res.append({
"site_id": i["id"],
"site_name": i["alias"],
"ports": list(set([i["port"] for i in i["domains"]])),
"domains": list(set([i["domain"] for i in i["domains"]])),
"ssl": any(i["ssl"] for i in i["domains"])
})
return res, ""
except Exception as e:
return [], str(e)
def create_php_site(self, site_name: str, port: int, **kwargs) -> Tuple[Optional[int], str]:
try:
dat = self.lpc.add_website(site_name, port, **kwargs)
# public.print_log(dat)
if dat["code"] == 200:
time.sleep(0.5)
site_id = self.lpc.check_site_create(site_name)
if isinstance(site_id, int):
return site_id, ""
return None, "Website creation failed"
return None, dat["message"]
except Exception as e:
return None, str(e)
def set_firewall_open(self, port: int, protocol: str = "tcp") -> Tuple[bool, str]:
try:
dat = self.lpc.open_port(port, protocol)
if dat["code"] == 200:
return True, ""
return False, dat["message"]
except Exception as e:
return False, str(e)
def add_domain(self, site_id: int, site_name: str, domain: str, port: int) -> Tuple[bool, str]:
try:
dat = self.lpc.add_website_domain(site_id, domain, port)
if dat["code"] == 200:
return True, ""
return False, dat["message"]
except Exception as e:
return False, str(e)
def has_domain(self, site_id: int, domain: str) -> bool:
try:
dat = self.lpc.website_domains(site_id)
if dat["code"] == 200:
for i in dat["data"]:
if i["domain"] == domain:
return True
return False
return False
except Exception as e:
return False
def target_file_exits(self, target_file: str) -> Tuple[bool, str]:
data = self.lpc.files_exits([target_file])
if not isinstance(data, dict):
return False, "Request file: {} status failed".format(target_file)
for i in data["data"]:
if i["path"] == target_file:
return True, ""
return False, ""
def upload_dir_check(self, target_dir: str) -> str:
data = self.lpc.files_exits([target_dir])
if not isinstance(data, dict):
return "Request file: {} status failed".format(target_dir)
for i in data["data"]:
if i["path"] == target_dir:
if not i.get("isDir", True):
return "The name path is not a directory"
return ""
return ""
def _upload_big_file(self, filename: str, target_path: str, upload_name: str,
call_log: Callable[[int, str], None] = None) -> str:
try:
fb = open(filename, 'rb')
except Exception as e:
public.print_error()
return "File {} failed to open, please check file permissions, error message is:{}".format(filename, str(e))
file_size = os.path.getsize(filename)
count = math.ceil(file_size / (1024 * 1024 * 5))
idx = 0
for i in range(0, file_size, 1024 * 1024 * 5):
file_data = fb.read(1024 * 1024 * 5)
err, data = self.lpc.chunkupload(upload_name, target_path, file_data, idx, count)
idx += 1
if err:
return "Upload file {} failed with error message:{}".format(filename, str(err))
if data:
call_log(100, "File upload:{} -> {},Upload successful".format(filename, upload_name))
else:
up_d = (i + len(file_data)) // file_size
call_log(up_d, "File upload:{} -> {},The uploaded size is:{}".format(
filename, upload_name, public.to_size(i + len(file_data))))
return ""
def _upload_little_file(self, filename: str, target_path: str, upload_name: str,
call_log: Callable[[int, str], None] = None) -> str:
return self.lpc.upload(filename, target_path, upload_name)
def download_file(self, filename: str, target_path: str, mode: str,
call_log: Callable[[int, str], None] = None) -> str:
target_file = os.path.join(target_path, os.path.basename(filename))
exits = os.path.exists(target_file)
if exits and mode == "ignore":
call_log(0, "File Download:{} -> {},The target file already exists, skip download".format(filename, target_file))
return ""
if exits and mode == "rename":
download_name = "{}_{}".format(os.path.basename(filename), public.md5(filename))
call_log(0, "File Download:{} -> {},The target file already exists, it will be renamed to {}".format(filename, target_file, download_name))
else:
download_name = os.path.basename(filename)
return self.lpc.download_file(filename, target_path, download_name, call_log=call_log)
def dir_walk(self, path: str) -> Tuple[List[dict], str]:
return self.lpc.dir_walk(path)
def upload_proxy(self):
try:
from YakPanel import request, cache
f_name = request.form.get('f_name')
f_path = request.form.get('f_path')
f_size = request.form.get('f_size')
f_start = request.form.get('f_start')
cache_key = "upload_file_{}_{}_{}".format(f_name, f_path, f_size)
num = cache.get(cache_key)
if num is None:
num = 0
cache.set(cache_key, num, 86400)
else:
num = int(num)
cache.set(cache_key, num + 1, 86400)
blob_file: FileStorage = request.files.getlist('blob')[0]
file_data = blob_file.read()
next_size = int(f_start) + len(file_data)
if next_size == int(f_size):
chunk_num = num + 1
else:
chunk_num = num + 2
err, data = self.lpc.chunkupload(f_name, f_path, file_data, num, chunk_num)
if err:
return {"status": False, "msg": "Upload file failed with error message:{}".format(str(err))}
elif data is None:
return str(next_size)
elif isinstance(data, dict) and data["code"] == 200:
return {"status": True, "msg": "Upload successful"}
return {"status": False, "msg": "Upload file failed with error message:{}".format(data["message"])}
except Exception as e:
public.print_error()
return {"status": False, "msg": "Upload file failed with error message:{}".format(str(e))}
def remove_file(self, filename: str, is_dir: bool) -> dict:
try:
res = self.lpc.remove_file(filename, is_dir)
if isinstance(res, dict):
return {"status": res["code"] == 200, "msg": res["message"]}
return {"status": False, "msg": "File deletion failed, remote request failed"}
except Exception as e:
return {"status": False, "msg": "Failed to delete file, error message is:{}".format(str(e))}
def file_list(self, path: str, p: int, row: int, search: str) -> Tuple[Dict, str]:
try:
res, err = self.lpc.files_search(path, p, row, search)
if err:
return {}, "Failed to retrieve file list, error message is:{}".format(err)
count = res["itemTotal"]
path = res["path"]
dirs = []
files = []
sub_items = [] if res["items"] is None else res["items"]
for i in sub_items:
# res = self._TIME_REGEXP.findall(i["modTime"])
# public.print_log(res)
mt_str = self._TIME_REGEXP.sub(lambda x: x.group().replace(x.group("err"), ""), i["modTime"])
for f_str in ("%Y-%m-%dT%H:%M:%S%z", "%Y-%m-%dT%H:%M:%S.%f%z"):
try:
mt = datetime.strptime(mt_str, f_str)
break
except:
pass
# public.print_log(i["modTime"], mt_str)
# public.print_error()
else:
continue
if i["isDir"]:
dirs.append({
"nm": html.unescape(i["name"]),
"sz": i["size"],
"mt": int(mt.timestamp()),
"acc": i["mode"][1:],
"user": i["user"],
"lnk": "" if not i["linkPath"] else " -> " + i["linkPath"],
"durl": "",
"cmp": 0,
"fav": "0",
"rmk": "",
"top": 0,
"sn": i["name"]
})
else:
files.append({
"nm": html.unescape(i["name"]),
"sz": i["size"],
"mt": int(mt.timestamp()),
"acc": i["mode"][1:],
"user": i["user"],
"lnk": "" if not i["linkPath"] else " -> " + i["linkPath"],
"durl": "",
"cmp": 0,
"fav": "0",
"rmk": "",
"top": 0,
"sn": i["name"]
})
return {
"path": path,
"page": public.get_page(count, p, row)["page"],
"dir": dirs,
"files": files
}, ""
except Exception as e:
return {}, "Failed to retrieve file list, error message is:{}".format(str(e))
def download_proxy(self, filename: str):
return self.lpc.download_proxy(filename)
def upload_check(self, target_file_list: List[str]) -> Tuple[List[dict], str]:
try:
res = self.lpc.files_exits(target_file_list)
if res is None:
return [], "Request for remote 1Panel failed"
data = res.get("data", [])
res_data = []
for i in data:
try:
mt = datetime.strptime(i["modTime"], "%Y-%m-%dT%H:%M:%S%z")
except:
try:
mt = datetime.strptime(i["modTime"], "%Y-%m-%dT%H:%M:%S.%f%z")
except:
continue
res_data.append({
'filename': i["path"],
'exists': True,
'size': i["size"],
'mtime': int(mt.timestamp()),
'isfile': False
})
return res_data, ""
except Exception as e:
return [], "Request for remote 1Panel failed with error message:{}".format(str(e))
def dir_size(self, path: str) -> Tuple[Optional[int], str]:
try:
res = self.lpc.dir_size(path)
if res is None:
return None, "Request for remote 1Panel failed"
if not isinstance(res, dict):
return None, "Request for remote 1Panel failed, response data:%s" % str(res)
return res["data"].get("size", 0), ""
except Exception as e:
return None, "Request for remote 1Panel failed: %s" % str(e)
def get_sshd_port(self) -> Optional[int]:
try:
data = self.lpc.get_sshd_config()
return int(data["port"]) if data else None
except:
return None
def create_dir(self, path: str) -> Tuple[Optional[Dict], str]:
try:
data = self.lpc.create_dir(path)
if not data:
return None, "Failed to create directory, 1Panel node connection failed"
status = data["code"] == 200
return {"status": status, "msg": data["message"] if not status else "Created successfully"}, ""
except Exception as e:
return None, "Request for remote 1Panel node failed: %s" % str(e)
def restart_bt_panel(self) -> Dict[str, Any]:
self.lpc.restart_panel()
return {"status": True, "msg": "Restart successful"}
def server_reboot(self) -> Dict[str, Any]:
self.lpc.server_reboot()
from mod.project.node.dbutil import ServerMonitorRepo
repo = ServerMonitorRepo()
repo.set_wait_reboot(self.node_server_ip, True)
def wait_for_reboot():
# wait 等待服务器重启成功, 超时时间默认为 10 分钟
wait_for = time.time() + 600
time.sleep(3)
while time.time() < wait_for:
if self.test_conn() == "": # 无错误时表示重启成功
repo.set_wait_reboot(self.node_server_ip, False)
from mod.project.node.dbutil import ServerNodeDB
node_data = ServerNodeDB().find_node(api_key=self.api_key, app_key=self.app_key.to_string())
if node_data:
monitor_node_once(node_data)
return {"status": True, "msg": "Server restart successful"}
time.sleep(3)
# public.print_log("Waiting for server restart... {}".format(wait_for - time.time()))
repo.set_wait_reboot(self.node_server_ip, False)
return {"status": False, "msg": "Restarting server failed, server information has not been detected for more than 10 minutes"}
t = threading.Thread(target=wait_for_reboot, daemon=True)
t.start()
return {"status": True, "msg": "The server restart has started, please wait patiently for the successful restart"}
def get_file_body(self, path: str) -> Tuple[Optional[str], str]:
data_dict, err = self.lpc.get_file_body(path)
if err != "":
return None, err
return data_dict["content"], ""
def read_ssh_key(self) -> Tuple[Optional[str], str]:
key, err = self.lpc.get_file_body("/root/.ssh/id_ed25519_1panel")
if err:
return None, "Failed to read SSH key, error message is: %s" % err
return key, ""