Files

1373 lines
54 KiB
Python
Raw Permalink Normal View History

2026-04-07 02:04:22 +05:30
# 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!"))