mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
1. Added Mail Marketing Automation trigger tasks 2. Added Mail Marketing Groups Import, Export, Merge 3. Added Mail Marketing Subscribers to support paste import 4. Added Mail Marketing Template Import Export Duplicate 5. Added Mail server add timed automatic reply 6. Added HTTPS Protection function for Website (disables automatic HTTP to HTTPS redirection when enabled) 7. Optimize Website Interface Button Integration 8. Optimize the response speed of WP Toolkit interface
294 lines
9.8 KiB
Python
294 lines
9.8 KiB
Python
import json
|
|
import os
|
|
import shutil
|
|
import tarfile
|
|
import time
|
|
from hashlib import md5
|
|
from typing import Optional, List, Union, Dict, Any
|
|
|
|
from .util import DB, ExecShell, write_file, write_log, read_file
|
|
|
|
|
|
class VersionTool:
|
|
_config_file = "/www/server/panel/data/version_config.json"
|
|
|
|
def __init__(self):
|
|
self._config: Optional[Dict[str, List[Dict[str, Any]]]] = None
|
|
self._pack_class = BasePack
|
|
self.pack_path = "/www/backup/versions"
|
|
if not os.path.isdir(self.pack_path):
|
|
os.makedirs(self.pack_path)
|
|
|
|
@property
|
|
def config(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
if self._config is not None:
|
|
return self._config
|
|
|
|
data = {}
|
|
try:
|
|
res = read_file(self._config_file)
|
|
if isinstance(res, str):
|
|
data = json.loads(res)
|
|
except (json.JSONDecoder, TypeError, ValueError):
|
|
pass
|
|
self._config = data
|
|
return self._config
|
|
|
|
def save_config(self):
|
|
if self._config is not None:
|
|
write_file(self._config_file, json.dumps(self._config))
|
|
|
|
def add_to_config(self, data: dict):
|
|
project_name = data.get("project_name")
|
|
self._config = None
|
|
if project_name not in self.config:
|
|
self.config[project_name] = []
|
|
self.config[project_name].append(data)
|
|
self.save_config()
|
|
|
|
def set_pack_class(self, pack_cls):
|
|
self._pack_class = pack_cls
|
|
|
|
def version_list(self, project_name: str):
|
|
if project_name in self.config:
|
|
return self.config[project_name]
|
|
return []
|
|
|
|
def get_version_info(self, project_name: str, version: str) -> Optional[dict]:
|
|
if project_name in self.config:
|
|
for i in self.config[project_name]:
|
|
if i.get("version") == version:
|
|
return i
|
|
return None
|
|
|
|
# 把某个路径下的文件打包并发布为一个版本
|
|
def publish_by_src_path(self,
|
|
project_name: str, # 名称
|
|
src_path: str, # 源路径
|
|
version: str, # 版本号
|
|
ps: Optional[str] = None, # 备注
|
|
other: Optional[dict] = None, # 其他信息
|
|
sync: bool = False, # 是否同步执行
|
|
):
|
|
|
|
if project_name in self.config:
|
|
for i in self.config[project_name]:
|
|
if i["version"] == version:
|
|
return "The current version already exists"
|
|
if not os.path.isdir(src_path):
|
|
return "The source path does not exist"
|
|
|
|
if ps is None:
|
|
ps = ''
|
|
|
|
if other is None:
|
|
other = {}
|
|
|
|
zip_name = "{}_{}.tar.gz".format(
|
|
os.path.basename(src_path), time.strftime('%Y%m%d_%H%M%S', time.localtime())
|
|
)
|
|
|
|
data = {
|
|
"project_name": project_name,
|
|
"version": version,
|
|
"ps": ps,
|
|
"other": other,
|
|
"zip_name": zip_name,
|
|
"backup_time": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
|
|
}
|
|
return self._pack_class(src_path, self.pack_path, zip_name, sync=sync, vt=self, data=data)(**other)
|
|
|
|
def recover(self,
|
|
project_name: str, # 名称
|
|
version: str, # 版本
|
|
target_path: str, # 目标路径
|
|
run_path=None
|
|
):
|
|
if not run_path:
|
|
run_path = target_path
|
|
if project_name not in self.config:
|
|
return 'The project does not exist'
|
|
|
|
target = None
|
|
for i in self.config[project_name]:
|
|
if i["version"] == version:
|
|
target = i
|
|
break
|
|
|
|
if target is None:
|
|
return 'Version does not exist'
|
|
|
|
file = os.path.join(self.pack_path, target["zip_name"])
|
|
if not os.path.exists(file):
|
|
return 'Version file missing'
|
|
|
|
tmp_path = '/tmp/version_{}'.format(int(time.time()))
|
|
tar = tarfile.open(file, mode='r')
|
|
tar.extractall(tmp_path)
|
|
user_data = None
|
|
if os.path.exists(target_path):
|
|
ExecShell("chattr -i -R {}/".format(target_path))
|
|
user_data = read_file(run_path + "/.user.ini")
|
|
ExecShell("rm -rf {}".format(target_path))
|
|
os.makedirs(target_path)
|
|
if not os.path.exists(target_path):
|
|
os.makedirs(target_path)
|
|
ExecShell(r"\cp -rf {}/* {}".format(tmp_path, target_path))
|
|
if user_data:
|
|
write_file(target_path + "/.user.ini", run_path)
|
|
ExecShell("chattr +i {}/.user.ini".format(run_path))
|
|
ExecShell("rm -rf {}".format(tmp_path))
|
|
return True
|
|
|
|
def publish_by_file(self,
|
|
project_name: str, # 名称
|
|
src_file: str, # 源路径
|
|
version: str, # 版本号
|
|
ps: Optional[str] = None, # 备注
|
|
other: Optional[dict] = None, # 其他信息
|
|
):
|
|
|
|
if project_name in self.config:
|
|
for i in self.config[project_name]:
|
|
if i["version"] == version:
|
|
return "The current version already exists"
|
|
|
|
if not os.path.isfile(src_file):
|
|
return "The source path does not exist"
|
|
|
|
if ps is None:
|
|
ps = ''
|
|
|
|
if other is None:
|
|
other = {}
|
|
|
|
zip_name = os.path.basename(src_file)
|
|
|
|
data = {
|
|
"project_name": project_name,
|
|
"version": version,
|
|
"ps": ps,
|
|
"other": other,
|
|
"zip_name": zip_name,
|
|
"backup_time": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
|
|
}
|
|
try:
|
|
shutil.copy(src_file, self.pack_path + "/" + zip_name)
|
|
except:
|
|
return "File save failed"
|
|
self.add_to_config(data)
|
|
return None
|
|
|
|
def remove(self,
|
|
project_name: str, # 名称
|
|
version: str, # 版本
|
|
) -> Optional[str]:
|
|
|
|
if project_name not in self.config:
|
|
return 'The project does not exist'
|
|
|
|
target = None
|
|
for i in self.config[project_name]:
|
|
if i["version"] == version:
|
|
target = i
|
|
break
|
|
|
|
if target is None:
|
|
return 'Version does not exist'
|
|
|
|
file = os.path.join(self.pack_path, target["zip_name"])
|
|
if os.path.isfile(file):
|
|
os.remove(file)
|
|
|
|
self.config[project_name].remove(target)
|
|
|
|
self.save_config()
|
|
return None
|
|
|
|
def set_ps(self, name: str, version: str, ps: str):
|
|
[i.update({'ps': ps}) for i in self.config[name] if i["version"] == version]
|
|
self.save_config()
|
|
return True
|
|
|
|
|
|
class BasePack:
|
|
exec_log_file = "/tmp/project_pack.log"
|
|
|
|
def __init__(self, src_path, target_path, zip_name, sync=False, vt: VersionTool = None, data: dict = None):
|
|
self.src_path = src_path
|
|
self.target_path = target_path
|
|
self.zip_name = zip_name
|
|
self.sync = sync
|
|
self.v = vt
|
|
self._add_data = data
|
|
|
|
def save_config(self):
|
|
self.v.add_to_config(self._add_data)
|
|
|
|
def __call__(self, *args, **kwargs) -> Optional[str]:
|
|
if not os.path.exists(self.src_path):
|
|
return "The source path does not exist"
|
|
target_path = "/www/backup/versions"
|
|
|
|
if not os.path.isdir(target_path):
|
|
os.makedirs(target_path)
|
|
if self.sync:
|
|
return self._sync_backup(self.src_path, target_path, self.zip_name)
|
|
else:
|
|
return self._async_backup(self.src_path, target_path, self.zip_name)
|
|
|
|
def _sync_backup(self, src: str, target_path: str, zip_name: str) -> Optional[str]:
|
|
try:
|
|
write_file(self.exec_log_file, "")
|
|
execStr = ("cd {} && "
|
|
"tar -zcvf '{}' --exclude=.user.ini ./ 2>&1 > {} \n"
|
|
"echo '---The packaging execution is complete---' >> {}"
|
|
).format(src, os.path.join(target_path, zip_name), self.exec_log_file, self.exec_log_file)
|
|
ExecShell(execStr)
|
|
self.save_config()
|
|
except:
|
|
return "The packaging execution failed"
|
|
|
|
def _async_backup(self, src: str, target_path: str, zip_name: str):
|
|
import threading
|
|
hash_name = self._hash_src_name(src)
|
|
backup_tip_path = "/tmp/mod_version_tip"
|
|
if os.path.exists(backup_tip_path):
|
|
os.makedirs(backup_tip_path)
|
|
|
|
tip_file = os.path.join(backup_tip_path, hash_name)
|
|
if os.path.isfile(tip_file):
|
|
mtime = os.stat(tip_file).st_mtime
|
|
if time.time() - mtime > 60 * 20: # 20 分钟未执行,认为出现在不可抗力,导致备份失败,允许再次备份
|
|
os.remove(tip_file)
|
|
else:
|
|
return "Packing is in progress, please do not proceed"
|
|
|
|
write_file(tip_file, "")
|
|
|
|
def _back_p():
|
|
try:
|
|
write_file(self.exec_log_file, "")
|
|
execStr = ("cd {} && "
|
|
"tar -zcvf '{}' --exclude=.user.ini ./ 2>&1 > {} \n"
|
|
"echo '---Backup execution completed---' >> {}"
|
|
).format(src, os.path.join(target_path, zip_name), self.exec_log_file, self.exec_log_file)
|
|
ExecShell(execStr)
|
|
self.save_config()
|
|
except:
|
|
pass
|
|
finally:
|
|
if os.path.exists(tip_file):
|
|
os.remove(tip_file)
|
|
|
|
t = threading.Thread(target=_back_p)
|
|
t.start()
|
|
|
|
@staticmethod
|
|
def _hash_src_name(name: Union[str, bytes]) -> str:
|
|
if isinstance(name, str):
|
|
name = name.encode('utf-8')
|
|
md5_obj = md5()
|
|
md5_obj.update(name)
|
|
return md5_obj.hexdigest()
|