Files
aaPanel/class_v2/btdockerModelV2/setupModel.py
T
Jack ed55fa708d Update to 7.7.0
Since version 7.7.0, we recommend yours update python to 3.12.

[+] Using nginx technology to load static files improves access speed
[+] Refactor homepage, website, FTP, and database using vue3
[+] Table loading changed to skeleton screen
[+] Add Website statistics-v2 professional plug-in
[+] Add Home page - top 5 resource occupancy
[+] Add protection for Files management (requires Tamper-proof for Enterprise 3.7)
[+] Website, FTP, Databases page add program status
[+] Add FTP log analysis (only supports Centos)
[+] Add password-free login to phpMyAdmin
[+] Add Proxy Project in Website (Supported when web service uses Nginx)
[+] Add WP Toolkit (Pro version only)
[+] Redesigned Docker module
[+] Add WP Toolkit Protection
[+] Add WP Toolkit Backup and Restore
[+] Add WP Toolkit Migrated
[+] Add WP Toolkit Clone site (supports new domain and subdomain)
[+] Add WP Toolkit Create site from backup of other panel
[+] Add WP Toolkit support for Cron automatic backup (only save Local disk)
[+] Add WP Toolkit operation log
[+] Add Integrity check for WP Toolkit
[+] Add WP Toolkit plug-in management and themes management

[*] Optimize phpMyAdmin formula access method
[*] Optimize Home page PHP display problem
[*] Optimize jump to the login interface after the login expires
[*] Optimize automatic renewal of SSL at some times
[*] Optimize Let's Encrypt to increase application success rate

[-] Fix Logs Audit cannot be opened
[-] Fix apache URL rewrite issue
[-] Fix phpmyadmin installation problem
[-] Fix the problem that some servers cannot install software
[-] Fix upload file error
[-] Fix left menu hiding problem
[-] Fix aaPanel Mobile QR code display problem
[-] Fix problem that third-party plug-ins are not displayed in the App Store
[-] Fix issue where the menu bar is blank when opening new tabs
[-] Fixed panel not being accessible in some cases
[-] Fix the issue where Curl warning caused the inability to apply for SSL
[-] Fix Quota issues for Website, FTP, Databases
[-] Fix file interface display problem on mobile terminal
2024-07-19 11:25:10 +08:00

687 lines
26 KiB
Python

# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import gettext
_ = gettext.gettext
import json
import os
import public
from btdockerModelV2 import dk_public as dp
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param
class main(dockerBase):
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:
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")
service_status = self.get_service_status()
if not service_status:
service_status = self.get_service_status()
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": "/etc/docker/daemon.json",
"installing": installing,
}
return public.return_message(0, 0, data)
@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:
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, _( "The monitoring save time needs to be a positive integer!"))
if save_date > 999:
return public.return_message(-1, 0, _( "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, _( "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, _( "Successfully set!"))
def get_service_status(self):
sock = '/var/run/docker.pid'
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:
"""
import public
act_dict = {'start': 'start', 'stop': 'stop', 'restart': 'restart'}
if get.act not in act_dict:
return public.return_message(-1, 0, _( "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, _("Docker service setup failed, please turn off aapanel anti-intrusion and try again!"))
return public.return_message(-1, 0, _("Setup failed! Reason for failure:{}".format(stderr)))
if get.act != "stop":
service_status = self.get_service_status()
if not service_status:
import time
public.ExecShell("systemctl stop docker")
public.ExecShell("systemctl stop docker.socket")
time.sleep(1)
public.ExecShell("systemctl start docker")
dp.write_log("Set the Docker service status to [{}]".format(act_dict[get.act]))
return public.return_message(0, 0, _("{} success".format(act_dict[get.act])))
# 获取加速配置
def get_registry_mirrors(self, get):
"""
获取镜像加速信息
@param get:
@return:
"""
try:
if not os.path.exists('/etc/docker/daemon.json'):
reg_mirrors = []
else:
conf = json.loads(public.readFile('/etc/docker/daemon.json'))
if "registry-mirrors" not in conf:
reg_mirrors = []
else:
reg_mirrors = conf['registry-mirrors']
except:
reg_mirrors = []
com_reg_mirrors = self._get_com_registry_mirrors()
# return {
# "registry_mirrors": reg_mirrors,
# "com_reg_mirrors": com_reg_mirrors
# }
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 = {}
if os.path.exists('/etc/docker/daemon.json'):
try:
conf = json.loads(public.readFile('/etc/docker/daemon.json'))
except Exception as e:
return public.return_message(-1, 0, _( "Global configuration file error, please check {}!".format(str(e))))
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, _( 'Speedup address [{}] Format error <br> Reference: https://mirror.ccs.tencentyun.com'.format(registry_mirrors)))
conf['registry-mirrors'] = public.xsssec2(registry_mirrors)
if isinstance(conf['registry-mirrors'], str):
conf['registry-mirrors'] = [conf['registry-mirrors']]
public.writeFile('/etc/docker/daemon.json', json.dumps(conf, indent=2))
if get.registry_mirrors_address != "":
self.update_com_registry_mirrors(get)
dp.write_log("Setup Docker acceleration successful!")
return public.return_message(0, 0, _( 'successfully set'))
except:
return public.return_message(-1, 0, _('Setup failed! Failure reason :{}'.format(public.get_error_info())))
def update_com_registry_mirrors(self, get):
"""
更新常用加速配置
@param get:
@return:
"""
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",
}
if get.registry_mirrors_address in com_reg_mirror:
return public.return_message(0, 0, _( "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, _( "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",
}
if get.registry_mirrors_address not in com_reg_mirror:
return public.return_message(0, 0, _( "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, _( "successfully delete!"))
def get_monitor_status(self):
"""
获取docker监控状态
@return:
"""
try:
from BTPanel import cache
except:
from cachelib import SimpleCache
cache = SimpleCache()
skey = "docker_monitor_status"
result = cache.get(skey)
if isinstance(result, bool):
return result
import psutil
is_monitor = False
for proc in psutil.process_iter():
try:
pinfo = proc.as_dict(attrs=['pid', 'name'])
if "monitorModel.py" in pinfo['name']:
is_monitor = True
except psutil.NoSuchProcess:
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, _( "Start monitoring successfully!"))
return public.return_message(-1, 0, _( "Failed to start monitoring!"))
else:
from BTPanel import cache
skey = "docker_monitor_status"
cache.set(skey, False)
if os.path.exists(self.moinitor_lock):
os.remove(self.moinitor_lock)
self.stop_monitor(get)
return public.return_message(0, 0, _( "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.returnMsg(True, "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):
# public.print_log("mwmwmwmwm 没文件")
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, _( "docker-compose file path cannot be empty!"))
if not os.path.exists(docker_compose_file):
return public.return_message(-1, 0, _( "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, _( "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, _( "Successfully set!"))
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)
# 2024/3/28 上午 10:36 检测是否已存在安装任务
if public.M('tasks').where('name=? and status=?', ("Install Docker Service", "-1")).count():
return public.return_message(-1, 0, _( "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 = "/bin/bash /www/server/panel/install/install_soft.sh 0 install docker_bin "
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, _( "The installation task has been added to the queue!"))
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, _( "The repair task has been added to the queue!"))
def get_daemon_json(self, get):
"""
获取daemon.json配置信息
@param get:
@return:
"""
daemon_json = "/etc/docker/daemon.json"
if not os.path.exists(daemon_json):
return public.return_message(0, 0, "")
try:
return public.return_message(0, 0, json.loads(public.readFile(daemon_json)))
except Exception as e:
print(e)
return public.return_message(-1, 0, "")
def save_daemon_json(self, get):
"""
保存daemon.json配置信息,保存前备份,验证可以成功执行后再替换
@param get:
@return:
"""
daemon_json = "/etc/docker/daemon.json"
if getattr(get, "daemon_json", "") == "":
public.ExecShell("rm -f {}".format(daemon_json))
return public.return_message(0, 0, _( "Saved successfully!"))
try:
conf = json.loads(get.daemon_json)
public.writeFile(daemon_json, json.dumps(conf, indent=2))
dp.write_log("Save daemon.json configuration successfully!")
return public.return_message(0, 0, _( "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, _( "Saving failed, reason: daemon.json configuration file format error!"))
return public.return_message(-1, 0, _( "Save failed, reason: {}".format(e)))
def uninstall_status(self, get):
"""
检测docker是否可以卸载
:param get:
:return:
"""
from btdockerModelV2 import containerModel
docker_list = containerModel.main().get_list(get)
from btdockerModelV2 import imageModel
images_list = imageModel.main().image_list(get)
if len(images_list) > 0 or len(docker_list["container_list"]) > 0:
return public.return_message(0, 0, {"status": False,
"msg": "Please manually delete all containers and images before uninstalling!"})
return public.return_message(0, 0, "Allow uninstallation")
def uninstall_status1(self, get):
"""
检测docker是否可以卸载
:param get:
:return:
"""
from btdockerModelV2 import containerModel
docker_list = containerModel.main().get_list(get)
from btdockerModelV2 import imageModel
images_list = imageModel.main().image_list(get)
if len(images_list) > 0 or len(docker_list["container_list"]) > 0:
return False
return True
def uninstall_docker_program(self, get):
"""
卸载docker和docker-compose
:param get:
:return:
"""
type = get.get("type/d", 0)
if type == 0:
uninstall_status = self.uninstall_status1(get)
if not uninstall_status["status"]:
return public.return_message(-1, 0, _( "Please manually delete all containers and images before uninstalling!"))
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, "Uninstall successfully!")