1373 lines
54 KiB
Python
1373 lines
54 KiB
Python
|
|
# 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!"))
|