# -*- coding: utf-8 -*- # +------------------------------------------------------------------- # | YakPanel # +------------------------------------------------------------------- # | Copyleft (c) 2015-2099 YakPanel(www.yakpanel.com) All lefts reserved. # +------------------------------------------------------------------- # | Author: wzz # | email : wzz@yakpanel.com # +------------------------------------------------------------------- # +------------------------------------------------------------------- # | docker app 管理模型 - # +------------------------------------------------------------------- import json import os.path import sys import time import traceback if "/www/server/panel/class" not in sys.path: sys.path.append('/www/server/panel/class') import public from mod.project.docker.app.base import App class AppManage(App): def __init__(self): super(AppManage, self).__init__() # 2023/12/4 下午 5:12 获取首次安装的初始化配置 def get_install_conf(self, get): ''' @name 获取首次安装的初始化配置 @author wzz <2023/12/4 下午 3:17> @param 参数名<数据类型> 参数描述 @return 数据类型 ''' try: self.get_app_json() if self.app_json is None: return {} self.app_json["field"][-2]["default"] = public.GetRandomString(16) return self.app_json except: return public.return_message(-1, 0, public.lang("Failed to obtain the installation configuration, please try to uninstall and reinstall!")) # 2023/12/4 下午 5:12 创建app def create_app(self, get): ''' @name 创建app @author wzz <2023/12/4 下午 4:40> @param 参数名<数据类型> 参数描述 @return 数据类型 ''' get.app_name = get.get("app_name", None) if get.app_name is None: return public.return_message(-1, 0, public.lang("Parameter error, please pass app_name parameter")) self.set_app_name(get.app_name) self.get_app_json() self.set_app_type() if get.get('gpu', 'false') == 'true': self.set_app_template_path("{}_gpu".format(self.app_name)) else: self.set_app_template_path(self.app_name) self.check_app_template(get) from btdockerModelV2 import setupModel as ds if not ds.main().get_service_status(): return public.return_message(-1, 0, public.lang("The docker service has not been started, please start the docker service first!")) if not ds.main().check_docker_service(): return public.return_message(-1, 0, public.lang("The docker service was not installed successfully, please install the docker service first!")) ccpres = self.check_compose_params(get) if ccpres["status"] == -1: return ccpres self.set_service_name(get.service_name) get.row = 10000 installed_apps = self.get_installed_apps(get)['message'] if isinstance(installed_apps, dict): self.installed_apps = installed_apps['data'] if isinstance(installed_apps, list): self.installed_apps = installed_apps # todo installed_apps适配新返回 if installed_apps and installed_apps.get('data', []): for app in installed_apps["data"]: if app["appname"] == "frps" and self.app_name == "frps": return public.return_message(-1, 0, public.lang("The frps application already exists, please do not install it again!")) if app["appname"] == "frpc" and self.app_name == "frpc": return public.return_message(-1, 0, public.lang("The frpc application already exists, please do not install it again!")) if app["service_name"] == self.service_name: return public.return_message(-1, 0, public.lang("The same service name already exists: {}, please change another name",self.service_name)) # 2024/8/6 上午11:11 检查依赖应用是否安装 if not self.app_json["depend"] is None: check_len = 0 depend_len = len(self.app_json["depend"]) for depend_app in self.app_json["depend"]: for app in installed_apps["data"]: if app["service_name"] == self.service_name: return public.return_message(-1, 0, public.lang("The same service name already exists: {}, please change another name",self.service_name)) if app["appname"] in depend_app["appname"] and app["m_version"] in tuple(depend_app["appversion"]): check_len += 1 break if check_len != depend_len: return public.return_message(-1, 0, "Please install dependent applications first: {}".format(",".join(["{}:{}".format(i["appname"], "/".join(i["appversion"])) for i in self.app_json["depend"]]))) else: if not self.app_json["depend"] is None: return public.return_message(-1, 0, "Please install dependent applications first: {}".format(",".join(["{}:{}".format(i["appname"], "/".join(i["appversion"])) for i in self.app_json["depend"]]))) if len(get.site_domains) > 0: for domain in get.site_domains: if not public.is_domain(domain): return public.return_message(-1, 0, public.lang("Domain name [{}] format is incorrect",domain)) newpid = public.M('domain').where("name=? and port=?", (domain, 80)).getField('pid') if newpid: result = public.M('sites').where("id=?", (newpid,)).find() if result: return public.return_message(-1, 0, 'Project type [{}] already exists with domain name: {}, please do not add it again!'.format( result['project_type'], domain)) self.apply_installed_app_template() self.set_app_path() get.app_path = self.app_path self.set_service_path() self.set_cmd_log() if "scripts" in self.app_json.keys(): self.app_scripts = self.app_json["scripts"] cbnet = self.check_yakpanel_net() if cbnet["status"] == -1: return cbnet if not self.check_yml(): return public.return_message(-1, 0, public.lang("Initialization {} failed. It was detected that the docker-compose.yml file does not exist. Please uninstall and reinstall and try again.",self.app_name)) self.set_compose_file() public.ExecShell("chmod -R 755 {}/{}".format(self.app_path,self.service_name)) public.ExecShell("echo -n > {}".format(self.app_cmd_log)) self.app_info = [ { "fieldKey": "service_name", "fieldTitle": "name", "fieldValue": self.service_name, }, { "fieldKey": "app_name", "fieldTitle": "app name", "fieldValue": self.app_json["apptitle"], }, { "fieldKey": "app_type", "fieldTitle": "app type", "fieldValue": self.app_json["appTypeCN"], } ] ucres = self.update_conf(get) if ucres["status"] == -1: return ucres # 2023/12/5 上午 9:48 写入安装后的配置文件 # self._write_conf(get) # 2024/8/8 上午11:32 设置启动命令行 cmd = ("nohup echo 'The name is starting, it may take more than 1-5 minutes...' >> {app_cmd_log};" "docker-compose -f {compose_file} up -d >> {app_cmd_log} 2>&1 && " "echo 'bt_successful' >> {app_cmd_log} || echo 'bt_failed' >> {app_cmd_log} &" .format( app_cmd_log=self.app_cmd_log, compose_file=self.compose_file, )) from mod.project.docker.app.gpu.tools import GPUTool if get.get('gpu', 'false') == 'true' and not GPUTool.is_install_ctk(): cmd = ("nohup echo 'The name is starting, it may take more than 1-5 minutes...' >> {app_cmd_log};" "{install_ctk};" "docker-compose -f {compose_file} up -d >> {app_cmd_log} 2>&1 && " "echo 'bt_successful' >> {app_cmd_log} || echo 'bt_failed' >> {app_cmd_log} &" .format( app_cmd_log=self.app_cmd_log, install_ctk=GPUTool.ctk_install_cmd(self.app_cmd_log), compose_file=self.compose_file, )) self.set_up_cmd(cmd) # 2024/8/7 上午11:05 处理复杂一些的应用程序配置 clres = self.set_complex_conf(get) if clres["status"] == -1: return clres get.app_info = self.app_info get.app_info.append({ "fieldKey": "installed_log", "fieldTitle": "installed log", "fieldValue": self.app_cmd_log, }) get.app_info.append({ "fieldKey": "gpu", "fieldTitle": "Whether to turn on the GPU", "fieldValue": True if get.get('gpu', 'false') == 'true' else False }) # 2023/12/5 上午 9:48 启动应用 self.up_app() create_result = {"status": 0} if len(get.site_domains) > 0: create_result = self.create_proxy(get) self.write_installed_json(get) if self.app_name in ("mysql", "mariadb"): get.type = "mysql" self.apply_database_to_panel(get) elif self.app_name in ("postgresql"): get.type = "pgsql" self.apply_database_to_panel(get) public.set_module_logs('dkapp_{}'.format(self.app_name), 'create_app', 1) public.set_module_logs('dkapp', 'create_app', 1) if create_result["status"] == -1: return public.return_message(0, 0, public.lang("The application was created successfully, but the reverse proxy creation failed. Error details: {}",create_result["message"])) return public.return_message(0, 0, public.lang("The application is created successfully, waiting for the application to initialize, which may take 1-5 minutes...")) # 2023/12/4 下午 10:03 写入安装后的配置文件 def _write_conf(self, get): ''' @name 写入安装后的配置文件 @author wzz <2023/12/4 下午 10:04> @param 参数名<数据类型> 参数描述 @return 数据类型 ''' self.set_user_conf_file() public.writeFile(self.user_conf_file, json.dumps(get.__dict__)) install_conf = self.get_install_conf(get) for field in install_conf['field']: field['default'] = getattr(get, field["attr"]) self.set_app_conf_file() public.writeFile(self.app_conf_file, json.dumps(install_conf)) # 2023/12/5 上午 11:44 更新.env文件和用户默认配置文件 def update_conf(self, get): ''' @name 更新.env文件和用户默认配置文件 @author wzz <2023/12/5 上午 11:44> @param 参数名<数据类型> 参数描述 @return 数据类型 ''' try: get.depend_app = get.get("depend_app", None) if type(get.depend_app) == str: get.depend_app = json.loads(get.depend_app) get.port_list = [] get.app_db = None get.db_host = None get.depdbtype = None get.depmidtype = None get.cache_db_host = None envs = None if self.app_json["apptype"] == "MCP" : server_path,command,envs = self.update_mcp_conf(get) self.app_json["env"].append({"key": "server_path","type": "string","default": "","desc": "服务路径"}) self.app_json["env"].append({"key": "command","type": "string","default": "","desc": "启动命令"}) get.server_path = server_path get.command = command if os.path.isdir("/etc/timezone"): public.ExecShell("rm -rf /etc/timezone") if not os.path.isfile("/etc/timezone"): public.writeFile("/etc/timezone", "Asia/Shanghai") for ap_json in self.app_json["env"]: if ap_json["type"] == "port": get.c_port = None get.c_port = get.get(ap_json["key"], None) if get.c_port is None: return public.return_message(-1, 0, public.lang("Parameter error, please pass: {} ",ap_json["key"])) for installed in self.installed_apps: if get.c_port in installed["port"]: return public.return_message(-1, 0, public.lang("Port [{}] is already used by [{}], please change to another port.",get.c_port, installed["service_name"])) cpres = self.check_port(get) if cpres["status"] == -1: return cpres get.port_list.append(get.c_port) elif ap_json["type"] == "password": key_name = ap_json["key"] random_pass = public.GetRandomString(16) setattr(get, key_name, get.get(key_name, random_pass)) elif ap_json["type"] == "db_host": if get.depend_app is None: return public.return_message(-1, 0, public.lang("Please select the database you need to connect to!")) if get.db_host is None: for depend_app in get.depend_app: if depend_app["appname"] in ("mysql", "mariadb", "mongodb", "postgresql"): get.db_host = depend_app["service_name"] get.depdbtype = depend_app["appname"] break setattr(get, ap_json["key"], get.db_host) elif ap_json["type"] == "cache_db_host": if get.cache_db_host is None or get.depmidtype is None: if get.depend_app is None: return public.return_message(-1, 0, public.lang("Please select the cache you want to connect to!")) for depend_app in get.depend_app: if depend_app["appname"] == "redis" or depend_app["appname"] == "memcached": get.cache_db_host = depend_app["service_name"] get.depmidtype = depend_app["appname"] break setattr(get, ap_json["key"], get.cache_db_host) elif ap_json["type"] == "redis_password": if get.depend_app is None: return public.return_message(-1, 0, public.lang("Please select the cache you want to connect to!")) for depend_app in get.depend_app: if depend_app["appname"] == "redis" or depend_app["appname"] == "memcached": get.cache_db_host = depend_app["service_name"] get.depmidtype = depend_app["appname"] break args = public.dict_obj() args.service_name = get.cache_db_host appinfo = self.get_installed_app_info_j(args) if appinfo["status"] == -1: return public.return_message(-1, 0, public.lang("Cache information not found")) for api in appinfo["data"]: if api["fieldKey"] == "redis_password": setattr(get, "redis_password", api["fieldValue"]) break elif ap_json["type"] == "database": get.app_db = getattr(get, ap_json["key"]) elif ap_json["type"] == "username": get.db_username = getattr(get, ap_json["key"]) elif ap_json["type"] in ("mysql_password", "mariadb_password"): mysql_password = public.GetRandomString(16) if not hasattr(get, ap_json["key"]): get.db_password = mysql_password else: get.db_password = getattr(get, ap_json["key"]) for depend_app in get.depend_app: if depend_app["appname"] in ("mysql", "mariadb"): get.db_host = depend_app["service_name"] get.depdbtype = depend_app["appname"] break if not get.db_host is None: create_res = self.create_database(get) if create_res["status"] == -1: return create_res setattr(get, ap_json["key"], get.db_password) elif ap_json["type"] in ("pgsql_password"): pgsql_password = public.GetRandomString(16) if not hasattr(get, ap_json["key"]): get.db_password = pgsql_password else: get.db_password = getattr(get, ap_json["key"]) for depend_app in get.depend_app: if depend_app["appname"] in ("postgresql"): get.db_host = depend_app["service_name"] get.depdbtype = depend_app["appname"] break if not get.db_host is None: create_res = self.create_pgsql_database(get) if create_res["status"] == -1: return create_res setattr(get, ap_json["key"], get.db_password) elif ap_json["type"] in ("defaultUserName", "defaultPassWord"): setattr(get, ap_json["key"], ap_json["default"]) elif ap_json["type"] == "domain_host": if len(get.site_domains) > 0: setattr(get, ap_json["key"], get.site_domains[0]) else: setattr(get, ap_json["key"], public.GetLocalIp()) elif ap_json["type"] == "server_ip": if not hasattr(get, ap_json["key"]) or getattr(get, ap_json["key"]) is None or getattr(get, ap_json["key"]) == "": setattr(get, ap_json["key"], public.GetLocalIp()) if ap_json["key"] == "app_path": get.app_path = self.service_path public.ExecShell("sed -i 's,^APP_PATH=.*,APP_PATH={app_path},' {app_path}/.env".format( app_path=self.service_path)) elif ap_json["type"] == "url": public.ExecShell("sed -i 's,^{field_attr}=.*,{field_attr}={get_parm},' {service_path}/.env".format( field_attr=ap_json["key"].upper(), get_parm=getattr(get, ap_json["key"]), service_path=self.service_path)) elif ap_json["key"] == "memory_limit": public.ExecShell("sed -i 's/^{field_attr}=.*/{field_attr}={get_parm}MB/' {service_path}/.env".format( field_attr=ap_json["key"].upper(), get_parm=getattr(get, ap_json["key"]), service_path=self.service_path)) else: get_parm = getattr(get, ap_json["key"]).replace('/', '\\/') public.ExecShell("sed -i 's/^{field_attr}=.*/{field_attr}={get_parm}/' {service_path}/.env".format( field_attr=ap_json["key"].upper(), get_parm=get_parm, service_path=self.service_path)) if not ap_json["key"] in ("version", "host_ip"): if ap_json["key"] in ("cpus", "memory_limit") and int(getattr(get, ap_json["key"])) == 0: self.app_info.append({ "fieldKey": ap_json["key"], "fieldTitle": ap_json["desc"], "fieldValue": "No limit", }) else: self.app_info.append({ "fieldKey": ap_json["key"], "fieldTitle": ap_json["desc"], "fieldValue": getattr(get, ap_json["key"]), }) for volume in self.app_json["volumes"].keys(): if self.app_json["volumes"][volume]["type"] == "path": if os.path.exists(os.path.join(self.app_template_path, volume)): public.ExecShell("cp -r {}/{} {}/{}".format(self.app_template_path, volume, self.service_path, volume)) else: public.ExecShell("mkdir -p {}".format(self.service_path + "/{}".format(volume))) public.ExecShell("chmod -R 777 {}".format(self.service_path + "/{}".format(volume))) if self.app_json["volumes"][volume]["type"] == "file": public.ExecShell("\cp -r {}/{} {}/{}".format(self.app_template_path, volume, self.service_path, volume)) if self.app_json["appname"] == "mysql": if get.m_version == "5": command = "--character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --explicit_defaults_for_timestamp=true --lower_case_table_names=1" if "5" in get.s_version: command = "--character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --lower_case_table_names=1" if not os.path.exists(os.path.join(self.service_path, "my.cnf")): public.ExecShell("\cp -r {}/my5.cnf {}/my.cnf".format(self.app_template_path, self.service_path)) elif get.m_version == "9": command = "" if not os.path.exists(os.path.join(self.service_path, "my.cnf")): public.ExecShell("\cp -r {}/my9.cnf {}/my.cnf".format(self.app_template_path, self.service_path)) else: command = "--default-authentication-plugin=mysql_native_password" if get.m_version == "8" and "4" in get.s_version: command = "" if not os.path.exists(os.path.join(self.service_path, "my.cnf")): public.ExecShell("\cp -r {}/my8.cnf {}/my.cnf".format(self.app_template_path, self.service_path)) public.ExecShell("sed -i 's/^COMMAND=.*/COMMAND={}/' {}/.env".format(command, self.service_path)) if get.get("editcompose","") != "": public.WriteFile(self.service_path+"/docker-compose.yml",get.editcompose) public.ExecShell("sed -i 's/BT_SERVICE_NAME6/{}/g' {}/*.yml".format(self.service_name, self.service_path)) if envs is not None: self.append_compose_environment(self.service_path+"/docker-compose.yml",self.service_name,envs) return public.return_message(0, 0, public.lang("The updated configuration is successful")) except Exception as e: return public.return_message(-1, 0, public.lang("Failed to update configuration: {}",str(e))) def update_mcp_conf(self,get): """ MCP 应用的配置更新 in: get.mcp_server_config { "mcpServers": { "amap-maps": { "command": "npx", "args": [ "-y", "@amap/amap-maps-mcp-server" ], "env": { "AMAP_MAPS_API_KEY": "您在高德官网上申请的key", "XXXX":"XXXX" } } } } out: SSE_PATH: "/amap-maps" COMMAND: "npx -y @amap/amap-maps-mcp-server" ENVS: { "AMAP_MAPS_API_KEY": "您在高德官网上申请的key", "XXXX":"XXXX" } """ mcp_server_config = json.loads(get.get("mcp_server_config")) if not mcp_server_config: return public.return_message(-1, 0, public.lang("MCP server misconfiguration")) if not isinstance(mcp_server_config, dict): return public.return_message(-1, 0, public.lang("MCP server misconfiguration")) if "mcpServers" not in mcp_server_config: return public.return_message(-1, 0, public.lang("MCP server misconfiguration")) if not isinstance(mcp_server_config.get("mcpServers"), dict): return public.return_message(-1, 0, public.lang("MCP server misconfiguration")) # 获取 mcpServers 中的第一个服务器名称 server_name = list(mcp_server_config['mcpServers'].keys())[0] # 构建 SSE_PATH server_path = "/{server_path}".format(server_path=server_name) # 获取服务器配置 server_config = mcp_server_config['mcpServers'][server_name] # 构建 COMMAND command = server_config['command'] args = " ".join(server_config['args']) command = "{command} {args}".format(command=command, args=args) envs = server_config.get('env',None) return server_path, command, envs def append_compose_environment(self,file_path, service_name, new_env): """ 设置compose文件中的env @param file_path compose完整路径 @param service_name 要修改env的services @new_env env环境 {'NEW_VAR': 'new_value', 'ANOTHER_VAR': 'another_value'} @reutrn true false """ if len(new_env.keys()) == 0: return public.return_message(0, 0, "") with open(file_path, 'r') as file: lines = file.readlines() # 找到服务的起始行 service_start = -1 for i, line in enumerate(lines): if line.strip().startswith(service_name + ':'): service_start = i break if service_start == -1: return public.return_message(-1, 0, public.lang("No service found [{}] ", service_name)) # 找到服务块的结束行 service_end = len(lines) for i in range(service_start + 1, len(lines)): if lines[i].strip() and not lines[i].startswith(' '): service_end = i break # 找到 environment: 的位置 env_line = -1 for i in range(service_start, service_end): if lines[i].strip() == 'environment:': env_line = i break # 确定缩进(与服务块对齐,通常为两个空格) indent = ' ' # 默认缩进 for i in range(service_start + 1, service_end): if lines[i].strip(): indent = lines[i][:lines[i].index(lines[i].strip())] break # 准备要插入的环境变量行 env_lines = ["{indent} {key}: {value}\n".format(indent = indent,key=key, value=value) for key, value in new_env.items()] # 插入新环境变量 if env_line != -1: # 在 environment: 的下一行插入 lines[env_line + 1:env_line + 1] = env_lines else: # 在 image: 的上一行插入 environment: 和新环境变量 image_line = -1 for i in range(service_start, service_end): if lines[i].strip().startswith('image:'): image_line = i break if image_line == -1: return public.return_message(-1, 0, public.lang("[{}] No found image", service_name)) lines[image_line:image_line] = [f"{indent}environment:\n"] + env_lines with open(file_path, 'w') as file: file.writelines(lines) return public.return_message(0, 0, "") # 2024/8/7 上午11:08 处理负责的app配置更新 def set_complex_conf(self, get): if self.app_name in ("frps", "frpc"): return self.set_frp_conf(get) # elif self.app_name == "alist": # return self.set_alist_conf(get) elif self.app_name == "nocodb": return self.set_nocodb_conf(get) elif self.app_name == "homeassistant": return self.set_homeassistant_conf(get) elif self.app_name == "openvpn": return self.set_openvpn_conf(get) elif self.app_name == "deepseek_r1": if self.app_scripts is None: return public.return_message(-1, 0, public.lang("The script file for deepseek_r1 was not found")) # 2025/2/8 10:07 获取当前内存大小,如果剩余可用内存不足1550mb,就不能部署 import psutil memory = psutil.virtual_memory() if memory.available < 1550 * 1024 * 1024: return public.return_message(-1, 0, public.lang("Less than 1550 MB of memory prevents deepseek r 1 from being deployed")) return self.set_deepseek_r1_conf(get) elif self.app_type == "MCP": server_path = self.find_field_value(self.app_info,"server_path") self.app_info.append({ "fieldKey": "local_server_url", "fieldTitle": "Intranet URL", "fieldValue": "http://{}:{}{}".format(public.get_local_ip(), get.c_port,server_path), }) self.app_info.append({ "fieldKey": "public_server_url", "fieldTitle": "Public website URL", "fieldValue": "http://{}:{}{}".format(public.get_server_ip(), get.c_port,server_path), }) return public.return_message(0, 0, public.lang("No processing required")) # 2025/2/5 17:22 处理deepseek_r1的配置 def set_deepseek_r1_conf(self, get): ''' @name 处理deepseek_r1的配置 ''' from mod.project.docker.app.gpu.tools import GPUTool command = self.app_scripts["command"].format(get.version) if get.get('gpu', 'false') == 'true' and not GPUTool.is_install_ctk(): cmd = ("nohup echo 'It is starting, and it may take more than 1-5 minutes to wait...' >> {app_cmd_log};" "{ctk_install_cmd};" "docker-compose -f {compose_file} up -d >> {app_cmd_log} 2>&1 && " "echo 'Wait for the Ollama service to start...' >> {app_cmd_log} && " "until curl -sSf http://localhost:{ollama_port}/api/tags; do sleep 2; done && " "echo 'Start the model deepseek-r1...' >> {app_cmd_log} && " "docker-compose -f {compose_file} exec -it ollama {command} >> {app_cmd_log} 2>&1 && " "docker-compose -f {compose_file} restart >> {app_cmd_log} 2>&1 && " "echo 'bt_successful' >> {app_cmd_log} || echo 'bt_failed' >> {app_cmd_log} &" .format( app_cmd_log=self.app_cmd_log, ctk_install_cmd=GPUTool.ctk_install_cmd(self.app_cmd_log), compose_file=self.compose_file, ollama_port=get.ollama_port, command=command, )) else: cmd = ("nohup echo 'It is starting, and it may take more than 1-5 minutes to wait' >> {app_cmd_log};" "docker-compose -f {compose_file} up -d >> {app_cmd_log} 2>&1 && " "echo 'Wait for the Ollama service to start...' >> {app_cmd_log} && " "until curl -sSf http://localhost:{ollama_port}/api/tags; do sleep 2; done && " "echo 'Start the model deepseek-r1...' >> {app_cmd_log} && " "docker-compose -f {compose_file} exec -it ollama {command} >> {app_cmd_log} 2>&1 && " "docker-compose -f {compose_file} restart >> {app_cmd_log} 2>&1 && " "echo 'bt_successful' >> {app_cmd_log} || echo 'bt_failed' >> {app_cmd_log} &" .format( app_cmd_log=self.app_cmd_log, compose_file=self.compose_file, ollama_port=get.ollama_port, command=command, )) self.set_up_cmd(cmd) return public.return_message(0, 0, public.lang("deepseek_r1 was successfully configured")) # 2024/8/7 上午11:06 处理frp/s/c配置的更新 def set_frp_conf(self, get): ''' @name 处理frp/s/c配置的更新 ''' if self.app_name == "frps": frp_conf = "{}/data/frps.toml".format(self.service_path) if not os.path.exists(frp_conf): if os.path.exists(os.path.join(self.app_template_path, "frps.toml")): public.ExecShell( "\cp -r {}/frps.toml {}/data/frps.toml".format(self.app_template_path, self.service_path)) public.ExecShell("rm -f {}/frps.toml".format(self.service_path)) elif self.app_name == "frpc": frp_conf = "{}/data/frpc.toml".format(self.service_path) if not os.path.exists(frp_conf): if os.path.exists(os.path.join(self.app_template_path, "frpc.toml")): public.ExecShell( "\cp -r {}/frpc.toml {}/data/frpc.toml".format(self.app_template_path, self.service_path)) public.ExecShell("rm -f {}/frpc.toml".format(self.service_path)) else: return public.return_message(-1, 0, public.lang("Unknown Applications: {}",self.app_name)) if not os.path.exists(frp_conf): return public.return_message(-1, 0, public.lang("{} profile not detected: {}",self.app_name, frp_conf)) frp_conf_content = public.readFile(frp_conf) if self.app_name == "frps": frp_conf_content = frp_conf_content.replace("bindPort = 7000", "bindPort = {}".format(get.frps_server_port)) frp_conf_content = frp_conf_content.replace("vhostHTTPPort = 40800", "vhostHTTPPort = {}".format(get.frps_http_port)) frp_conf_content = frp_conf_content.replace("vhostHTTPSPort = 40443", "vhostHTTPSPort = {}".format(get.frps_https_port)) frp_conf_content = frp_conf_content.replace("webServer.port = 7500", "webServer.port = {}".format(get.frps_web_port)) frp_conf_content = frp_conf_content.replace("webServer.user = \"\"", "webServer.user = \"{}\"".format(get.frps_user)) frp_conf_content = frp_conf_content.replace("webServer.password = \"\"", "webServer.password = \"{}\"".format(get.frps_password)) else: frp_conf_content = frp_conf_content.replace("serverPort = 7000", "serverPort = {}".format(get.frps_server_port)) frp_conf_content = frp_conf_content.replace("serverAddr = \"127.0.0.1\"", "serverAddr = \"{}\"".format(get.frps_server_ip)) frp_conf_content = frp_conf_content.replace("webServer.port = 7400", "webServer.port = {}".format(get.frpc_web_port)) frp_conf_content = frp_conf_content.replace("webServer.user = \"\"", "webServer.user = \"{}\"".format(get.frpc_user)) frp_conf_content = frp_conf_content.replace("webServer.password = \"\"", "webServer.password = \"{}\"".format(get.frpc_password)) public.writeFile(frp_conf, frp_conf_content) if int(get.allow_access) == 1: # 2024/4/18 上午10:21 添加端口到系统防火墙 from firewallModelV2.comModel import main as comModel firewall_com = comModel() if self.app_name == "frps": get.port = get.frps_server_port firewall_com.set_port_rule(get) get.port = get.frps_http_port firewall_com.set_port_rule(get) get.port = get.frps_https_port firewall_com.set_port_rule(get) get.port = get.frps_web_port firewall_com.set_port_rule(get) else: get.port = get.frpc_web_port firewall_com.set_port_rule(get) return public.return_message(0, 0, public.lang("The {} configuration was successfully updated",self.app_name)) # 2024/8/8 上午11:21 处理alist的密码 def set_alist_conf(self, get): ''' @name 处理alist的密码 ''' alist_password = public.GetRandomString(10) self.app_info.append({ "fieldKey": "alist_user", "fieldTitle": "Alist account", "fieldValue": "admin", }) self.app_info.append({ "fieldKey": "alist_password", "fieldTitle": "Alist password", "fieldValue": alist_password, }) # 2024/8/8 上午11:32 重新设置启动命令行 cmd = ("nohup echo 'Starting up, you may need to wait for more than 1-5 minutes.' >> {app_cmd_log};" "docker-compose -f {compose_file} up -d >> {app_cmd_log} 2>&1 && " "docker-compose -f {compose_file} exec -it {service_name} ./alist admin set {alist_password} >> {app_cmd_log} 2>&1 && " "echo 'bt_successful' >> {app_cmd_log} || echo 'bt_failed' >> {app_cmd_log} &" .format( service_name=self.service_name, alist_password=alist_password, app_cmd_log=self.app_cmd_log, compose_file=self.compose_file, )) self.set_up_cmd(cmd) return public.return_message(0, 0, public.lang("The Alist configuration was updated")) # 2024/8/22 下午5:33 处理nocodb的数据库连接 def set_nocodb_conf(self, get): ''' @name 处理nocodb的数据库连接 ''' if get.depend_app is None: return public.return_message(-1, 0, public.lang("Please select the database you want to connect to!")) if get.db_host is None: for depend_app in get.depend_app: if depend_app["appname"] in ("mysql", "mariadb", "mongodb", "postgresql"): get.db_host = depend_app["service_name"] get.depdbtype = depend_app["appname"] break public.ExecShell("sed -i 's/^DB_HOST=.*/DB_HOST={}/' {}/.env".format(get.db_host, self.service_path)) get.app_db = get.db_name if get.depdbtype in ("mysql", "mariadb"): create_res = self.create_database(get) if create_res["status"] == -1: return create_res public.ExecShell("sed -i 's/^DB_PORT=.*/DB_PORT=3306/' {}/.env".format(self.service_path)) public.ExecShell("sed -i 's/^DB_TYPE=.*/DB_TYPE=mysql2/' {}/.env".format(self.service_path)) elif get.depdbtype in ("postgresql"): create_res = self.create_pgsql_database(get) if create_res["status"] == -1: return create_res public.ExecShell("sed -i 's/^DB_PORT=.*/DB_PORT=5432/' {}/.env".format(self.service_path)) public.ExecShell("sed -i 's/^DB_TYPE=.*/DB_TYPE=pg/' {}/.env".format(self.service_path)) else: return public.return_message(-1, 0, public.lang("Unknown database type: {}",get.depdbtype)) return public.return_message(0, 0, public.lang("The Nocodb configuration was updated")) # 2024/8/23 下午4:43 处理homeassistant配置 def set_homeassistant_conf(self, get): ''' @name 处理homeassistant ''' get.c_port = 8123 for installed in self.installed_apps: if get.c_port in installed["port"]: return public.return_message(-1, 0, public.lang("Port [{}] is already used by [{}], please change to another port.",get.c_port, installed["service_name"])) cpres = self.check_port(get) if cpres["status"] == -1: return cpres if int(get.allow_access) == 1: # 2024/4/18 上午10:21 添加端口到系统防火墙 from firewallModelV2.comModel import main as comModel firewall_com = comModel() get.port = get.c_port firewall_com.set_port_rule(get) get.port_list.append(get.c_port) self.app_info.append({ "fieldKey": "web_http_port", "fieldTitle": "Web http port", "fieldValue": 8123, }) self.app_info.append({ "fieldKey": "access_url", "fieldTitle": "access url", "fieldValue": "http://{}:{}".format(public.GetLocalIp(), get.c_port), }) return public.return_message(0, 0, public.lang("Updated Homeassistant configuration successfully")) # 2024/8/28 下午12:13 设置openvpn前置处理 def set_openvpn_conf(self, get): ''' @name ''' cmd = ("nohup echo 'Starting up, you may need to wait for more than 1-5 minutes.' >> {app_cmd_log};" "docker pull kylemanna/openvpn >> {app_cmd_log} 2>&1;" "docker run -v {service_path}/openvpn:/etc/openvpn --rm kylemanna/openvpn ovpn_genconfig -u udp://{server_ip} >> {app_cmd_log} 2>&1;" "docker run -v {service_path}/openvpn:/etc/openvpn --rm -e EASYRSA_BATCH=1 -e EASYRSA_REQ_CN=OpenVPN_Server kylemanna/openvpn ovpn_initpki nopass >> {app_cmd_log} 2>&1;" "docker run -v {service_path}/openvpn:/etc/openvpn --rm -e EASYRSA_BATCH=1 -e EASYRSA_REQ_CN=OpenVPN_Server kylemanna/openvpn easyrsa build-client-full {service_name} nopass >> {app_cmd_log} 2>&1;" "docker run -v {service_path}/openvpn:/etc/openvpn --rm kylemanna/openvpn ovpn_getclient {service_name} > {service_path}/{service_name}.ovpn;" "docker-compose -f {compose_file} up -d >> {app_cmd_log} 2>&1 && " "echo 'bt_successful' >> {app_cmd_log} || echo 'bt_failed' >> {app_cmd_log} &" ).format( service_path=self.service_path, server_ip=get.ovpn_server_url, service_name=self.service_name, app_cmd_log=self.app_cmd_log, compose_file=self.compose_file, ) self.set_up_cmd(cmd) return public.return_message(0, 0, public.lang("Updated Openvpn configuration successfully")) def rebuild_mysql_database(self, get): try: get.db_host = get.service_name get.c_port = None get.mysql_root_password = None get.type = "mysql" # 获取已安装的mysql应用信息 installed_json = self.read_json(self.installed_json_file) # 直接找到符合条件的 MySQL 应用 target_app = next( (i for app_list in installed_json.values() for i in app_list if i["service_name"] == get.service_name and i["appname"] == "mysql"), None ) if target_app: # 找到 MySQL 应用 # 获取端口 get.c_port = target_app["port"][0] # 直接获取 mysql_root_password get.mysql_root_password = next( (info["fieldValue"] for info in target_app["appinfo"] if info["fieldKey"] == "mysql_root_password"), None ) db_sid = self.get_database_sid(get) if not db_sid: self.apply_database_to_panel(get) except Exception as e: public.print_log(str(e)) # 2024/8/1 下午10:12 重建指定app def rebuild_app(self, get): ''' @name 重建指定app ''' get.service_name = get.get("service_name", None) if get.service_name is None: return public.return_message(-1, 0, public.lang("Please pass the service_name parameter")) get.app_name = get.get("app_name", None) if get.app_name is None: return public.return_message(-1, 0, public.lang("Please pass the app_name parameter")) self.set_service_name(get.service_name) self.set_app_name(get.app_name) self.set_app_path() self.set_service_path() self.set_compose_file() command = self.set_type(0).set_path(self.compose_file).get_compose_down() public.ExecShell(command) command = self.set_type(0).set_path(self.compose_file).get_compose_up_remove_orphans() public.ExecShell(command) # 处理数据库容器,点击重建 解决远程数据库不存在的问题 if get.app_name == "mysql": self.rebuild_mysql_database(get) return public.return_message(0, 0, public.lang("Rebuilt successfully!")) # 2024/8/2 上午11:40 获取指定app支持升级的版本 def update_versions(self, get): ''' @name 获取指定app支持升级的版本 ''' get.id = get.get("id", None) if get.id is None: return public.return_message(-1, 0, public.lang("Please pass the id parameter")) installed_json = self.read_json(self.installed_json_file) if not installed_json: return public.return_message(0, 0, []) self.get_apps_json() canupdate_version = [] for app_type in installed_json.keys(): for i in installed_json[app_type]: if i["id"] == get.id: if i["m_version"] in ("main", "latest"): return public.return_message(0, 0, []) for app in self.apps_json: if app["appname"] == i["appname"]: for version in app["appversion"]: if version["m_version"] == i["m_version"]: for sv in version["s_version"]: if "." in sv: c_v = float(sv) else: c_v = int(sv) if "." in i["s_version"]: i_v = float(i["s_version"]) else: i_v = int(i["s_version"]) if c_v > i_v: canupdate_version.append({ "m_version": version["m_version"], "s_version": sv, "version": "{}.{}".format(version["m_version"], sv) }) if len(canupdate_version) == 0: return public.return_message(0, 0, []) return public.return_message(0, 0, canupdate_version) return public.return_message(-1, 0, public.lang("The specified app was not found!")) # 2024/8/1 下午4:20 更新指定app def update_app(self, get): ''' @name 更新指定app ''' installed_json = self.read_json(self.installed_json_file) if not installed_json: return public.return_message(-1, 0, public.lang("No installed apps yet!")) get.id = get.get("id", None) if get.id is None: return public.return_message(-1, 0, public.lang("Please pass the id parameter!")) get.m_version = get.get("m_version", None) if get.m_version is None: return public.return_message(-1, 0, public.lang("please pass the m_version parameter!")) get.s_version = get.get("s_version", None) if get.s_version is None: return public.return_message(-1, 0, public.lang("please pass the s_version parameter!")) self.app_version = "{}.{}".format(get.m_version, get.s_version) get.backup = get.get("backup", False) if type(get.backup) != bool: if get.backup == "false": get.backup = False else: get.backup = True if get.backup: self.backup_app(get) for app_type in installed_json.keys(): for i in installed_json[app_type]: if i["id"] == get.id: if i["m_version"] != get.m_version: return public.return_message(-1, 0, public.lang("You can only choose the same version number as the major version for updating!")) if "." in i["s_version"]: if float(i["s_version"]) == float(get.s_version): return public.return_message(-1, 0, public.lang("The current application is already the latest version and does not need to be updated!")) if float(i["s_version"]) > float(get.s_version): return public.return_message(-1, 0, public.lang("You can only choose a version number higher than the original version to update!")) else: if int(i["s_version"]) == int(get.s_version): return public.return_message(-1, 0, public.lang("The current application is already the latest version and does not need to be updated!")) if int(i["s_version"]) > int(get.s_version): return public.return_message(-1, 0, public.lang("You can only choose a version number higher than the original version to update!")) self.set_service_name(i["service_name"]) self.set_app_name(i["appname"]) self.set_app_path() self.set_service_path() self.set_compose_file() self.down_app() get.pull = get.get("pull", False) if get.pull: self.pull_app() public.ExecShell("sed -i 's/^VERSION=.*/VERSION={}/' {}/.env".format(self.app_version, self.service_path)) command = self.set_type(0).set_path(self.compose_file).get_compose_up_remove_orphans() public.ExecShell(command) i["m_version"] = get.m_version i["s_version"] = get.s_version i["version"] = self.app_version i["updateat"] = int(time.time()) self.write_json(self.installed_json_file, installed_json) return public.return_message(0, 0, public.lang("The update was successful!")) return public.return_message(-1, 0, public.lang("The specified app was not found!!")) # 2024/8/2 下午4:09 忽略指定app的更新 def ignore_update(self, get): ''' @name 忽略指定app的更新 ''' pass # 2024/8/6 上午9:07 获取依赖应用安装情况 def get_dependence_apps(self, get): ''' @name 获取依赖应用安装情况 ''' get.depend_app = get.get("depend_app", None) if get.depend_app is None: return public.return_message(-1, 0, public.lang("please pass the depend_app parameter!")) if type(get.depend_app) == str: get.depend_app = json.loads(get.depend_app) installed_json = self.read_json(self.installed_json_file) if not installed_json: return public.return_message(0, 0, []) installed_databases = [] for depend_app in get.depend_app: tmp = {"appname": depend_app["app_name"], "app_type": depend_app["app_type"], "installed": []} for app_type in installed_json.keys(): if app_type == depend_app["app_type"]: for i in installed_json[app_type]: if i["appname"] == depend_app["app_name"]: installed_dep = { "service_name": i["service_name"], "version": i["version"] } if installed_dep not in tmp["installed"]: tmp["installed"].append(installed_dep) installed_databases.append(tmp) del tmp return self.pageResult(True, data=installed_databases) # 2024/8/5 下午4:03 获取指定app的日志 def get_app_log(self, get): ''' @name 获取指定app的日志 ''' get.service_name = get.get("service_name", None) if get.service_name is None: return public.return_message(-1, 0, public.lang("please pass the service_name parameter!")) get.app_name = get.get("app_name", None) if get.app_name is None: return public.return_message(-1, 0, public.lang("please pass the app_name parameter!")) self.set_service_name(get.service_name) self.set_app_name(get.app_name) self.set_app_path() self.set_service_path() self.set_compose_file() command = self.set_type(0).set_path(self.compose_file).set_tail("500").get_tail_compose_log() stdout, stderr = public.ExecShell(command) return public.return_message(0, 0, stdout) # 2024/8/6 下午4:14 获取指定应用的安装日志 def get_app_installed_log(self, get): ''' @name 获取指定应用的安装日志 ''' get.service_name = get.get("service_name", None) if get.service_name is None: return public.return_message(-1, 0, public.lang("please pass the service_name parameter!")) self.set_service_name(get.service_name) self.set_cmd_log() if not os.path.exists(self.app_cmd_log): return public.return_message(0, 0,"") return public.return_message(0, 0, public.readFile(self.app_cmd_log)) # 2024/8/1 下午4:22 备份指定应用 def backup_app(self, get): ''' @name 备份指定应用 ''' installed_json = self.read_json(self.installed_json_file) if not installed_json: return public.return_message(-1, 0, public.lang("No installed apps yet!")) get.id = get.get("id", None) if get.id is None: return public.return_message(-1, 0, public.lang("Please pass the id parameter!")) for app_type in installed_json.keys(): for i in installed_json[app_type]: if i["id"] == get.id: self.set_service_name(i["service_name"]) self.set_service_backup_path() backup_conf = self.backup_conf.copy() backup_conf["backup_path"] = self.service_backup_path backup_conf["backup_time"] = str(int(time.time())) backup_conf["file_name"] = "{}_{}.tar.gz".format(i["service_name"], backup_conf["backup_time"]) if not os.path.exists(self.service_backup_path): os.makedirs(self.service_backup_path, exist_ok=True, mode=0o755) public.ExecShell("cp -r {} {}".format(os.path.join(i["path"], i["service_name"]), self.service_backup_path)) public.ExecShell("cd {service_backup_path} && tar -zcf {file_name} {i_name} ".format( service_backup_path=self.service_backup_path, file_name=backup_conf["file_name"], i_name=i["service_name"])) if os.path.exists("{}/{}".format(self.service_backup_path, backup_conf["file_name"])): if (os.path.getsize("{}/{}".format(self.service_backup_path, backup_conf["file_name"])) == 0 or os.path.getsize("{}/{}".format(self.service_backup_path, backup_conf["file_name"])) < 10): public.ExecShell("rm -f {}/{}".format(self.service_backup_path, backup_conf["file_name"])) return public.return_message(-1, 0, public.lang("Backup failed!")) else: return public.return_message(-1, 0, public.lang("Backup failed!")) public.ExecShell("rm -rf {}".format(os.path.join(self.service_backup_path, i["service_name"]))) backup_conf["size"] = os.path.getsize("{}/{}".format(self.service_backup_path, backup_conf["file_name"])) backup_json = self.read_json(self.backup_json_file) if backup_json: if i["id"] in backup_json.keys(): backup_json[i["id"]].append(backup_conf) else: backup_json[i["id"]] = [backup_conf] else: backup_json = {i["id"]: [backup_conf]} self.write_json(self.backup_json_file, backup_json) return public.return_message(0, 0, public.lang("Backup successful!")) return public.return_message(-1, 0, public.lang("The specified app was not found!!")) # 2024/8/1 下午6:05 获取备份列表 def get_backup_list(self, get): ''' @name 获取备份列表 ''' backup_json = self.read_json(self.backup_json_file) if not backup_json: page_data = self.get_page([], get) return self.pageResult(True, data=page_data["data"], page=page_data["page"]) get.id = get.get("id", None) if get.id is None: return public.return_message(-1, 0, public.lang("Please pass the id parameter!")) if not get.id in backup_json.keys(): page_data = self.get_page([], get) return self.pageResult(True, data=page_data["data"], page=page_data["page"]) page_data = self.get_page(backup_json[get.id], get) # 2025/1/6 15:20 时间倒序 page_data["data"] = sorted(page_data["data"], key=lambda x: x["backup_time"], reverse=True) return self.pageResult(True, data=page_data["data"], page=page_data["page"]) # 2024/8/1 下午6:19 删除备份 def delete_backup(self, get): ''' @name 删除备份 ''' backup_json = self.read_json(self.backup_json_file) if not backup_json: return public.return_message(-1, 0, public.lang("There is no backup data yet!")) get.id = get.get("id", None) if get.id is None: return public.return_message(-1, 0, public.lang("Please pass the id parameter!")) get.file_name = get.get("file_name", None) if get.file_name is None: return public.return_message(-1, 0, public.lang("please pass the file_name parameter!")) if not get.id in backup_json.keys(): return public.return_message(-1, 0, public.lang("The specified app was not found!!")) for i in backup_json[get.id]: if i["file_name"] == get.file_name: public.ExecShell("rm -f {}/{}".format(i["backup_path"], i["file_name"])) backup_json[get.id].remove(i) self.write_json(self.backup_json_file, backup_json) return public.return_message(0, 0, public.lang("The deletion is successful!")) return public.return_message(-1, 0, public.lang("The specified backup was not found!")) # 2024/8/1 下午8:31 恢复备份 def restore_backup(self, get): ''' @name 恢复备份 ''' backup_json = self.read_json(self.backup_json_file) if not backup_json: return public.return_message(-1, 0, public.lang("There is no backup data yet!")) get.id = get.get("id", None) if get.id is None: return public.return_message(-1, 0, public.lang("Please pass the id parameter!")) installed_json = self.read_json(self.installed_json_file) if not installed_json: return public.return_message(-1, 0, public.lang("No installed apps yet!")) get.file_name = get.get("file_name", None) if get.file_name is None: return public.return_message(-1, 0, public.lang("Please pass the file_name parameter!")) get.backup = get.get("backup", False) if type(get.backup) != bool: if get.backup == "false": get.backup = False else: get.backup = True if get.backup: self.backup_app(get) for i in backup_json[get.id]: if not os.path.exists("{}/{}".format(i["backup_path"], i["file_name"])): return public.return_message(-1, 0, public.lang("The backup file does not exist!")) if os.path.getsize("{}/{}".format(i["backup_path"], i["file_name"])) < 10: return public.return_message(-1, 0, public.lang("The backup file is abnormal!")) for app_type in installed_json.keys(): for j in installed_json[app_type]: if j["id"] == get.id: self.set_service_name(j["service_name"]) self.service_path = os.path.join(j["path"], j["service_name"]) self.set_app_name(j["appname"]) self.set_compose_file() self.down_app() public.ExecShell("rm -rf {}".format(self.service_path)) public.ExecShell("cp -r {}/{} {}".format(i["backup_path"], get.file_name, j["path"])) public.ExecShell("cd {} && tar -zxf {}".format(j["path"], get.file_name)) self.start_app(get) return public.return_message(0, 0, public.lang("Recovery successful!")) return public.return_message(-1, 0, public.lang("App does not exist!")) return public.return_message(-1, 0, public.lang("The specified backup was not found!")) # 2024/8/1 下午9:25 上传备份文件到备份目录 def upload_backup(self, get): ''' @name 上传备份文件到备份目录 ''' get.id = get.get("id", None) if get.id is None: return public.return_message(-1, 0, public.lang("Please pass the id parameter!")) get.f_path = get.get("f_path/s", None) if get.f_path is None: return public.return_message(-1, 0, public.lang("Please pass the f_path parameter!")) get.f_name = get.get("f_name/s", None) if get.f_name is None: return public.return_message(-1, 0, public.lang("Please pass the f_name parameter!")) get.file_name = get.f_name get.f_size = get.get("f_size/s", None) if get.f_size is None: return public.return_message(-1, 0, public.lang("Please pass the f_size parameter!")) get.f_start = get.get("f_start/s", 0) get.blob = get.get("blob/s", None) if get.blob is None: return public.return_message(-1, 0, public.lang("Please pass the blob parameter!")) get.restore_backup = get.get("restore_backup", False) if type(get.restore_backup) != bool: if get.restore_backup == "false": get.restore_backup = False else: get.restore_backup = True installed_json = self.read_json(self.installed_json_file) if not installed_json: return public.return_message(-1, 0, public.lang("No installed apps yet!")) if os.path.exists(os.path.join(get.f_path, get.f_name)): return public.return_message(-1, 0, public.lang("There is a backup file with the same name, if you need to upload it again, please delete the file with the same name!")) from files import files fileObj = files() backup_json = self.read_json(self.backup_json_file) for app_type in installed_json.keys(): for i in installed_json[app_type]: if i["id"].strip() == get.id.strip(): self.set_service_name(i["service_name"]) self.set_service_backup_path() backup_conf = self.backup_conf.copy() backup_conf["backup_path"] = self.service_backup_path backup_conf["backup_time"] = str(int(time.time())) backup_conf["file_name"] = get.f_name backup_conf["size"] = int(get.f_size) get.f_path = self.service_backup_path upload_result = fileObj.upload(get) if type(upload_result) == dict and not upload_result["status"]: return public.returnResult(status=False, msg=upload_result["msg"]) get.size = os.path.getsize(os.path.join(self.service_backup_path, get.f_name)) if get.size != int(get.f_size): return public.returnResult(status=False, msg="The upload file size is inconsistent, and the upload fails!") if not backup_json or not get.id in backup_json.keys(): backup_json = {get.id: [backup_conf]} self.write_json(self.backup_json_file, backup_json) if not get.restore_backup: return public.return_message(0, 0, public.lang("The backup file was uploaded successfully!")) else: return self.restore_backup(get) for i in backup_json[get.id]: if i["file_name"] == get.f_name: i["backup_time"] = str(int(time.time())) self.write_json(self.backup_json_file, backup_json) if not get.restore_backup: return public.return_message(0, 0, public.lang("The backup file was uploaded successfully!")) else: return self.restore_backup(get) else: backup_json[get.id].append(backup_conf) self.write_json(self.backup_json_file, backup_json) if not get.restore_backup: return public.return_message(0, 0, public.lang("The backup file was uploaded successfully!")) else: return self.restore_backup(get) return public.return_message(-1, 0, public.lang("App does not exist!")) # 2024/8/7 下午3:15 设置app状态 def set_app_status(self, get): ''' @name 设置app状态 ''' get.status = get.get("status", None) if get.status is None: return public.return_message(-1, 0, public.lang("Please pass the status parameter!")) if not get.status in ("start", "stop", "restart", "rebuild"): return public.return_message(-1, 0, public.lang("Please pass start/stop/restart/rebuild parameter!")) # 根据get.status调用对应的方法 if get.status == "start": return self.start_app(get) elif get.status == "stop": return self.stop_app(get) elif get.status == "restart": return self.restart_app(get) elif get.status == "rebuild": return self.rebuild_app(get) # 2024/8/1 下午3:40 停止指定compose服务 def down_app(self): ''' @name 停止指定compose服务 ''' command = self.set_type(0).set_path(self.compose_file).get_compose_down() public.ExecShell(command) # 2024/8/22 下午10:56 后台执行down_app def down_app_bg(self): ''' @name 后台执行down_app ''' command = self.set_type(0).set_path(self.compose_file).get_compose_down() public.ExecShell(command + " &") # 2024/8/1 下午3:33 卸载指定app def remove_app(self, get): ''' @name 卸载指定app ''' installed_json = self.read_json(self.installed_json_file) if not installed_json: return public.return_message(-1, 0, public.lang("No installed apps yet!")) get.id = get.get("id", None) if get.id is None: return public.return_message(-1, 0, public.lang("Please pass the id parameter!")) get.delete_data = get.get("delete_data", 0) for app_type in installed_json.keys(): for i in installed_json[app_type]: if i["id"] == get.id: self.compose_file = os.path.join(i["path"], i["service_name"], "docker-compose.yml") if i["appname"] == "sftpgo": self.down_app_bg() else: self.down_app() if int(get.delete_data) == 1: public.ExecShell("rm -rf {}".format(os.path.join(i["path"], i["service_name"]))) if not i["depDataBase"] is None: if i["depDataBase"]["type"] in ("mysql", "mariadb"): self.delete_database_for_app(i["depDataBase"]["db"]) elif i["depDataBase"]["type"] in ("postgresql", ): self.delete_pgsql_database_for_app(i["depDataBase"]["db"]) if i["appname"] in ("mysql", "mariadb", "postgresql"): self.set_service_name(i["service_name"]) for appInfo in i["appinfo"]: if appInfo["fieldKey"] == "mysql_port": get.c_port = appInfo["fieldValue"] elif appInfo["fieldKey"] == "mariadb_port": get.c_port = appInfo["fieldValue"] elif appInfo["fieldKey"] == "mysql_root_password": get.mysql_root_password = appInfo["fieldValue"] elif appInfo["fieldKey"] == "mariadb_root_password": get.mariadb_root_password = appInfo["fieldValue"] elif appInfo["fieldKey"] == "postgres_password": get.postgres_password = appInfo["fieldValue"] if i["appname"] == "postgresql": self.delete_pgsql_database_from_panel(get) else: self.delete_database_from_panel(get) if not i["domain"] is None: from mod.project.proxy.comMod import main as proxyMod pMod = proxyMod() get.site_name = i["domain"] res = public.M("sites").where("name=?", (get.site_name,)).find() if not res: public.M("domain").where("name=?", (i["domain"],)).delete() else: get.id = str(res["id"]) res = pMod.delete(get) if res["status"] == -1: return res installed_json[app_type].remove(i) self.write_json(self.installed_json_file, installed_json) return public.return_message(0, 0, public.lang("The uninstall was successful!")) return public.return_message(-1, 0, public.lang("The specified app was not found!!")) # 2024/8/15 下午4:33 清空所有已装应用 def remove_all_installed(self, get): ''' @name 清空所有已装应用 ''' installed_json = self.read_json(self.installed_json_file) if not installed_json: return public.return_message(-1, 0, public.lang("No installed apps yet!")) get.delete_data = get.get("delete_data", 0) for app_type in installed_json.keys(): for i in installed_json[app_type]: self.compose_file = os.path.join(i["path"], i["service_name"], "docker-compose.yml") if i["appname"] == "sftpgo": self.down_app_bg() else: self.down_app() if int(get.delete_data) == 1: public.ExecShell("rm -rf {}".format(os.path.join(i["path"], i["service_name"]))) if not i["depDataBase"] is None: if i["depDataBase"]["type"] in ("mysql", "mariadb"): self.delete_database_for_app(i["depDataBase"]["db"]) elif i["depDataBase"]["type"] in ("postgresql",): self.delete_pgsql_database_for_app(i["depDataBase"]["db"]) if i["appname"] in ("mysql", "mariadb", "postgresql"): self.set_service_name(i["service_name"]) for appInfo in i["appinfo"]: if appInfo["fieldKey"] == "mysql_port": get.c_port = appInfo["fieldValue"] elif appInfo["fieldKey"] == "mariadb_port": get.c_port = appInfo["fieldValue"] elif appInfo["fieldKey"] == "mysql_root_password": get.mysql_root_password = appInfo["fieldValue"] elif appInfo["fieldKey"] == "mariadb_root_password": get.mariadb_root_password = appInfo["fieldValue"] elif appInfo["fieldKey"] == "postgres_password": get.postgres_password = appInfo["fieldValue"] self.delete_database_from_panel(get) if not i["domain"] is None: from mod.project.proxy.comMod import main as proxyMod pMod = proxyMod() get.site_name = i["domain"] res = public.M("sites").where("name=?", (get.site_name,)).find() if not res: public.M("domain").where("name=?", (i["domain"],)).delete() else: get.id = str(res["id"]) res = pMod.delete(get) # if res["status"] == -1: return res public.ExecShell("rm -f {}".format(self.installed_json_file)) # 强制刷新app列表 def refresh_apps_list(self,): ''' @name 强制刷新app列表 ''' public.ExecShell("rm -f {}".format(self.apps_json_file)) public.ExecShell("rm -f {}".format(self.app_tags_file)) public.ExecShell("rm -rf {}".format(self.templates_path)) self.download_apps_json() self.update_ico() # 2024/8/2 下午4:47 获取app列表 def get_apps(self, get): ''' @name 获取app列表 ''' get.force = get.get("force/d", 0) if int(get.force) == 1: public.ExecShell("rm -f {}".format(self.apps_json_file)) public.ExecShell("rm -f {}".format(self.app_tags_file)) public.ExecShell("rm -rf {}".format(self.templates_path)) public.ExecShell("rm -f {}".format(self.ollama_online_models_file)) self.download_apps_json() self.update_ico() self.get_apps_json() if self.apps_json is None: return public.return_message(-1, 0, public.lang("Failed to obtain the application category, please click [Update Application List] in the upper right corner")) from mod.project.docker.app.base import App cbnet = App().check_yakpanel_net() if cbnet["status"] == -1: return cbnet get.app_type = get.get("app_type", "all") get.row = 20000 import re query = get.get("query", None) if query: pattern = re.compile( rf'.*{{}}.*'.format(re.escape(query)), flags=re.IGNORECASE ) else: # 无查询时直接匹配所有 pattern = re.compile(rf'^.*$', flags=re.IGNORECASE) installed_json = self.read_json(self.installed_json_file) app_list = [] from mod.project.docker.apphub.apphubManage import AppHub apphub_json = AppHub().get_hub_apps() self.apps_json = self.apps_json + apphub_json for app in self.apps_json: if app["appstatus"] == 0: continue app["installedCount"] = 0 if app["apptype"] == "AI": from mod.project.docker.app.gpu.tools import GPUTool GPUTool.register_app_gpu_option(app) if get.app_type in ("all", app["apptype"],"AppHub"): if(get.app_type == "AppHub" and app["appid"] != -1): continue # 模糊搜索检查 match = False if (pattern.search(app["appname"]) or pattern.search(app["apptitle"]) or pattern.search(app["appdesc"])): match = True if not match: continue # 不匹配则跳过 # 计算安装次数 if not app["reuse"] and installed_json: for i in installed_json[app["apptype"]]: if i["appname"] == app["appname"] and i["appid"] == app["appid"]: app["installedCount"] += 1 break elif installed_json: if app["apptype"] in installed_json: for i in installed_json[app["apptype"]]: if i["appname"] == app["appname"] and i["appid"] == app["appid"]: app["installedCount"] += 1 app_list.append(app) page_data = self.get_page(app_list, get) # 2023/12/6 下午 4:13 获取系统内存,转成MB,为最大可用内存 import psutil mem = psutil.virtual_memory() mem = int(mem.total / 1024 / 1024) cpu = psutil.cpu_count() return self.pageResult(True, msg='', data=page_data["data"], page=page_data["page"], mem=mem, cpu=cpu) # 2024/8/1 上午10:35 获取已安装的应用列表 def get_installed_apps(self, get): ''' @name 获取已安装的应用列表 ''' installed_json = self.read_json(self.installed_json_file) if not installed_json: return public.return_message(0, 0, []) get.app_type = get.get("app_type", "all") if get.app_type != "all" and not get.app_type in self.types: return public.return_message(-1, 0, public.lang("Application type error, please pass in:{}!",",".join(self.types))) if get.app_type != "all" and not get.app_type in installed_json.keys(): return public.return_message(0, 0,[]) # return public.return_message(0, 0,public.lang("No installed apps yet!")) get.query = get.get("query", None) from btdockerModelV2.dockerSock import container sk_container = container.dockerContainer() sk_container_list = sk_container.get_container() installed_apps = [] self.get_apps_json() if get.app_type == "all": for app_type in installed_json.keys(): type_res = self.structure_installed_apps(app_type, sk_container_list, installed_json, get.query) installed_apps.extend(type_res) else: installed_apps = self.structure_installed_apps(get.app_type, sk_container_list, installed_json, get.query) # 按照createTime倒序排序 installed_apps = sorted(installed_apps, key=lambda x: x["createTime"], reverse=True) page_data = self.get_page(installed_apps, get) # 给billionmail应用添加console_url字段 for app in page_data["data"]: if app.get("appname","") == 'billionmail': app_info = app.get("appinfo", []) app["console_url"] = self.get_global_console_url(app_info) return self.pageResult(True, data=page_data["data"], page=page_data["page"]) # 2024/8/1 下午4:02 获取指定已安装的应用信息 def get_installed_app_info(self, get): ''' @name 获取指定已安装的应用信息 ''' installed_json = self.read_json(self.installed_json_file) if not installed_json: return public.return_message(-1, 0, public.lang("No installed apps yet!")) get.id = get.get("id", None) get.service_name = get.get("service_name", None) if get.id is None and get.service_name is None: return public.return_message(-1, 0, public.lang("Please pass id/service_name parameter!")) for app_type in installed_json.keys(): for i in installed_json[app_type]: if i["id"] == get.id: self.get_apps_json() i["canUpdate"] = 1 if self.check_canupdate(i) else 0 return public.return_message(0, 0, i) if i["service_name"] == get.service_name: self.get_apps_json() i["canUpdate"] = 1 if self.check_canupdate(i) else 0 return public.return_message(0, 0, i) return public.return_message(-1, 0, public.lang("The specified app was not found!!")) # 2024/8/1 下午4:02 获取指定已安装的应用appinfo json信息 def get_installed_app_info_j(self, get): ''' @name 获取指定已安装的应用信息 ''' installed_json = self.read_json(self.installed_json_file) if not installed_json: return public.return_message(-1, 0, public.lang("No installed apps yet!")) get.id = get.get("id", None) get.service_name = get.get("service_name", None) if get.id is None and get.service_name is None: return public.return_message(-1, 0, public.lang("Please pass id/service_name parameter!")) for app_type in installed_json.keys(): for i in installed_json[app_type]: if i["id"] == get.id: self.get_apps_json() i["canUpdate"] = 1 if self.check_canupdate(i) else 0 return public.return_message(0, 0, i["appinfo"]) if i["service_name"] == get.service_name: self.get_apps_json() i["canUpdate"] = 1 if self.check_canupdate(i) else 0 return public.return_message(0, 0, i["appinfo"]) return public.return_message(-1, 0, public.lang("The specified app was not found!!")) # 2024/7/30 上午10:38 获取应用分类标签 def get_tags(self, get): if not os.path.exists(self.app_tags_file): public.downloadFile(public.get_url() + '/src/dk_app/yakpanel/apps/apptags.json', self.app_tags_file) app_tags = self.read_json(self.app_tags_file) if not app_tags: public.ExecShell("rm -f {}".format(self.app_tags_file)) public.downloadFile(public.get_url() + '/src/dk_app/yakpanel/apps/apptags.json', self.app_tags_file) app_tags = self.read_json(self.app_tags_file) if not app_tags: return public.return_message(0, 0, []) return public.return_message(0, 0, app_tags) def client_db_shell(self, get): """ 容器执行命令 :param get: :return: """ try: if not hasattr(get, "id"): return public.return_message(-1, 0, public.lang("Missing parameters! id")) if not hasattr(get, "appname"): return public.return_message(-1, 0, public.lang("Missing parameters! appname")) if get.appname.strip() not in ["mysql", "redis", "postgresql", "tdengine"]: return public.return_message(-1, 0, public.lang("This app link is not supported!")) command = "docker exec -it {}" if get.appname == "mysql": command = command.format(get.id) + " mysql -uroot -p{} --socket=/tmp/mysql.sock".format(get.password) elif get.appname == "redis": command = command.format(get.id) + " redis-cli -a {}".format(get.password) elif get.appname == "postgresql": command = command.format(get.id) + " psql -U postgres" elif get.appname == "tdengine": command = command.format(get.id) + " taos" return public.return_message(0, 0, command) except Exception as e: return public.return_message(-1, 0, public.lang('Failed to get the container')) def get_app_compose(self,get): """ 获取应用的docker-compose.yml文件内容 :param get: :return: """ app_name = get.get("app_name", None) appid = get.get("appid", None) if app_name is None: return public.return_message(-1, 0, public.lang("Missing parameters! app_name")) if "." in app_name or "/" in app_name: return public.return_message(-1, 0, public.lang("The app name is not legal, please do not include illegal characters!")) if get.get('gpu', 'false') == 'true': app_name = app_name+"_gpu" if appid == '-1': #/www/dk_project/dk_app/apphub/apphub compose_path = os.path.join(self.project_path, "apphub", "apphub",app_name,"latest","docker-compose.yml") self.app_template_path = compose_path else: #/www/dk_project/dk_app/templates compose_path = os.path.join(self.project_path, "templates", app_name, "docker-compose.yml") self.app_template_path = compose_path self.check_app_template(get) if not os.path.exists(compose_path): return public.return_message(-1, 0, public.lang("No docker-compose.yml file found for the specified app!")) compose_content = public.readFile(compose_path) return public.return_message(0, 0, compose_content) def get_global_console_url(self, app_info): """ 返回billionmail ip的控制台链接(端口字段不一致 先适配billionmail) 优先顺序: 优先https,其次http """ https_port = None http_port = None for info in app_info: if info["fieldKey"] == "HTTPS_PORT" and info["fieldValue"]: https_port = info["fieldValue"] elif info["fieldKey"] == "HTTP_PORT" and info["fieldValue"]: http_port = info["fieldValue"] public.print_log("https_port:", https_port) public.print_log("http_port:", http_port) ip_address = public.GetLocalIp() if https_port: return f"https://{ip_address}:{https_port}" elif http_port: return f"http://{ip_address}:{http_port}" else: return f"http://{ip_address}"