| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| import json |
| import os |
| import shutil |
| import time |
| from typing import Dict, Callable, Any |
|
|
| import public |
| from btdockerModel import dk_public as dp |
| from btdockerModel.dockerBase import dockerBase |
|
|
|
|
| class main(dockerBase): |
| __CONFIG_FILE = "/etc/docker/daemon.json" |
|
|
| def __init__(self): |
| if not os.path.exists(self.__CONFIG_FILE): |
| public.ExecShell("mkdir -p /etc/docker") |
| public.writeFile(self.__CONFIG_FILE, '{}') |
|
|
| def get_docker_compose_version(self): |
| try: |
| key = "dk_compose_version" |
| if public.get_cache_func(key)['data']: |
| if int(time.time()) - public.get_cache_func(key)["time"] < 86400: |
| return public.get_cache_func(key)["data"] |
|
|
| stdout, stderr = public.ExecShell("docker compose version --short") |
| if stderr != "": |
| stdout, stderr = public.ExecShell("docker-compose version --short") |
| if stderr != "": |
| return "" |
| public.set_cache_func(key, stdout.strip()) |
| return stdout.strip() |
| except: |
| return "" |
|
|
| def get_config(self, get): |
| """ |
| 获取设置配置信息 |
| @param get: |
| @return: |
| """ |
| |
| check_docker_compose = self.check_docker_compose_service() |
|
|
| |
| try: |
| installing = public.M('tasks').where('name=? and status=?', ("安装[Docker服务]", "-1")).count() |
| if not installing: |
| installing = public.M('tasks').where('name=? and status=?', ("安装[Docker服务]", "-1")).count() |
| except: |
| installing = 0 |
|
|
| if not os.path.exists("/www/server/panel/data/db/docker.db"): |
| public.ExecShell("mv -f /www/server/panel/data/docker.db /www/server/panel/data/db/docker.db") |
|
|
| |
| service_status = self.get_service_status() |
| if not service_status: |
| service_status = self.get_service_status() |
|
|
| |
| daemon_config = self._get_daemon_config() |
|
|
| |
| docker_compose_version = self.get_docker_compose_version() |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| from mod.project.docker.app.base import App |
| dk_project_path = App.dk_project_path |
| install_path = os.path.join(dk_project_path,'dk_app','installed.json') |
| allow_update_install_path = True |
| if os.path.exists(install_path): |
| try: |
| installed_apps = json.loads(public.readFile(install_path)) |
| for type,apps in installed_apps.items(): |
| if len(apps) > 0: |
| allow_update_install_path = False |
| except: |
| allow_update_install_path = False |
|
|
| return { |
| "service_status": service_status, |
| "docker_installed": self.check_docker_service(), |
| "docker_compose_installed": check_docker_compose[0], |
| "docker_compose_path": check_docker_compose[1], |
| "monitor_status": self.get_monitor_status(), |
| "monitor_save_date": dp.docker_conf()['SAVE'], |
| "daemon_path": self.__CONFIG_FILE, |
| "installing": installing, |
| **daemon_config, |
| "docker_compose_version": docker_compose_version, |
| "bad_registry": True, |
| "bad_registry_path": "https://docker.1ms.run", |
| "dk_project_path": dk_project_path, |
| "allow_update_install_path": allow_update_install_path |
| } |
|
|
| @classmethod |
| def _get_daemon_config(cls): |
| """获取 Docker 守护进程配置""" |
| default_config = { |
| "warehouse": [], |
| "log_cutting": {}, |
| "iptables": True, |
| "live_restore": False, |
| "driver": ["native.cgroupdriver=systemd"], |
| "socket": "unix:///var/run/docker.sock", |
| "ipv6_status": False, |
| "ipv6_addr": "", |
| "proxy":{}, |
| "data-root":"/var/lib/docker" |
| } |
|
|
| try: |
| data = json.loads(public.readFile(cls.__CONFIG_FILE)) |
|
|
| return { |
| "warehouse": data.get("insecure-registries", default_config["warehouse"]), |
| "log_cutting": data.get("log-opts", default_config["log_cutting"]), |
| "iptables": data.get("iptables", default_config["iptables"]), |
| "live_restore": data.get("live-restore", default_config["live_restore"]), |
| "driver": data.get("exec-opts", default_config["driver"])[0], |
| "socket": data.get("hosts", default_config["socket"]), |
| "ipv6_status": data.get("ipv6", default_config["ipv6_status"]), |
| "ipv6_addr": data.get("fixed-cidr-v6", default_config["ipv6_addr"]), |
| "proxy": data.get('proxies', default_config['proxy']), |
| "data-root": data.get("data-root", default_config["data-root"]) |
| } |
| except: |
| |
| return default_config |
|
|
| @staticmethod |
| def _get_com_registry_mirrors(): |
| """ |
| 获取常用加速配置 |
| @return: |
| """ |
| com_reg_mirror_file = "{}/class/btdockerModel/config/com_reg_mirror.json".format(public.get_panel_path()) |
| try: |
| com_reg_mirror = json.loads(public.readFile(com_reg_mirror_file)) |
| except: |
| public.ExecShell("rm -f {}".format(com_reg_mirror_file)) |
| public.downloadFile("{}/src/com_reg_mirror.json".format(public.get_url()), com_reg_mirror_file) |
| try: |
| com_reg_mirror = json.loads(public.readFile(com_reg_mirror_file)) |
| except: |
| com_reg_mirror = { |
| "https://mirror.ccs.tencentyun.com": "腾讯云镜像加速站", |
| } |
|
|
| return com_reg_mirror |
|
|
| def set_monitor_save_date(self, get): |
| """ |
| :param save_date: int 例如30 表示 30天 |
| :param get: |
| :return: |
| """ |
| import re |
| conf_path = "{}/data/docker.conf".format(public.get_panel_path()) |
| docker_conf = public.readFile(conf_path) |
| try: |
| save_date = int(get.save_date) |
| except: |
| return public.returnMsg(False, "监控保存时间需要为正整数!") |
| if save_date > 999: |
| return public.returnMsg(False, "监控数据不能保留超过999天!") |
| if not docker_conf: |
| docker_conf = "SAVE={}".format(save_date) |
| public.writeFile(conf_path, docker_conf) |
| return public.returnMsg(True, "设置成功!") |
| docker_conf = re.sub("SAVE\s*=\s*\d+", "SAVE={}".format(save_date), |
| docker_conf) |
| public.writeFile(conf_path, docker_conf) |
| dp.write_log("设置监控时间为[{}]天!".format(save_date)) |
| return public.returnMsg(True, "设置成功!") |
|
|
| def get_service_status(self): |
| sock = '/var/run/docker.pid' |
|
|
| |
| tagfile = "/www/server/panel/data/o.pl" |
| if os.path.exists(tagfile) and self.check_docker_service(): |
| try: |
| if public.cache_get(tagfile): |
| return True |
| content = public.readFile(tagfile).strip() |
| if content == "docker_bt_nas": |
| public.cache_set(tagfile, 1, 86400) |
| return True |
| except: |
| pass |
|
|
| if os.path.exists(sock): |
| try: |
| client = dp.docker_client() |
| if client: |
| return True |
| return False |
| except: |
| return False |
| else: |
| return False |
|
|
| |
| def docker_service(self, get): |
| """ |
| :param act start/stop/restart |
| :param get: |
| :return: |
| """ |
| act_dict = {'start': '启动', 'stop': '停止', 'restart': '重启'} |
| if get.act not in act_dict: |
| return public.returnMsg(False, '只允许传start|stop|restart!') |
| exec_str = 'systemctl {} docker'.format(get.act) |
| if get.act == "stop": |
| exec_str += ";systemctl {} docker.socket".format(get.act) |
| stdout, stderr = public.ExecShell(exec_str) |
| if stderr and not "but it can still be activated by:\n docker.socket\n" in stderr: |
| dp.write_log("将 Docker 服务状态设置为 [{}]失败,失败原因:{}".format(act_dict[get.act], stderr)) |
|
|
| jou_stdout, jou_stderr = public.ExecShell("journalctl -xe -u docker -n 100 --no-pager|grep libusranalyse.so") |
| if jou_stdout != "": |
| return public.returnMsg("docker服务设置失败,请关闭堡塔防入侵后再尝试!") |
|
|
| if "Can't operate. Failed to connect to bus" in stderr: |
| wsl_cmd = "/etc/init.d/docker {}".format(get.act) |
| wsl_stdout, wsl_stderr = public.ExecShell(wsl_cmd) |
| if not wsl_stderr: |
| return public.returnMsg(True, "设置成功!") |
| return public.returnMsg(False, "设置失败!失败原因:{}".format(wsl_stderr)) |
| return public.returnMsg(False, "设置失败!失败原因:{}".format(stderr)) |
|
|
| if get.act != "stop": |
| service_status = self.get_service_status() |
| if not service_status: |
| import time |
| public.ExecShell("systemctl stop docker") |
| public.ExecShell("systemctl stop docker.socket") |
| time.sleep(1) |
| public.ExecShell("systemctl start docker") |
|
|
| dp.write_log("将 Docker 服务状态设置为 [{}]".format(act_dict[get.act])) |
| return public.returnMsg(True, "{}成功".format(act_dict[get.act])) |
|
|
| |
| def get_registry_mirrors(self, get): |
| """ |
| 获取镜像加速信息 |
| @param get: |
| @return: |
| """ |
|
|
| try: |
| conf = json.loads(public.readFile(self.__CONFIG_FILE)) |
| if "registry-mirrors" not in conf: |
| reg_mirrors = [] |
| else: |
| reg_mirrors = conf['registry-mirrors'] |
| except: |
| reg_mirrors = [] |
| |
| |
| cache_key = 'detector_mirrors' |
| if str(get.get('force',"0")) == "1": |
| from mod.project.docker.config.mirrorDetector import DockerMirrorDetector |
| detector_mirrors = DockerMirrorDetector.test_batch(reg_mirrors,max_workers=4) |
| detector_mirrors = {item["url"]:item for item in detector_mirrors} |
| public.cache_set(cache_key, detector_mirrors, 86400) |
| else: |
| detector_mirrors = public.cache_get(cache_key) |
| if not detector_mirrors: |
| detector_mirrors = {} |
|
|
| res_mirrors = [] |
| for mirr_url in reg_mirrors: |
| if mirr_url in detector_mirrors.keys(): |
| res_mirrors.append({ |
| "url": mirr_url, |
| "ts": detector_mirrors[mirr_url]["ts"], |
| "available": detector_mirrors[mirr_url]["available"] |
| }) |
| else: |
| res_mirrors.append({"url": mirr_url,"ts": "","available": None}) |
|
|
| |
| com_reg_mirrors = public.cache_get("com_reg_mirrors") |
| if not com_reg_mirrors: |
| com_reg_mirrors = self._get_com_registry_mirrors() |
| public.cache_set("com_reg_mirrors", com_reg_mirrors, 86400) |
| |
| com_reg_mirrors = dict(filter(lambda x: x[0] not in reg_mirrors, com_reg_mirrors.items())) |
| return { |
| "registry_mirrors": res_mirrors, |
| "com_reg_mirrors": com_reg_mirrors |
| } |
|
|
| |
| def set_registry_mirrors(self, get): |
| """ |
| :param registry_mirrors_address registry.docker-cn.com,hub-mirror.c.163.com |
| :param get: |
| :return: |
| """ |
| import re |
| try: |
| registry_mirrors_address = get.get("registry_mirrors_address/s", "").strip() |
| conf = self.get_daemon_json() |
| |
| |
| new_mirrors = [] |
| if registry_mirrors_address: |
| mirrors = registry_mirrors_address.split(',') |
| for mirror in mirrors: |
| mirror = mirror.strip().rstrip('/') |
| if not mirror: |
| continue |
| |
| |
| if not re.match(r'^https?://', mirror): |
| return public.returnMsg(False,'加速地址[{}]格式错误 参考:https://mirror.ccs.tencentyun.com'.format(mirror)) |
| |
| if mirror not in new_mirrors: |
| new_mirrors.append(mirror) |
| |
| |
| current_mirrors = conf.get('registry-mirrors', []) |
| if not isinstance(current_mirrors, list): |
| if isinstance(current_mirrors, str): |
| current_mirrors = [current_mirrors] |
| else: |
| current_mirrors = [] |
| |
| |
| if current_mirrors == new_mirrors: |
| return public.returnMsg(True, '设置成功') |
|
|
| |
| if new_mirrors: |
| conf['registry-mirrors'] = new_mirrors |
| else: |
| if 'registry-mirrors' in conf: |
| del conf['registry-mirrors'] |
|
|
| public.writeFile(self.__CONFIG_FILE, json.dumps(conf, indent=2)) |
| dp.write_log("设置Docker加速成功!") |
| |
| return public.returnMsg(True, '设置成功') |
| except Exception as e: |
| return public.returnMsg(False, "设置失败!{}".format(str(e))) |
| |
| def detector_mirrors(self, get): |
| """ |
| 检测当前加速源地址是否可用 |
| @param get: |
| @return: |
| """ |
| mirrors_list = get.get("mirrors","").split(",") |
| if mirrors_list == [""]: |
| return public.return_data(True,[]) |
|
|
| cache_key = "detector_mirrors" |
| detector_dict = public.cache_get(cache_key) |
| if not detector_dict: |
| detector_dict = {} |
| |
| from mod.project.docker.config.mirrorDetector import DockerMirrorDetector |
| detector_result = DockerMirrorDetector.test_batch(mirrors_list,max_workers=4) |
| |
| for detector in detector_result: |
| detector_dict[detector["url"]] = { |
| "ts": detector["ts"], |
| "available": detector["available"] |
| } |
|
|
| public.cache_set(cache_key, detector_dict, 86400) |
| return public.return_data(True,detector_result) |
| |
|
|
| def update_com_registry_mirrors(self, get): |
| """ |
| 更新常用加速配置 |
| @param get: |
| @return: |
| """ |
| if get.registry_mirrors_address == "": |
| return public.returnMsg(True, "设置成功!") |
|
|
| import time |
| com_reg_mirror_file = "{}/class/btdockerModel/config/com_reg_mirror.json".format(public.get_panel_path()) |
| try: |
| com_reg_mirror = json.loads(public.readFile(com_reg_mirror_file)) |
| except: |
| com_reg_mirror = { |
| "https://mirror.ccs.tencentyun.com": "腾讯云镜像加速站", |
| } |
|
|
| if get.registry_mirrors_address in com_reg_mirror: |
| return public.returnMsg(True, "设置成功!") |
|
|
| remarks = get.remarks if "remarks" in get and get.remarks != "" else ("自定义镜像站" + str(int(time.time()))) |
|
|
| com_reg_mirror.update({"{}".format(get.registry_mirrors_address): remarks}) |
| public.writeFile(com_reg_mirror_file, json.dumps(com_reg_mirror, indent=2)) |
| dp.write_log("更新常用加速配置成功!") |
| return public.returnMsg(True, "更新成功!") |
|
|
| def del_com_registry_mirror(self, get): |
| """ |
| 删除常用加速配置 |
| @param get: |
| @return: |
| """ |
| com_reg_mirror_file = "{}/class/btdockerModel/config/com_reg_mirror.json".format(public.get_panel_path()) |
| try: |
| com_reg_mirror = json.loads(public.readFile(com_reg_mirror_file)) |
| except: |
| com_reg_mirror = { |
| "https://mirror.ccs.tencentyun.com": "腾讯云镜像加速站", |
| } |
|
|
| if get.registry_mirrors_address not in com_reg_mirror: |
| return public.returnMsg(True, "删除成功!") |
|
|
| del com_reg_mirror["{}".format(get.registry_mirrors_address)] |
| public.writeFile(com_reg_mirror_file, json.dumps(com_reg_mirror, indent=2)) |
| dp.write_log("删除常用加速配置成功!") |
| return public.returnMsg(True, "删除成功!") |
|
|
| def get_monitor_status(self): |
| """ |
| 获取docker监控状态 |
| @return: |
| """ |
| try: |
| from BTPanel import cache |
| except: |
| from cachelib import SimpleCache |
| cache = SimpleCache() |
|
|
| skey = "docker_monitor_status" |
| result = cache.get(skey) |
| if isinstance(result, bool): |
| return result |
|
|
| import psutil |
| is_monitor = False |
| try: |
| for proc in psutil.process_iter(): |
| try: |
| pinfo = proc.as_dict(attrs=['pid', 'name']) |
| if "monitorModel.py" in pinfo['name']: |
| is_monitor = True |
| except: |
| pass |
| except: |
| pass |
| cache.set(skey, is_monitor, 86400) |
| return is_monitor |
|
|
| def set_docker_monitor(self, get): |
| """ |
| 开启docker监控获取docker相取资源信息 |
| :param act: start/stop |
| :return: |
| """ |
| import time |
| python = "/www/server/panel/pyenv/bin/python" |
| if not os.path.exists(python): |
| python = "/www/server/panel/pyenv/bin/python3" |
| cmd_line = "/www/server/panel/class/btdockerModel/monitorModel.py" |
| if get.act == "start": |
| self.stop_monitor(get) |
| if not os.path.exists(self.moinitor_lock): |
| public.writeFile(self.moinitor_lock, "1") |
|
|
| shell = "nohup {} {} &".format(python, cmd_line) |
| public.ExecShell(shell) |
| time.sleep(1) |
| if self.get_monitor_status(): |
| dp.write_log("Docker监控启动成功!") |
| self.add_monitor_cron(get) |
| return public.returnMsg(True, "启动监控成功!") |
| return public.returnMsg(False, "启动监控失败!") |
| else: |
| from BTPanel import cache |
| skey = "docker_monitor_status" |
| cache.set(skey, False) |
|
|
| if os.path.exists(self.moinitor_lock): |
| os.remove(self.moinitor_lock) |
|
|
| self.stop_monitor(get) |
| return public.returnMsg(True, "Docker监控成功停止!") |
|
|
| |
| def stop_monitor(self, get): |
| ''' |
| @name 名称/描述 |
| @param 参数名<数据类型> 参数描述 |
| @return 数据类型 |
| ''' |
| cmd_line = [ |
| "/www/server/panel/class/btdockerModel/monitorModel.py", |
| "/www/server/panel/class/projectModel/bt_docker/dk_monitor.py" |
| ] |
|
|
| for cmd in cmd_line: |
| in_pid = True |
| sum = 0 |
| while in_pid: |
| in_pid = False |
| pid = dp.get_process_id( |
| "python", |
| "{}".format(cmd)) |
| if pid: |
| in_pid = True |
|
|
| if not pid: |
| pid = dp.get_process_id( |
| "python3", |
| "{}".format(cmd) |
| ) |
| if pid: |
| in_pid = True |
| public.ExecShell("kill -9 {}".format(pid)) |
| sum += 1 |
| if sum > 100: |
| break |
|
|
| import os |
|
|
| |
| directory = "/www/server/cron/" |
| if not os.path.exists(directory): |
| os.makedirs(directory) |
|
|
| |
| for filename in os.listdir(directory): |
| if not filename.endswith(".log"): |
| filepath = os.path.join(directory, filename) |
| if os.path.isdir(filepath): |
| continue |
| |
| with open(filepath, 'r') as file: |
| content = file.read() |
| if "monitorModel.py" in content or "dk_monitor.py" in content: |
| |
| if os.path.exists(filepath): |
| os.remove(filepath) |
| if os.path.exists(os.path.join(directory, "{}.log".format(filename))): |
| os.remove(os.path.join(directory, "{}.log".format(filename))) |
| public.ExecShell("crontab -l | sed '/{}/d' | crontab -".format(filename)) |
|
|
| dp.write_log("Docker监控成功停止!") |
|
|
| public.M('crontab').where('name=?', ("[勿删]docker监控守护程序",)).delete() |
| return public.returnMsg(True, "Docker监控成功停止!") |
|
|
| |
| def add_monitor_cron(self, get): |
| ''' |
| @name 名称/描述 |
| @author wzz <2023/12/7 下午 6:24> |
| @param 参数名<数据类型> 参数描述 |
| @return 数据类型 |
| ''' |
| try: |
| import crontab |
| if public.M('crontab').where('name', ("[勿删]docker监控守护程序",)).count() == 0: |
| p = crontab.crontab() |
| llist = p.GetCrontab(None) |
|
|
| if type(llist) == list: |
| for i in llist: |
| if i['name'] == '[勿删]docker监控守护程序': |
| return |
|
|
| get = { |
| "name": "[勿删]docker监控守护程序", |
| "type": "minute-n", |
| "where1": 5, |
| "hour": "", |
| "minute": "", |
| "week": "", |
| "sType": "toShell", |
| "sName": "", |
| "backupTo": "localhost", |
| "save": '', |
| "sBody": """ |
| if [ -f {} ]; then |
| new_mt=`ps aux|grep monitorModel.py|grep -v grep` |
| old_mt=`ps aux|grep dk_monitor.py|grep -v grep` |
| |
| if [ -z "$new_mt" ] && [ -z "$old_mt" ]; then |
| nohup /www/server/panel/pyenv/bin/python /www/server/panel/class/btdockerModel/monitorModel.py & |
| fi |
| fi |
| """.format(self.moinitor_lock), |
| "urladdress": "undefined" |
| } |
| p.AddCrontab(get) |
| except Exception as e: |
| return False |
|
|
| def check_docker_compose_service(self): |
| """ |
| 检查docker-compose是否已经安装 |
| :return: |
| """ |
| docker_compose = "/usr/bin/docker-compose" |
|
|
| docker_compose_path = "{}/class/btdockerModel/config/docker_compose_path.pl".format(public.get_panel_path()) |
| if os.path.exists(docker_compose_path): |
| docker_compose = public.readFile(docker_compose_path).strip() |
|
|
| if not os.path.exists(docker_compose): |
| dk_compose_list = ["/usr/libexec/docker/cli-plugins/docker-compose", "/usr/local/docker-compose"] |
| for i in dk_compose_list: |
| if os.path.exists(i): |
| public.ExecShell("ln -sf {} {}".format(i, "/usr/bin/docker-compose")) |
| break |
|
|
| if not os.path.exists(docker_compose): |
| return False, "" |
|
|
| return True, docker_compose |
|
|
| def check_docker_service(self): |
| """ |
| 检查docker是否安装 |
| @return: |
| """ |
| docker = "/usr/bin/docker" |
| if not os.path.exists(docker): |
| return False |
| return True |
|
|
| def set_docker_compose_path(self, get): |
| """ |
| 设置docker-compose的路径 |
| @param get: |
| @return: |
| """ |
| docker_compose_file = get.docker_compose_path if "docker_compose_path" in get else "" |
| if docker_compose_file == "": |
| return public.returnMsg(False, "docker-compose文件路径不能为空!") |
|
|
| if not os.path.exists(docker_compose_file): |
| return public.returnMsg(False, "docker-compose文件不存在!") |
|
|
| public.ExecShell("chmod +x {}".format(docker_compose_file)) |
| cmd_result = public.ExecShell("{} --version".format(docker_compose_file)) |
| if not cmd_result[0]: |
| return public.returnMsg(False, "docker-compose文件不可执行或不是docker-compose文件!") |
|
|
| docker_compose_path = "{}/class/btdockerModel/config/docker_compose_path.pl".format(public.get_panel_path()) |
|
|
| public.writeFile(docker_compose_path, docker_compose_file) |
| dp.write_log("设置docker-compose路径成功!") |
| return public.returnMsg(True, "设置成功!") |
|
|
| |
| def check_area(self, get): |
| ''' |
| @name 检查本机公网IP归属地是否为中国大陆IP |
| @param get: |
| @return dict{"status":True/False,"msg":"提示信息"} |
| ''' |
| flag = "0" |
| try: |
| client_ip = public.get_server_ip() |
| c_area = public.get_free_ip_info(client_ip) |
| if ("中国" in c_area["country"] and |
| "中国" in c_area["info"] and |
| "CN" in c_area["en_short_code"] and |
| not "local" in c_area): |
| if "腾讯云" in c_area["carrier"]: |
| flag = "2" |
| elif "阿里云" in c_area["carrier"]: |
| flag = "3" |
| else: |
| flag = "1" |
| except: |
| |
| flag = "1" |
|
|
| return flag |
|
|
| |
| def install_docker_program(self, get): |
| """ |
| 安装docker和docker-compose |
| :param get: |
| :return: |
| """ |
| import time |
| url = get.get("url/s", "") |
| type = get.get("type/d", 0) |
|
|
| public.ExecShell("rm -f /etc/yum.repos.d/docker-ce.repo") |
| if url == "": |
| c_flag = self.check_area(get) |
| if c_flag != "0": |
| if c_flag == "2": |
| url = "mirrors.tencent.com/docker-ce" |
| elif c_flag == "3": |
| url = "mirrors.aliyun.com/docker-ce" |
| else: |
| url_file = "/www/server/panel/class/btdockerModel/config/install_url.pl" |
| public.ExecShell("rm -f {}".format(url_file)) |
| public.downloadFile("{}/src/dk_app/apps/install_url.pl".format(public.get_url()), url_file) |
| url_body = public.readFile(url_file) |
| url = url_body.strip() if url_body else "" |
|
|
| |
| if public.M('tasks').where('name=? and status=?', ("安装[Docker服务]", "-1")).count(): |
| return public.returnMsg(False, "已存在安装任务,请勿重复添加!") |
|
|
| mmsg = "安装[Docker服务]" |
|
|
| if type == 0 and url == "": |
| |
| execstr = ("wget -O /tmp/docker_install.sh {}/install/0/docker_install.sh && bash /tmp/docker_install.sh install").format(public.get_url()) |
| elif type == 0 and url != "": |
| |
| execstr = ("wget -O /tmp/docker_install.sh {}/install/0/docker_install.sh && bash /tmp/docker_install.sh install {}").format(public.get_url(), url.strip('"')) |
| else: |
| |
| execstr = "wget -O /tmp/docker_bin.sh {}/install/0/docker_bin.sh && bash /tmp/docker_bin.sh install".format(public.get_url()) |
|
|
| sid_result = public.M('tasks').add('id,name,type,status,addtime,execstr', (None, mmsg, 'execshell', '0', time.strftime('%Y-%m-%d %H:%M:%S'), execstr)) |
| from panelPlugin import panelPlugin |
| panelPlugin().create_install_wait_msg(sid_result, mmsg, False) |
|
|
| public.httpPost( |
| public.GetConfigValue('home') + '/api/panel/plugin_total', { |
| "pid": "1111111", |
| 'p_name': "Docker商用模块" |
| }, 3) |
|
|
| return public.returnMsg(True, "安装任务已添加到队列中!") |
|
|
| @staticmethod |
| def uninstall_status(get): |
| """ |
| 检测docker是否可以卸载 |
| :param get: |
| :return: |
| """ |
| |
| from btdockerModel import containerModel |
| docker_list = containerModel.main().get_list(get) |
| |
| from btdockerModel import imageModel |
| images_list = imageModel.main().image_list(get) |
| if len(images_list) > 0 or len(docker_list["container_list"]) > 0: |
| return public.returnMsg(False, "请手动删除所有容器和所有镜像后再进行卸载docker!") |
| return public.returnMsg(True, "可以卸载!") |
|
|
| def uninstall_docker_program(self, get): |
| """ |
| 卸载docker和docker-compose |
| :param get: |
| :return: |
| """ |
| type = get.get("type/d", 0) |
| if type == 0: |
| uninstall_status = self.uninstall_status(get) |
| if not uninstall_status["status"]: |
| return uninstall_status |
|
|
| public.ExecShell( |
| "wget -O /tmp/docker_install.sh {}/install/0/docker_install.sh && bash /tmp/docker_install.sh uninstall" |
| .format(public.get_url() |
| )) |
| public.ExecShell("rm -rf /usr/bin/docker-compose") |
|
|
| return public.returnMsg(True, "卸载成功!") |
|
|
| def repair_docker(self, get): |
| """ |
| 修复docker |
| @param get: |
| @return: |
| """ |
| import time |
| mmsg = "Repair Docker service" |
| execstr = "curl -fsSL https://get.docker.com -o /tmp/get-docker.sh && sed -i '/sleep 20/d' /tmp/get-docker.sh && /bin/bash /tmp/get-docker.sh" |
| public.M('tasks').add('id,name,type,status,addtime,execstr', |
| (None, mmsg, 'execshell', '0', |
| time.strftime('%Y-%m-%d %H:%M:%S'), execstr)) |
| public.httpPost( |
| public.GetConfigValue('home') + '/api/panel/plugin_total', { |
| "pid": "1111111", |
| 'p_name': "Docker商用模块" |
| }, 3) |
| return public.returnMsg(True, "修复任务已添加到队列中!") |
|
|
| def get_daemon_json(self): |
| """ |
| 获取daemon.json配置信息 |
| @param get: |
| @return: |
| """ |
| try: |
| return json.loads(public.readFile(self.__CONFIG_FILE)) |
| except Exception as e: |
| raise RuntimeError("读取配置文件失败:{}".format(str(e))) |
|
|
| def save_daemon_json(self, get): |
| """ |
| 保存daemon.json配置信息,保存前备份,验证可以成功执行后再替换 |
| @param get: |
| @return: |
| """ |
| if getattr(get, "daemon_json", "") == "": |
| public.ExecShell("rm -f {}".format(self.__CONFIG_FILE)) |
| return public.returnMsg(True, "保存成功!") |
|
|
| try: |
| conf = json.loads(get.daemon_json) |
| public.writeFile(self.__CONFIG_FILE, json.dumps(conf, indent=2)) |
| dp.write_log("保存daemon.json配置成功!") |
| return public.returnMsg(True, "保存成功!") |
| except Exception as e: |
| print(e) |
| if "Expecting property name enclosed in double quotes" in str(e): |
| return public.returnMsg(False, "保存失败,原因:daemon.json配置文件格式错误!") |
|
|
| return public.returnMsg(False, "保存失败,原因:{}!".format(e)) |
|
|
| def set_docker_global(self, get): |
| ''' |
| docker全局配置接口 |
| 根据传值进行不同操作的写入/etc/docker/daemon.json |
| 全局设置后都需要重启docker生效 |
| ''' |
|
|
| |
| ''' |
| 加速URL 默认 https://docker.1ms.run |
| 私有仓库 |
| 日志切割 |
| iptables 是否自动管理iptables规则 默认True |
| live-restore docker实时恢复 默认False |
| driver方式 指定容器运行参数 默认systemd |
| 全局开启ipv6 |
| ''' |
| config_actions: Dict[str, Callable[[Any], Any]] = { |
| 'registry_mirrors_address': self.set_registry_mirrors, |
| 'warehouse': self.set_warehouse, |
| 'log_cutting': self.set_log_cutting, |
| 'iptables': self.set_iptables, |
| 'live_restore': self.set_live_restore, |
| 'driver': self.set_drive, |
| 'status': self.set_ipv6_global, |
| 'proxy_settings': self.set_proxy, |
| "docker_root_dir": self.set_docker_root_dir |
| } |
|
|
| |
| for key, func_name in config_actions.items(): |
| if hasattr(get, key): |
| result = func_name(get) |
| return result |
|
|
| return public.returnMsg(False, "不存在的配置项!") |
| def set_docker_root_dir(self, get): |
| ''' |
| 设置docker根目录 |
| @param get: docker_root_dir |
| @return: |
| ''' |
| try: |
| |
| get.act = "stop" |
| self.docker_service(get) |
|
|
| |
| data = self.get_daemon_json() |
| current_root_dir = data.get("data-root", "/var/lib/docker") |
| new_root_dir = get.get("docker_root_dir", "") |
|
|
| if not new_root_dir: |
| return public.returnMsg(False, "Docker根目录不能为空!") |
|
|
| if current_root_dir == new_root_dir: |
| return public.returnMsg(True, "当前Docker根目录已是目标目录,无需更改!") |
|
|
| |
| move_data = get.get("move_data", False) |
|
|
| if move_data and os.path.exists(current_root_dir): |
| try: |
| |
| current_size = sum(os.path.getsize(os.path.join(dirpath, filename)) |
| for dirpath, dirnames, filenames in os.walk(current_root_dir) |
| for filename in filenames) |
|
|
| |
| shutil.copytree(current_root_dir, new_root_dir) |
|
|
| |
| new_size = sum(os.path.getsize(os.path.join(dirpath, filename)) |
| for dirpath, dirnames, filenames in os.walk(new_root_dir) |
| for filename in filenames) |
|
|
| if current_size != new_size: |
| raise Exception("文件大小不匹配,可能存在数据丢失!") |
|
|
| |
| shutil.rmtree(current_root_dir) |
|
|
| except Exception as e: |
| |
| data = self.get_daemon_json() |
| data["data-root"] = current_root_dir |
| public.writeFile(self.__CONFIG_FILE, json.dumps(data, indent=2)) |
|
|
| |
| get.act = "start" |
| self.docker_service(get) |
|
|
| return public.returnMsg(False, "移动失败,已恢复原有配置,原因:{}".format(str(e))) |
|
|
| |
| data["data-root"] = new_root_dir |
| public.writeFile(self.__CONFIG_FILE, json.dumps(data, indent=2)) |
|
|
| |
| get.act = "start" |
| self.docker_service(get) |
| return public.returnMsg(True, "设置成功!") |
| except Exception as e: |
| return public.returnMsg(False, "设置失败!{}".format(str(e))) |
|
|
| def set_proxy(self, get): |
| ''' |
| 设置全局代理 |
| @param get: proxy_settings |
| @return: |
| ''' |
| try: |
| |
| try: |
| proxy_settings = json.loads(get.get("proxy_settings", "{}")) |
| http_proxy = proxy_settings.get("http_proxy", "").strip() |
| https_proxy = proxy_settings.get("https_proxy", "").strip() |
| no_proxy = proxy_settings.get("no_proxy", "localhost,127.0.0.1").strip() |
| except json.JSONDecodeError: |
| return public.returnMsg(False, "代理设置的JSON格式错误!") |
|
|
| data = self.get_daemon_json() |
|
|
| if not proxy_settings and "proxies" in data: |
| del data["proxies"] |
| else: |
| daemon_proxy = data.get("proxies", {}) |
| daemon_proxy["http-proxy"] = http_proxy |
| daemon_proxy["https-proxy"] = https_proxy |
| daemon_proxy["no-proxy"] = no_proxy |
| data["proxies"] = daemon_proxy |
|
|
| public.writeFile(self.__CONFIG_FILE, json.dumps(data, indent=2)) |
|
|
| |
| self.reload_global_config() |
|
|
| |
| get.act = "restart" |
| self.docker_service(get) |
| return public.returnMsg(True, "代理设置成功!") |
| except Exception as e: |
| return public.returnMsg(False, "设置失败!{}".format(str(e))) |
|
|
| def set_warehouse(self, get): |
| ''' |
| 设置私有仓库 |
| @param get: warehouse |
| @return: |
| ''' |
| try: |
| warehouse = get.get("warehouse", "").strip() |
| data = self.get_daemon_json() |
|
|
| if warehouse == "" and "insecure-registries" in data: |
| del data["insecure-registries"] |
| else: |
| warehouse = [i.strip() for i in warehouse.split(",")] |
| data["insecure-registries"] = warehouse |
|
|
| public.writeFile(self.__CONFIG_FILE, json.dumps(data, indent=2)) |
|
|
| |
| get.act = "restart" |
| self.docker_service(get) |
| return public.returnMsg(True, "设置成功!") |
| except Exception as e: |
| return public.returnMsg(False, "设置失败!{}".format(str(e))) |
|
|
| def set_log_cutting(self, get): |
| ''' |
| 设置日志切割 |
| @param get: log_cutting |
| @return: |
| ''' |
| try: |
| log_cutting = get.get("log_cutting", {}) |
| if isinstance(log_cutting, str): |
| try: |
| log_cutting = json.loads(log_cutting) |
| except: |
| log_cutting = {} |
|
|
| |
| if isinstance(log_cutting, dict): |
| log_cutting = {k: str(v) for k, v in log_cutting.items()} |
|
|
| data = self.get_daemon_json() |
| if not log_cutting and "log-opts" in data: |
| del data["log-opts"] |
| else: |
| data["log-opts"] = log_cutting |
|
|
| public.writeFile(self.__CONFIG_FILE, json.dumps(data, indent=2)) |
|
|
| |
| get.act = "restart" |
| self.docker_service(get) |
| return public.returnMsg(True, "设置成功!") |
| except Exception as e: |
| return public.returnMsg(False, "设置失 败!{}".format(str(e))) |
|
|
| def set_iptables(self, get): |
| ''' |
| 设置 Docker 对 iptables 规则的自动配置 |
| @param get:a |
| @return: dict |
| ''' |
| try: |
| iptables = get.get("iptables/d", "") |
| data = self.get_daemon_json() |
| data["iptables"] = True if iptables == 1 else False |
| public.writeFile(self.__CONFIG_FILE, json.dumps(data, indent=2)) |
|
|
| |
| get.act = "restart" |
| self.docker_service(get) |
| return public.returnMsg(True, "设置成功!") |
| except Exception as e: |
| return public.returnMsg(False, "设置失败!{}".format(str(e))) |
|
|
| def set_live_restore(self, get): |
| ''' |
| 设置 docker实时恢复容器 |
| 允许在 Docker 守护进程发生意外停机或崩溃时保留正在运行的容器状态 |
| @param get: |
| @return: dict |
| ''' |
| try: |
| live_restore = get.get("live_restore/d", "") |
|
|
| data = self.get_daemon_json() |
| data["live-restore"] = True if live_restore == 1 else False |
|
|
| public.writeFile(self.__CONFIG_FILE, json.dumps(data, indent=2)) |
|
|
| |
| get.act = "restart" |
| self.docker_service(get) |
| return public.returnMsg(True, "设置成功!") |
| except Exception as e: |
| return public.returnMsg(False, "设置失败!{}".format(str(e))) |
|
|
| def set_drive(self, get): |
| ''' |
| 设置docker driver方式 |
| @param get: |
| @return: dict |
| ''' |
| try: |
| drive = get.get("driver", "") |
|
|
| data = self.get_daemon_json() |
|
|
| if drive == "systemd": |
| data["exec-opts"] = ["native.cgroupdriver=systemd"] |
| elif drive == "cgroupfs": |
| data["exec-opts"] = ["native.cgroupdriver=cgroupfs"] |
| elif drive == "" and "exec-opts" in data: |
| del data["exec-opts"] |
|
|
| public.writeFile(self.__CONFIG_FILE, json.dumps(data, indent=2)) |
|
|
| |
| get.act = "restart" |
| self.docker_service(get) |
| return public.returnMsg(True, "设置成功!") |
| except Exception as e: |
| return public.returnMsg(False, "设置失败!{}".format(str(e))) |
|
|
| def set_ipv6_global(self, get): |
| ''' |
| 设置全局开启ipv6 |
| @param get: |
| @return: dict |
| ''' |
| try: |
| status = get.get("status/d", 0) |
| ipaddr = get.get("ipaddr", "") |
|
|
| data = self.get_daemon_json() |
|
|
| if status == 1: |
| data["ipv6"] = True |
| if ipaddr == "": |
| subnet = self.random_ipv6_subnet() |
| data["fixed-cidr-v6"] = subnet |
| else: |
| if not self.is_valid_ipv6_subnet(ipaddr): |
| return public.returnMsg(False, "请输入正确的IPv6地址!") |
| data["fixed-cidr-v6"] = ipaddr |
| else: |
| if "ipv6" in data and data["ipv6"]: |
| del data["ipv6"] |
| if "fixed-cidr-v6" in data and data["fixed-cidr-v6"]: |
| del data["fixed-cidr-v6"] |
|
|
| public.writeFile(self.__CONFIG_FILE, json.dumps(data, indent=2)) |
|
|
| |
| get.act = "restart" |
| self.docker_service(get) |
|
|
| return public.returnMsg(True, "设置成功!") |
| except Exception as e: |
| return public.returnMsg(False, "设置失败!{}".format(str(e))) |
|
|
| def random_ipv6_subnet(self): |
| """ |
| 在2001:db8前缀中生成一个随机IPv6子网 |
| Returns: |
| str: Random IPv6 subnet in CIDR format. |
| """ |
| import random |
| prefix = "2001:db8:" |
| hextets = ["".join(random.choice("123456789abcdef") + ''.join(random.choice("0123456789abcdef") for _ in range(3))) for _ in range(6)] |
| return prefix + ":".join(hextets) + "/64" |
|
|
| def is_valid_ipv6_subnet(self, subnet): |
| import ipaddress |
| try: |
| |
| ipaddress.IPv6Network(subnet, strict=False) |
| return True |
| except (ipaddress.AddressValueError, ValueError) as e: |
| |
| return False |
|
|
| |
| def get_system_info(self, get): |
| ''' |
| @name 获取系统信息 |
| ''' |
| from btdockerModel.dockerSock.system import dockerSystem |
| import psutil |
| system_info = dockerSystem().get_system_info() |
|
|
| if not system_info: |
| try: |
| if os.path.exists("/etc/redhat-release"): |
| OperatingSystem = public.readFile("/etc/redhat-release").strip() |
| elif os.path.exists("/etc/issue"): |
| OperatingSystem = public.readFile("/etc/issue").strip() |
| else: |
| OperatingSystem = public.ExecShell(". /etc/os-release && echo $VERSION")[0].strip() |
| except: |
| OperatingSystem = "Linux系统,无法正确获取版本号!" |
|
|
| system_info = { |
| "Name": public.ExecShell("hostname")[0].strip(), |
| "OperatingSystem": OperatingSystem, |
| "Architecture": public.ExecShell("uname -m")[0].strip(), |
| "KernelVersion": public.ExecShell("uname -r")[0].strip(), |
| "NCPU": public.ExecShell("nproc")[0].strip(), |
| "MemTotal": psutil.virtual_memory().total, |
| "ServerVersion": "", |
| "DockerRootDir": "", |
| } |
|
|
| return public.returnResult(True, data=system_info) |
|
|
| def update_compose(self, get): |
| ''' |
| 升级docker-compose版本 |
| ''' |
| try: |
| pid_file = "/tmp/update_dk-compose.pid" |
| log_file = "/tmp/update_dk-compose.log" |
| try: |
| import psutil |
| p = psutil.Process(int(public.readFile(pid_file))) |
| if p.is_running(): |
| return public.returnMsg(True, "升级进程PID正在执行中,请等待完成!") |
| except: |
| pass |
|
|
| if os.path.exists(log_file): |
| public.writeFile(log_file, "") |
|
|
| if os.path.exists(pid_file): |
| os.remove(pid_file) |
| |
| key = "dk_compose_version" |
| skey = "dk_compose_github_version" |
|
|
| compose_version, stderr = public.ExecShell("docker compose version --short") |
| if stderr != "": |
| compose_version, stderr = public.ExecShell("docker-compose version --short") |
| if stderr != "": |
| return public.returnMsg(False, "获取当前compose版本失败!失败原因:{}".format(stderr)) |
|
|
| |
| public.set_cache_func(key, compose_version.strip()) |
|
|
| github_version = public.cache_get(skey) |
| if not public.cache_get(skey): |
| |
| github_version, stderr = public.ExecShell( |
| ''' |
| curl -s https://api.github.com/repos/docker/compose/releases/latest | awk -F'"' '/"tag_name":/ {print $4}' | sed 's/^v//' |
| ''' |
| ) |
| if stderr != "": |
| return public.returnMsg(False, "获取github最新compose版本失败!失败原因:{}".format(stderr)) |
| |
| public.cache_set(skey, github_version.strip(), 86400) |
|
|
| if compose_version.strip() == github_version.strip(): |
| return public.returnMsg(False, "当前compose版本与github最新版本一致,无需升级!") |
|
|
| if compose_version.strip() < github_version.strip(): |
| |
| try: |
| if os.path.exists("/usr/bin/docker-compose"): |
| os.remove("/usr/bin/docker-compose") |
| if os.path.exists("/usr/local/lib/docker/cli-plugins/docker-compose"): |
| os.remove("/usr/local/lib/docker/cli-plugins/docker-compose") |
| except: |
| pass |
|
|
| public.ExecShell( |
| ''' |
| nohup bash -c ' |
| sudo curl -L "https://1ms.run/install/docker-compose/latest/$(uname -s)/$(uname -m)" -o /usr/local/bin/docker-compose && |
| sudo chmod +x /usr/local/bin/docker-compose && |
| sudo ln -sf /usr/local/bin/docker-compose /usr/bin/docker-compose && |
| sudo ln -sf /usr/local/bin/docker-compose /usr/libexec/docker/cli-plugins/docker-compose |
| ' >{log_file} 2>&1 && echo 'bt_successful' >> {log_file} || echo 'bt_failed' >> {log_file} & |
| echo $! > {pid_file} |
| '''.format(log_file=log_file, pid_file=pid_file) |
| ) |
|
|
| return public.returnMsg(True, "正在升级docker-compose版本,请稍后查看日志!最新版本:{}".format(github_version.strip())) |
|
|
| except Exception as e: |
| return public.returnMsg(False, "升级失败!失败原因:{}".format(str(e))) |
|
|
| @staticmethod |
| def reload_global_config(): |
| ''' |
| 重载全局配置文件 |
| ''' |
| try: |
| |
| stdout, stderr = public.ExecShell("systemctl reload docker") |
| if stderr: |
| return public.returnMsg(False, "重载失败,原因:{}".format(stderr)) |
| return public.returnMsg(True, "重载成功!") |
| except Exception as e: |
| error_message = "全局配置文件重载失败:{}".format(str(e)) |
| dp.write_log(error_message) |
| return public.returnMsg(False, error_message) |
|
|
| def set_dk_project_path(self,get): |
| ''' |
| @name 设置docker项目路径 |
| @param "data":{"参数名":""} <数据类型> 参数描述 |
| @return dict{"status":True/False,"msg":"提示信息"} |
| ''' |
| if not hasattr(get, 'path') and get.path is None and get.path == "": |
| return public.returnResult(False, "参数错误,path参数!") |
|
|
| if get.path.strip() == "": |
| get.path = "/www/dk_project" |
|
|
| project_path_file = "{}/class/btdockerModel/config/project_path.pl".format(public.get_panel_path()) |
|
|
| res = public.writeFile(project_path_file, get.path) |
| msg = "设置成功!" |
| if not res: |
| msg = "设置失败!" |
| return public.returnMsg(res,msg) |