# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------

# ------------------------------
# Docker模型
# ------------------------------

import json
import os
import shutil
import time
from typing import Dict, Callable, Any

import public
from btdockerModelV2 import dk_public as dp
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param


class main(dockerBase):
    __CONFIG_FILE = "/etc/docker/daemon.json"

    def __init__(self):
        if not os.path.exists(self.__CONFIG_FILE):
            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=?', ("Install Docker Service", "-1")).count()
            if not installing:
                installing = public.M('tasks').where('name=? and status=?', ("Install Docker Service", "-1")).count()
        except:
            public.print_log(public.get_error_info())
            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")

        if not os.path.exists("/www/server/panel/data/docker.db"):
            public.ExecShell("mv -f /www/server/panel/data/db/docker.db /www/server/panel/data/docker.db")
        # 检查docker服务状态
        service_status = self.get_service_status()
        if not service_status:
            service_status = self.get_service_status()
        # 获取 Docker 守护进程文件配置
        daemon_config = self._get_daemon_config()

        # # 获取docker-compose版本
        docker_compose_version = self.get_docker_compose_version()

        # ipv6_file = "/etc/docker/daemon.json"

        # try:
        #     data = json.loads(public.readFile(self.__CONFIG_FILE))
        #     ipv6_status = data["ipv6"]
        #     ipv6_addr = data["fixed-cidr-v6"]
        # except:
        #     ipv6_addr = ""
        #     ipv6_status = False

        try:
            bad_registry = os.path.exists("/www/server/panel/config/bad_registry.pl")
            bad_registry_path = public.readFile("/www/server/panel/config/bad_registry.pl")
            if not bad_registry_path:
                bad_registry = False
                bad_registry_path = ""
        except:
            bad_registry = False
            bad_registry_path = ""

        # 应用商店路径
        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

        data = {
            "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": bad_registry,
            "bad_registry_path": bad_registry_path,
            "dk_project_path": dk_project_path,
            "allow_update_install_path": allow_update_install_path
        }
        return public.return_message(0, 0, data)

    @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))

            proxy = {
                "http_proxy": data.get("http-proxy"),
                "https_proxy": data.get("https-proxy"),
                "no_proxy": data.get("no-proxy")
            } if data.get("http-proxy", "") else default_config["proxy"]
            driver = data.get("exec-opts", 0)
            if not driver or len(driver) == 0:
                driver = default_config["driver"]
            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": 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": proxy,
                "data-root": data.get("data-root", default_config["data-root"])
            }
        except:
            # public.print_log(public.get_error_info())
            return default_config

    @staticmethod
    def _get_com_registry_mirrors():
        """
        获取常用加速配置
        @return:
        """
        com_reg_mirror_file = "{}/class_v2/btdockerModelV2/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://docker.m.daocloud.io": "Third party image accelerator",
                }

        return com_reg_mirror

    def set_monitor_save_date(self, get):
        """
        :param save_date: int 例如30 表示 30天
        :param get:
        :return:
        """
        # 校验参数
        try:
            get.validate([
                Param('save_date').Require().Integer(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))

        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.return_message(-1, 0, public.lang("The monitoring save time needs to be a positive integer!"))
        if save_date > 999:
            return public.return_message(-1, 0,
                                         public.lang("Monitoring data cannot be retained for more than 999 days!"))
        if not docker_conf:
            docker_conf = "SAVE={}".format(save_date)
            public.writeFile(conf_path, docker_conf)
            return public.return_message(0, 0, public.lang("Successfully set!"))
        docker_conf = re.sub(r"SAVE\s*=\s*\d+", "SAVE={}".format(save_date),
                             docker_conf)
        public.writeFile(conf_path, docker_conf)
        dp.write_log("et the monitoring time to [{}] days!".format(save_date))
        return public.return_message(0, 0, public.lang("Successfully set!"))

    def get_service_status(self):
        sock = '/var/run/docker.pid'

        # Nas镜像标识文件
        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

    # docker服务状态设置
    def docker_service(self, get):
        """
        :param act start/stop/restart
        :param get:
        :return:
        """

        act_dict = {'start': 'start', 'stop': 'stop', 'restart': 'restart'}
        if get.act not in act_dict:
            return public.return_message(-1, 0, public.lang("There's no way to do that"))
        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(
                "Setting the Docker service status to [{}] failed, failure reason:{}".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.return_message(-1, 0, public.lang(
                    "Docker service setup failed, please turn off yakpanel anti-intrusion and try again!"))

            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.return_message(0, 0, public.lang("Setup Successful!"))
                return public.return_message(-1, 0, public.lang("Setup failed! Failure reason:{}", wsl_stderr))
            return public.return_message(-1, 0, public.lang("Setup failed! Failure reason:{}", 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("Set the Docker service status to [{}]".format(act_dict[get.act]))
        return public.return_message(0, 0, public.lang("{} success", act_dict[get.act]))

    # 获取加速配置
    def get_registry_mirrors(self, get):
        """
        获取镜像加速信息
        @param get:
        @return:
        """
        try:
            if not os.path.exists(self.__CONFIG_FILE):
                reg_mirrors = []
            else:
                # 修复文件为空报错
                conf = {}
                con = public.readFile(self.__CONFIG_FILE)
                if con:
                    conf = json.loads(con)

                # 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 = []

        # 缓存一天获取列表  不用每次都去请求
        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)

        data = {
            "registry_mirrors": reg_mirrors,
            "com_reg_mirrors": com_reg_mirrors
        }
        return public.return_message(0, 0, data)

    # 设置加速配置
    def set_registry_mirrors(self, get):
        """
        :param registry_mirrors_address registry.docker-cn.com\nhub-mirror.c.163.com
        :param get:
        :return:
        """
        # {"registry_mirrors_address": "https://wzz1sdf11nb.com", "remarks": ""}
        # 校验参数
        try:
            get.validate([
                Param('registry_mirrors_address').Require().String(),
                Param('remarks').String(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))

        if not os.path.exists('/etc/docker/'):
            os.makedirs('/etc/docker', 755, True)

        import re
        try:
            get.registry_mirrors_address = get.get("registry_mirrors_address/s", "")
            conf = self.get_daemon_json()

            if not get.registry_mirrors_address.strip():
                if "registry-mirrors" in conf:
                    del (conf['registry-mirrors'])
            else:
                registry_mirrors = get.registry_mirrors_address.strip()
                if registry_mirrors == "":
                    # 2024/4/16 下午12:10 双重保险
                    if 'registry-mirrors' in conf:
                        del (conf['registry-mirrors'])
                else:
                    if not re.search('https?://', registry_mirrors):
                        return public.return_message(-1, 0, public.lang(
                            "Speedup address [ {}] Format error <br> Reference: https://mirror.ccs.tencentyun.com",
                            registry_mirrors))

                    conf['registry-mirrors'] = public.xsssec2(registry_mirrors)
                    if isinstance(conf['registry-mirrors'], str):
                        conf['registry-mirrors'] = [conf['registry-mirrors']]

            public.writeFile(self.__CONFIG_FILE, json.dumps(conf, indent=2))
            self.update_com_registry_mirrors(get)
            dp.write_log("Setup Docker acceleration successful!")
            get.act = "restart"
            self.docker_service(get)
            return public.return_message(0, 0, public.lang("successfully set"))

        except:
            return public.return_message(-1, 0,
                                         public.lang("Setup failed! Failure reason :{}", public.get_error_info()))

    def update_com_registry_mirrors(self, get):
        """
        更新常用加速配置
        @param get:
        @return:
        """
        if get.registry_mirrors_address == "":
            return public.return_message(0, 0, public.lang("successfully set"))

        import time
        com_reg_mirror_file = "{}/class_v2/btdockerModelV2/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://docker.m.daocloud.io": "Third party image accelerator",
                # "https://mirror.ccs.tencentyun.com": "腾讯云镜像加速站",
            }

        if get.registry_mirrors_address in com_reg_mirror:
            return public.return_message(0, 0, public.lang("Successfully set!"))

        remarks = get.remarks if "remarks" in get and get.remarks != "" else ("Custom mirrors" + 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("Updated common acceleration configuration successfully!")
        return public.return_message(0, 0, public.lang("Update successfully!"))

    def del_com_registry_mirror(self, get):
        """
        删除常用加速配置
        @param get:
        @return:
        """
        com_reg_mirror_file = "{}/class_v2/btdockerModelV2/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://docker.m.daocloud.io": "Third party image accelerator",
                # "https://mirror.ccs.tencentyun.com": "腾讯云镜像加速站",
            }

        if get.registry_mirrors_address not in com_reg_mirror:
            return public.return_message(0, 0, public.lang("successfully delete!"))

        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("Remove common acceleration configuration successfully!")
        return public.return_message(0, 0, public.lang("successfully delete!"))

    def get_monitor_status(self):
        """
        获取docker监控状态
        @return:
        """
        try:
            from YakPanel 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:
        """
        # 校验参数
        try:
            get.validate([
                Param('act').Require().String('in', ['start', 'stop']),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))

        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_v2/btdockerModelV2/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 started monitoring successfully!")
                self.add_monitor_cron(get)
                return public.return_message(0, 0, public.lang("Start monitoring successfully!"))
            return public.return_message(-1, 0, public.lang("Failed to start monitoring!"))
        else:
            from YakPanel 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.return_message(0, 0, public.lang("Docker monitoring stopped successfully!"))

    # 2024/1/4 上午 9:32 停止容器监控进程
    def stop_monitor(self, get):
        '''
            @name 名称/描述
            @param 参数名<数据类型> 参数描述
            @return 数据类型
        '''
        cmd_line = [
            "/www/server/panel/class_v2/btdockerModelV2/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)

        # 遍历目录下的所有非.log结尾的文件
        for filename in os.listdir(directory):
            if not filename.endswith(".log"):
                filepath = os.path.join(directory, filename)
                if os.path.isdir(filepath):
                    continue
                # 检查文件内容是否包含 "monitorModel.py"
                with open(filepath, 'r') as file:
                    content = file.read()
                    if "monitorModel.py" in content or "dk_monitor.py" in content:
                        # 删除原文件和对应的.log文件
                        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 monitoring stopped successfully!")

        public.M('crontab').where('name=?', ("[Do not delete] docker monitoring daemon",)).delete()
        return public.return_message(0, 0, public.lang("Docker monitoring stopped successfully!"))

    # 2023/12/7 下午 6:24 创建计划任务，监听监控进程是否存在，如果不存在则添加
    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', ("[Do not delete] docker monitoring daemon",)).count() == 0:
                p = crontab.crontab()
                llist = p.GetCrontab(None)

                if type(llist) == list:
                    for i in llist:
                        if i['name'] == '[Do not delete] docker monitoring daemon':
                            return

                get = {
                    "name": "[Do not delete] docker monitoring daemon",
                    "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_v2/btdockerModelV2/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_v2/btdockerModelV2/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.return_message(-1, 0, public.lang("docker-compose file path cannot be empty!"))

        if not os.path.exists(docker_compose_file):
            return public.return_message(-1, 0, public.lang("docker-compose file does not exist!"))

        public.ExecShell("chmod +x {}".format(docker_compose_file))
        cmd_result = public.ExecShell("{} --version".format(docker_compose_file))
        if not cmd_result[0]:
            return public.return_message(-1, 0, public.lang(
                "docker-compose file is not executable or is not a docker-compose file!"))

        docker_compose_path = "{}/class_v2/btdockerModelV2/config/docker_compose_path.pl".format(
            public.get_panel_path())

        public.writeFile(docker_compose_path, docker_compose_file)
        dp.write_log("Set docker-compose path successfully!")
        return public.return_message(0, 0, public.lang("Successfully set!"))

    # 2024/9/2 下午2:22 检查本机公网IP归属地是否为中国大陆IP
    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:
            # 如果获取失败，就默认为中国大陆IP
            flag = "1"

        return flag

    # 2024/9/2 下午3:00 安装docker和docker-compose
    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/yakpanel/apps/install_url.pl".format(public.get_url()), url_file)
                    url_body = public.readFile(url_file)
                    url = url_body.strip() if url_body else ""

        # 2024/3/28 上午 10:36 检测是否已存在安装任务
        if public.M('tasks').where('name=? and status=?', ("Install Docker Service", "-1")).count():
            return public.return_message(-1, 0, public.lang(
                "The installation task already exists, please do not add it again!"))

        mmsg = "Install Docker Service"
        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 commercial module"
        #     }, 3)

        # 提交安装统计
        import threading
        import requests
        threading.Thread(target=requests.post, kwargs={
            'url': '{}/api/panel/panel_count_daily'.format(public.OfficialApiBase()),
            'data': {
                'name': 'docker_installations',
            }}).start()

        return public.return_message(0, 0, public.lang("The installation task has been added to the queue!"))


    def uninstall_status(self, get):
        """
        检测docker是否可以卸载
        :param get:
        :return:
        """
        from btdockerModelV2 import containerModel
        docker_list = containerModel.main().get_list(get)['message']
        from btdockerModelV2 import imageModel
        images_list = imageModel.main().image_list(get)['message']
        if len(images_list) > 0 or len(docker_list["container_list"]) > 0:
            return public.return_message(0, 0, public.lang(
                "Please delete all containers and images before uninstalling docker!"))
        return public.return_message(0, 0, public.lang("Can be uninstalled!"))

    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.return_message(0, 0, public.lang("Uninstall successful!"))

    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 commercial module"
        #     }, 3)
        return public.return_message(0, 0, public.lang("The repair task has been added to the queue!"))

    def get_daemon_json(self):
        """
        获取daemon.json配置信息
        @param get:
        @return:
        """

        if not os.path.exists(self.__CONFIG_FILE):
            public.ExecShell("mkdir -p /etc/docker")
            public.ExecShell("touch /etc/docker/daemon.json")
            return {}

        try:
            return json.loads(public.readFile(self.__CONFIG_FILE))
        except Exception as e:
            print(e)
            return {}

    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.return_message(0, 0, public.lang("Saved successfully!"))

        try:
            conf = json.loads(get.daemon_json)
            public.writeFile(self.__CONFIG_FILE, json.dumps(conf, indent=2))
            dp.write_log("Save daemon.json configuration successfully!")
            return public.return_message(0, 0, public.lang("Saved successfully!"))
        except Exception as e:
            public.print_log("err: {}".format(e))
            if "Expecting property name enclosed in double quotes" in str(e):
                return public.return_message(-1, 0, public.lang(
                    "Saving failed, reason: daemon.json configuration file format error!"))

            return public.return_message(-1, 0, public.lang("Save failed, reason: {}", e))

    def set_docker_global(self, get):
        '''
        docker全局配置接口
        根据传值进行不同操作的写入/etc/docker/daemon.json
        全局设置后都需要重启docker生效
        '''

        # 配置项与对应的处理方法的映射
        '''
        加速URL  默认 https://docker.1ms.run
        私有仓库  warehouse
        日志切割  log_cutting  {"max-file":"2","max-size":"10m"}  保留份数  文件大小,  关闭功能:log_cutting {}
        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.return_message(-1, 0, public.lang("A configuration item that doesn't exist!"))

    def set_docker_root_dir(self, get):
        '''
        设置docker根目录
        @param get: docker_root_dir
        @return:
        '''
        try:
            # 停止docker服务
            get.act = "stop"
            self.docker_service(get)

            # 获取当前和目标Docker根目录
            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.return_message(-1, 0, public.lang("The Docker root cannot be empty！"))

            if current_root_dir == new_root_dir:
                return public.return_message(0, 0, public.lang("The current Docker root directory is the target directory and does not need to be changed!"))

            # 检查是否需要移动现有的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进行拷贝
                    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("File size mismatch, data loss is possible!")

                    # 拷贝成功后删除源目录
                    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))

                    # 重启docker服务
                    get.act = "start"
                    self.docker_service(get)

                    return public.return_message(-1, 0, public.lang(
                        "The move failed, the original configuration has been restored, the reason:{}", e))

            # 写入新的Docker根目录到配置文件
            data["data-root"] = new_root_dir
            public.writeFile(self.__CONFIG_FILE, json.dumps(data, indent=2))

            # 重启docker服务
            get.act = "start"
            self.docker_service(get)
            return public.return_message(0, 0, public.lang("Setup successful!"))
        except Exception as e:
            return public.return_message(-1, 0, public.lang("Setup failed!{}", e))

    def set_proxy(self, get):
        '''
        设置全局代理
        @param get: proxy_settings
        @return:
        '''
        try:
            # 从 JSON 字符串中解析代理设置
            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.return_message(-1, 0, public.lang("The JSON format of the proxy settings is wrong!"))

            data = self.get_daemon_json()

            if not proxy_settings and "http-proxy" in data:
                del data["http-proxy"]
                del data["https-proxy"]
                del data["no-proxy"]
            else:
                data["http-proxy"] = http_proxy
                data["https-proxy"] = https_proxy
                data["no-proxy"] = no_proxy

            public.writeFile(self.__CONFIG_FILE, json.dumps(data, indent=2))

            # 重载全局配置文件
            self.reload_global_config()

            # 重启docker服务
            get.act = "restart"
            self.docker_service(get)
            return public.return_message(0, 0, public.lang("Proxy setup successful!"))
        except Exception as e:
            return public.return_message(-1, 0, public.lang("Setup failed!{}", 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))

            #  重启docker服务
            get.act = "restart"
            self.docker_service(get)
            return public.return_message(0, 0, public.lang("Setup successful!"))
        except Exception as e:
            return public.return_message(-1, 0, public.lang("Setup failed!{}", 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 = {}

            # 确保 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))

            #  重启docker服务
            get.act = "restart"
            self.docker_service(get)
            return public.return_message(0, 0, public.lang("Setup successful!"))
        except Exception as e:
            return public.return_message(-1, 0, public.lang("Setup failed!{}", 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))

            #  重启docker服务
            get.act = "restart"
            self.docker_service(get)
            return public.return_message(0, 0, public.lang("Setup successful!"))
        except Exception as e:
            return public.return_message(-1, 0, public.lang("Setup failed!{}", 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))

            #  重启docker服务
            get.act = "restart"
            self.docker_service(get)
            return public.return_message(0, 0, public.lang("Setup successful!"))
        except Exception as e:
            return public.return_message(-1, 0, public.lang("Setup failed!{}", 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))

            #  重启docker服务
            get.act = "restart"
            self.docker_service(get)
            return public.return_message(0, 0, public.lang("Setup successful!"))
        except Exception as e:
            return public.return_message(-1, 0, public.lang("Setup failed!{}", e))


    def set_ipv6_global(self, get):
        '''
        设置全局开启ipv6
        @param get:
        @return:  dict
        '''
        try:
            status = get.get("status/d", 0)
            ipaddr = get.get("ipaddr", "")
            if not os.path.exists("/etc/docker"): public.ExecShell("mkdir -p /etc/docker")
            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.return_message(-1, 0, public.lang("Please enter the correct I Pv 6 address!"))
                    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))

            #  重启docker服务
            get.act = "restart"
            self.docker_service(get)

            return public.return_message(0, 0, public.lang("Setup Successful!"))
        except Exception as e:
            return public.return_message(-1, 0, public.lang("Setup failed!{}", 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:
            # 尝试解析IPv6子网
            ipaddress.IPv6Network(subnet, strict=False)
            return True
        except (ipaddress.AddressValueError, ValueError) as e:
            # 解析失败，不是合法的IPv6子网
            return False

    # 2024/12/16 17:49 获取系统信息
    def get_system_info(self, get):
        '''
            @name 获取系统信息
        '''
        from btdockerModelV2.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, the version number cannot be obtained correctly!"

            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.return_message(0, 0, system_info)
        # 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.return_message(0, 0, public.lang(
                        "The upgrade process PID is being executed, please wait for it to complete!"))
            except:
                pass

            if os.path.exists(log_file):
                public.writeFile(log_file, "")

            if os.path.exists(pid_file):
                os.remove(pid_file)
            # 获取当前compose版本
            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.return_message(-1, 0, public.lang(
                        "Getting the current compose version failed! Reasons for failure:{}", stderr))

            # 写入缓存compose version 版本文件
            public.set_cache_func(key, compose_version.strip())

            github_version = public.cache_get(skey)
            if not public.cache_get(skey):
                # 获取github最新compose版本
                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.return_message(-1, 0, public.lang(
                        "Getting the latest compose version on github failed! Reasons for failure:{}", stderr))
                # 写入缓存github version 版本
                public.cache_set(skey, github_version.strip(), 86400)

            if compose_version.strip() == github_version.strip():
                return public.return_message(-1, 0, public.lang(
                    "The current compose version is consistent with the latest version on github, no upgrade required!"))

            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.return_message(0, 0, public.lang(
                "Upgrading docker-compose version, check the logs later! Latest Version:{}", github_version.strip()))

        except Exception as e:
            return public.return_message(-1, 0, public.lang("Upgrade failed! Reasons for failure:{}", e))

    @staticmethod
    def reload_global_config():
        '''
        重载全局配置文件
        '''
        try:
            # 执行重载命令
            stdout, stderr = public.ExecShell("systemctl reload docker")
            if stderr:
                return public.return_message(-1, 0, public.lang("Overload fails due to:{}", stderr))
            return public.return_message(0, 0, public.lang("Reload successful!"))
        except Exception as e:
            error_message = "Global profile overload failed:{}".format(e)
            dp.write_log(error_message)
            return public.return_message(-1, 0, public.lang("Global profile overload failed:{}", e))

    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.return_message(-1, 0, public.lang("Wrong parameter, path parameter!"))

        if get.path.strip() == "":
            get.path = "/www/dk_project"

        project_path_file = "{}/class_v2/btdockerModelV2/config/project_path.pl".format(public.get_panel_path())

        res = public.writeFile(project_path_file, get.path)
        if not res:
            return public.return_message(-1, 0, public.lang("Setup failed!"))
        return public.return_message(0, 0, public.lang("Setup successful!"))
