Update to v8.7.0

This commit is contained in:
aapanel.com
2026-04-16 18:17:07 +08:00
parent c3f2d7566c
commit ee92fa2dbd
2472 changed files with 33631 additions and 20919 deletions
+286 -6
View File
@@ -10,6 +10,8 @@
# ------------------------------
# Docker模型
# ------------------------------
import uuid
import subprocess
import json
import os
import time
@@ -26,6 +28,289 @@ from public.validate import Param
class main(dockerBase):
def _compose_task_dir(self):
return "{}/data/docker_compose_tasks".format(public.get_panel_path())
def _compose_task_file(self, task_id):
return "{}/{}.json".format(self._compose_task_dir(), task_id)
def _compose_task_log(self, task_id):
return "{}/{}.log".format(self._compose_task_dir(), task_id)
def _ensure_compose_task_dir(self):
task_dir = self._compose_task_dir()
if not os.path.exists(task_dir):
os.makedirs(task_dir, 0o755, True)
return task_dir
def _write_task_meta(self, task_id, data):
self._ensure_compose_task_dir()
task_file = self._compose_task_file(task_id)
tmp_file = task_file + ".tmp"
public.writeFile(tmp_file, json.dumps(data, ensure_ascii=False))
os.replace(tmp_file, task_file)
def _read_task_meta(self, task_id):
task_file = self._compose_task_file(task_id)
if not os.path.exists(task_file):
return None
content = public.readFile(task_file)
if not content:
return None
try:
return json.loads(content)
except Exception:
return None
def _update_task_meta(self, task_id, **kwargs):
task_data = self._read_task_meta(task_id)
if not task_data:
return
task_data.update(kwargs)
self._write_task_meta(task_id, task_data)
def _append_task_log(self, task_id, text):
log_file = self._compose_task_log(task_id)
if isinstance(text, bytes):
text = text.decode('utf-8', errors='ignore')
if not text.endswith("\n"):
text += "\n"
public.writeFile(log_file, text, "a+")
def _run_cmd_and_stream(self, cmd, task_id, cwd=None):
p = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=cwd,
text=True,
bufsize=1,
encoding='utf-8',
errors='ignore'
)
while True:
line = p.stdout.readline() if p.stdout else ""
if line:
self._append_task_log(task_id, line.rstrip("\n"))
if p.poll() is not None:
break
if p.stdout:
remain = p.stdout.read()
if remain:
self._append_task_log(task_id, remain.rstrip("\n"))
return p.returncode
def _create_project_in_path_stream(self, name, path, task_id):
workdir = "/".join(path.split("/")[:-1])
cmd = ["/usr/bin/docker-compose", "-p", name, "up", "-d"]
return self._run_cmd_and_stream(cmd, task_id, cwd=workdir)
def _create_project_in_file_stream(self, project_name, file, task_id):
project_path = "{}/{}".format(self.compose_path, project_name)
project_file = "{}/docker-compose.yaml".format(project_path)
if not os.path.exists(project_path):
os.makedirs(project_path)
template_content = public.readFile(file)
public.writeFile(project_file, template_content)
cmd = ["/usr/bin/docker-compose", "-p", project_name, "-f", project_file, "up", "-d"]
return self._run_cmd_and_stream(cmd, task_id)
def _cleanup_compose_tasks(self):
task_dir = self._compose_task_dir()
if not os.path.exists(task_dir):
return
now = int(time.time())
for file_name in os.listdir(task_dir):
if not file_name.endswith(".json"):
continue
task_id = file_name[:-5]
task_data = self._read_task_meta(task_id)
if not task_data:
continue
status = task_data.get("status", "")
ended_at = int(task_data.get("ended_at", 0) or 0)
created_at = int(task_data.get("created_at", 0) or 0)
base_time = ended_at or created_at
# 成功任务保留 1 小时
# 失败任务保留 24 小时
if status == "success":
expire = 3600
elif status == "failed":
expire = 86400
else:
continue
if base_time and now - base_time > expire:
task_file = self._compose_task_file(task_id)
log_file = self._compose_task_log(task_id)
if os.path.exists(task_file):
os.remove(task_file)
if os.path.exists(log_file):
os.remove(log_file)
def create_async(self, get):
try:
get.validate([
Param('template_id').Require().Integer(),
Param('project_name').Require().String().Xss(),
Param('remark').Require().String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
return public.return_message(-1, 0, str(ex))
# 清理旧日志和文件
self._cleanup_compose_tasks()
self._ensure_compose_task_dir()
task_dir = self._compose_task_dir()
for file_name in os.listdir(task_dir):
if not file_name.endswith('.json'):
continue
try:
content = public.readFile("{}/{}".format(task_dir, file_name))
if not content:
continue
item = json.loads(content)
if item.get('project_name') == public.xsssec(get.project_name) and item.get('status') == 'deploying':
return public.return_message(-1, 0, public.lang(
"There is already a deployment task in progress for this project"))
except Exception:
continue
task_id = "dc_{}_{}".format(int(time.time()), uuid.uuid4().hex[:8])
log_file = self._compose_task_log(task_id)
self._write_task_meta(task_id, {
"task_id": task_id,
"project_name": public.xsssec(get.project_name),
"status": "deploying",
"result_msg": "",
"error_msg": "",
"log_file": log_file,
"created_at": int(time.time()),
"started_at": 0,
"ended_at": 0,
"request_data": {
"template_id": int(get.template_id),
"project_name": str(get.project_name),
"remark": str(get.remark)
}
})
public.writeFile(log_file, "", "w+")
python_bin = "/www/server/panel/pyenv/bin/python"
if not os.path.exists(python_bin):
python_bin = "python3"
cmd = "nohup {} /www/server/panel/script/docker_compose_async_task.py --task-id {} > /dev/null 2>&1 &".format(
python_bin, task_id
)
public.ExecShell(cmd)
return public.return_message(0, 0, {
"task_id": task_id,
"status": "deploying"
})
# def task_status(self, get):
# task_id = get.get("task_id/s", "")
# if not task_id:
# return public.return_message(-1, 0, public.lang("Please pass task_id"))
# task_data = self._read_task_meta(task_id)
# if not task_data:
# return public.return_message(-1, 0, public.lang("Task does not exist"))
# return public.return_message(0, 0, {
# "task_id": task_id,
# "status": task_data.get("status", "failed"),
# "result_msg": task_data.get("result_msg", ""),
# "error_msg": task_data.get("error_msg", "")
# })
def task_logs(self, get):
task_id = get.get("task_id/s", "")
if not task_id:
return public.return_message(-1, 0, public.lang("Please pass task_id"))
task_data = self._read_task_meta(task_id)
if not task_data:
return public.return_message(-1, 0, public.lang("Task does not exist"))
log_file = task_data.get("log_file", self._compose_task_log(task_id))
logs = ""
if os.path.exists(log_file):
logs = public.readFile(log_file)
if not logs:
logs = ""
return public.return_message(0, 0, {
"task_id": task_id,
"status": task_data.get("status", "failed"),
"logs": logs,
"result_msg": task_data.get("result_msg", ""),
"error_msg": task_data.get("error_msg", "")
})
def run_create_task(self, task_id):
task_data = self._read_task_meta(task_id)
if not task_data:
return
self._update_task_meta(task_id, status='deploying', started_at=int(time.time()))
req = task_data.get('request_data', {})
get = public.to_dict_obj(req)
try:
self._append_task_log(task_id, "[INFO] Start deploying project: {}".format(get.project_name))
project_name = public.md5(public.xsssec(get.project_name))
template_id = get.template_id
template_info = dp.sql("templates").where("id=?", template_id).find()
if len(template_info) < 1:
raise Exception(public.lang("This template was not found, or file is corrupt!"))
if not os.path.exists(template_info['path']):
raise Exception(public.lang("Template file does not exist"))
template_exist = dp.sql("stacks").where("template_id=?", (template_id,)).find()
if template_exist:
raise Exception(public.lang(
"Template [{}] has been deployed by project: [{}], please change a template and try again!",
template_info['name'], template_exist['name']
))
name_exist = self.check_project_container_name(public.readFile(template_info['path']), get)
if name_exist:
raise Exception(name_exist['message'])
stacks_info = dp.sql("stacks").where("name=?", (public.xsssec(get.project_name))).find()
if stacks_info:
raise Exception(public.lang("The project name already exists!"))
dp.sql("stacks").insert({
"name": public.xsssec(get.project_name),
"status": "1",
"path": template_info['path'],
"template_id": template_id,
"time": time.time(),
"remark": public.xsssec(get.remark)
})
self._append_task_log(task_id, "[INFO] Stack metadata inserted")
if template_info.get('add_in_path') == 1:
code = self._create_project_in_path_stream(project_name, template_info['path'], task_id)
else:
code = self._create_project_in_file_stream(project_name, template_info['path'], task_id)
if code != 0:
raise Exception("docker-compose up failed with code {}".format(code))
check_shell = "/usr/bin/docker-compose -p {} ps -q".format(project_name)
container_ids, _ = public.ExecShell(check_shell)
if not container_ids.strip():
raise Exception("Docker deployment failed. Please check logs")
dp.write_log("Project [{}] deployed successfully!".format(public.xsssec(get.project_name)))
public.set_module_logs('docker', 'add_project', 1)
self._append_task_log(task_id, "[INFO] Deployment finished successfully")
self._update_task_meta(
task_id,
status='success',
result_msg=public.lang("Successful deployment!"),
error_msg='',
ended_at=int(time.time())
)
except Exception as ex:
dp.sql("stacks").where("name=?", (public.xsssec(get.project_name),)).delete()
err = str(ex)
self._append_task_log(task_id, "[ERROR] {}".format(err))
self._update_task_meta(
task_id,
status='failed',
result_msg='',
error_msg=err,
ended_at=int(time.time())
)
public.print_log(traceback.format_exc())
def get_docker_compose_version(self):
try:
import subprocess
@@ -313,12 +598,7 @@ class main(dockerBase):
data = dp.sql("templates").where("id=?", (get.template_id,)).find()
if not data:
return public.return_message(-1, 0, public.lang("This template was not found!"))
if os.path.exists(data['path']):
# 删除模板文件 目录不处理
try:
os.remove(data['path'])
except:
pass
get.id = get.template_id