# coding: utf-8 # ------------------------------------------------------------------- # yakpanel # ------------------------------------------------------------------- # Copyright (c) 2015-2017 yakpanel(https://www.yakpanel.com) All rights reserved. # ------------------------------------------------------------------- # Author: sww # ------------------------------------------------------------------- # php异步项目部署模块 # ------------------------------ import traceback import re, os, sys, json, time, hashlib import shutil if '/www/server/panel/' not in sys.path: sys.path.insert(0, '/www/server/panel/') from mod.base.web_conf import IpRestrict, remove_sites_service_config from mod.base.process.process import RealProcess from mod.base.process.server import RealServer from mod.base.database_tool import add_database from mod.base.web_conf import NginxDomainTool, check_domain from mod.base.web_conf.dir_tool import DirTool from mod.base.web_conf.proxy import Proxy from mod.base.backup_tool import VersionTool from mod.base.web_conf.referer import Referer from mod.base.web_conf.redirect import Redirect from mod.base.process.server import syssafe_admin if '/www/server/panel/class/' not in sys.path: sys.path.insert(0, '/www/server/panel/class/') import public import tarfile import psutil try: import zipfile except: public.ExecShell("btpip install zipfile") import zipfile try: import gzip except: public.ExecShell("btpip install indexed-gzip") import gzip try: import bz2 except: public.ExecShell("btpip install bz2file") import bz2 class main(IpRestrict, RealProcess, Proxy, Redirect): # 继承并使用同ip黑白名单限制 SSLManager ssl继承证书管理 _phpa_path = '/www/server/phpa_project' # _phpa_logs = '{}/logs'.format(_phpa_path) setupPath = public.get_setup_path() is_ipv6 = os.path.exists(setupPath + '/panel/data/ipv6.pl') siteName = None sitePath = None sitePort = None phpVersion = None pids = None _log_name = '项目管理' _php_logs_path = "/www/wwwlogs/php_async" __proxyfile = '{}/data/proxyfile.json'.format(public.get_panel_path()) group_file = "/www/server/panel/data/phpa_project_groups.json" def __init__(self): IpRestrict.__init__(self) Proxy.__init__(self) RealProcess.__init__(self) Redirect.__init__(self) if not os.path.exists(self.group_file): public.writeFile(self.group_file, '{}') def create_project(self, get): try: public.set_module_logs('phpmod', 'create_phpmod', 1) if not hasattr(get, 'webname'): return public.returnResult(False, '请输入域名!') if not hasattr(get, 'site_path'): return public.returnResult(False, '请输入项目路径!') if not os.path.exists(get.site_path): public.ExecShell('mkdir -p {}'.format(get.site_path)) if not hasattr(get, 'project_proxy_path') and hasattr(get, 'open_proxy') and int(get.open_proxy) == 1: return public.returnResult(False, '设置反向代理!') if not hasattr(get, 'project_port') or get.project_port == '': get.project_port = 0 if not hasattr(get, 'php_version'): return public.returnResult(False, '请选择PHP版本!') if not hasattr(get, 'project_cmd'): return public.returnResult(False, '请输入启动命令!') if not hasattr(get, 'is_power_on'): get.is_power_on = 1 if not hasattr(get, 'project_ps'): get.project_ps = '' if not hasattr(get, 'sql'): get.sql = "" if not hasattr(get, 'run_user'): get.run_user = 'www' if not hasattr(get, 'composer_version'): get.composer_version = '2.7.3' if not os.path.exists('/www/server/phpa_project/logs'): public.ExecShell('mkdir -p /www/server/phpa_project/logs') public.ExecShell('chmod -R 777 /www/server/phpa_project/logs') # 设置项目目录权限 public.ExecShell("chown -R {}:{} {}".format(get.run_user.strip(), get.run_user.strip(), get.site_path)) public.ExecShell("chmod -R 755 {}".format(get.site_path)) webname = json.loads(get.webname) # 域名重复性检查 domains = [webname['domain']] + webname['domainlist'] for domain in domains: if public.M('sites').where('name=?', (domain.split(':')[0].strip(),)).count(): return public.returnResult(False, '指定域名已存在: {}'.format(domain)) # 域名存在检查 get.project_cmd = get.project_cmd.strip() # 创建项目 if public.M('sites').where('name=?', (webname['domain'].split(':')[0].strip(),)).count(): return public.returnResult(False, '指定项目已存在: {}'.format(webname)) self.siteName = self.ToPunycode(webname['domain'].split(':')[0].strip()) self.sitePath = self.ToPunycodePath(get.site_path) self.sitePort = 80 if ':' not in webname['domain'] else webname['domain'].split(':')[1].strip() self.phpVersion = get.php_version # 添加nginx网站配置文件 self.nginxAdd() # 添加多余的域名 domainlist = [] for domain in domains: domainname = domain.split(':')[0].strip() domainport = '80' if ':' not in domain else domain.split(':')[1].strip() domainlist.append((domainname, domainport)) if len(domainlist) > 1: NginxDomainTool().nginx_set_domain(self.siteName, *domainlist) # 创建默认文档 if not os.path.exists('{}/index.ttml'.format(self.sitePath)): connect = """ 恭喜,异步项目创建成功!

恭喜,异步项目创建成功!

这是默认index.html,本页面由系统自动生成

""" public.writeFile('{}/index.html'.format(self.sitePath), connect) # 添加反向代理 if hasattr(get, 'open_proxy') and int(get.open_proxy) == 1: args = { "proxyname": self.siteName, "sitename": self.siteName, "proxydir": get.project_proxy_path, "proxysite": "http://127.0.0.1:{}".format(get.project_port), "todomain": "$host", "type": "1", "cache": "1", "subfilter": '[{"sub1":"","sub2":""},{"sub1":"","sub2":""},{"sub1":"","sub2":""}]', "advanced": "1", "cachetime": "1", } self.create_proxy(public.to_dict_obj(args)) # 修改启动命令 start_cmd = get.project_cmd if 'php' == get.project_cmd[:3]: get.project_cmd = '/www/server/php/{}/bin/php -c {}/php-cli.ini {}'.format(get.php_version, get.site_path, get.project_cmd[3:]) else: get.project_cmd = '/www/server/php/{}/bin/php -c {}/php-cli.ini {}'.format(get.php_version, get.site_path, get.project_cmd) is_fork = 0 if ' -d' in get.project_cmd: is_fork = 1 # 创建服务 并设置开机启动 realserver = RealServer() realserver.create_daemon(self.siteName, '', get.project_cmd, get.site_path, get.run_user.strip(), get.is_power_on, logs_file=os.path.join(self._phpa_logs, self.siteName + '.log'), is_fork=is_fork) # 安装依赖 dependence = 0 if hasattr(get, 'install_dependence') and get.install_dependence == "1": public.run_thread(self.install_dependence, (self.siteName, get.php_version, get.site_path, get.composer_version)) dependence = 1 else: public.ExecShell('cp /www/server/php/{}/etc/php-cli.ini {}/'.format(get.php_version, get.site_path)) public.ExecShell("sed -i '/disable_functions/d' {}".format(os.path.join(get.site_path, 'php-cli.ini'))) public.ExecShell("chown root:root {}".format(os.path.join(get.site_path, 'php-cli.ini'))) public.ExecShell("chomd 644 {}".format(os.path.join(get.site_path, 'php-cli.ini'))) # 写入数据库 pdata = { 'name': self.siteName, 'path': get.site_path, 'ps': get.project_ps, 'status': 1, 'type_id': 0, 'project_type': 'PHP', 'project_config': json.dumps( { 'ssl_path': '/www/wwwroot/phpa_ssl', 'sitename': self.siteName, 'domains': [webname['domain']] + webname['domainlist'], 'project_path': get.site_path, 'project_cmd': get.project_cmd, 'is_power_on': get.is_power_on, 'port': int(get.project_port), 'log_path': self._log_name, 'php_version': get.php_version, 'dependence': dependence, 'start_cmd': start_cmd, 'project_port': get.project_port, 'type': 'PHPMOD', 'run_user': get.run_user.strip(), } ), 'addtime': public.getDate() } pid = public.M('sites').insert(pdata) for domain in domains: domain_arr = domain.split(':') if len(domain_arr) == 1: domain_arr.append(80) domain_arr[0] = self.ToPunycode(domain_arr[0].strip()) public.M('domain').add('pid,name,port,addtime', (pid, domain_arr[0], domain_arr[1], public.getDate())) # 检查是否创建数据库 if get.sql == "MYSQL": if not (hasattr(get, 'sql_user') or hasattr(get, 'sql_pwd') or hasattr(get, 'sql_codeing')): return public.returnResult(False, '请填写数据库信息') mysql_data = { "server_id": 0, "database_name": get.sql_user, "db_user": get.sql_user, "password": get.sql_pwd, "dataAccess": "ip", "address": "127.0.0.1", "codeing": get.sql_codeing, "ps": "", "listen_ip": "0.0.0.0/0", "host": "", "pid": pid, } add_database(db_type="mysql", data=mysql_data) self.modify_project_run_state(public.to_dict_obj({'sitename': self.siteName, 'project_action': 'start'})) # 写日志 public.WriteLog(self._log_name, '添加PHP动态项目{}'.format(webname)) return public.returnResult(True, 'Successfully added' + ("" if dependence == 0 else ",正在安装依赖!")) except: return public.returnResult(False, str(traceback.format_exc())) def re_install_dependence(self, get): if not hasattr(get, 'id'): return public.returnResult(False, '参数缺失:ids!') project_config = public.M('sites').where('id=?', (get.id,)).select()[0] project_config = json.loads(project_config['project_config']) if os.path.exists('/tmp/php_mod_{}.pl'.format(project_config['name'])) and int(project_config['dependence']) == 1: pid = public.readFile('/tmp/php_mod_{}.pl'.format(project_config['name'])) try: import psutil p = psutil.Process(int(pid)) if p.name() == 'Yak-Panel' or p.name() == 'btpython': return public.returnResult(False, '正在安装中请稍后重试!') except: pass public.run_thread(self.install_dependence, (project_config['name'], project_config['php_version'], project_config['path'])) return public.returnResult(True, '正在安装依赖!') def check_install(self, get): if not hasattr(get, 'name'): return public.returnResult(False, '参数缺失:name!') if not hasattr(get, 'php_version'): return public.returnResult(False, '参数缺失:php_version!') if get.name == 'swoole': res = public.ExecShell("/www/server/php/{}/bin/php --ri {}|grep Version|awk '{{print $3}}'".format(get.php_version, get.name)) if res[0] != '': return public.returnResult(True, res[0].strip()) return public.returnResult(False, '未安装!请前往PHP-安装扩展中安装{}!'.format(get.name)) if get.name == 'fileinfo': res = public.ExecShell("/www/server/php/{}/bin/php --ri {}".format(get.php_version, get.name)) if res[0] != '' and 'fileinfo' in res[0] and 'enabled' in res[0]: return public.returnResult(True, '已安装!') return public.returnResult(False, '未安装!请前往PHP-安装扩展中安装{}!'.format(get.name)) def print_log(self, path, log): public.writeFile(path, log, 'a+') def check_auto_install(self, get): if not hasattr(get, 'path'): return public.returnResult(False, '参数缺失:path!') if not os.path.exists("{}/composer.json".format(get.path)): return False if not os.path.exists('{}/composer.lock'.format(get.path)) or not os.path.exists('{}/vendor'.format(get.path)): return True return False def get_swoole_correspondence_php(self, get): if not hasattr(get, 'swoole_version') and get.swoole_version.strip() in ['2', '4', '5']: return public.returnResult(False, '参数缺失:swoole_version!') swoole_version = get.swoole_version.strip() php_correspondence = { '2': ['52', '53', '54', '55', '70', '71', '72'], '4': ['72', '73', '74', '80', '81', '82'], '5': ['80', '81', '82', '83'], } php_list = php_correspondence[swoole_version] result = [] for php in php_list: setup_status = os.path.exists('/www/server/php/{}/bin/php'.format(php)) res = public.ExecShell("/www/server/php/{}/bin/php --ri swoole|grep Version|awk '{{print $3}}'".format(php))[0] swoole_status = True if res != '' else False swoole_version = res.strip() result.append({ 'php_version': php, 'setup_status': setup_status, 'swoole_status': swoole_status, 'swoole_version': swoole_version, 'setup_swoole_isactive': True if swoole_version != '' and swoole_version[0] == get.swoole_version else False }) return public.returnResult(True, data=result, msg='获取成功!') @syssafe_admin def install_composer(self, composer_version, logs_path): composer_name = '/usr/bin/composer{}'.format(('_' + composer_version.replace('.', '_')) if composer_version != '' else '') if not os.path.exists(composer_name) and composer_version != '': url = public.get_url() + '/src/compose/composer_{}'.format(composer_version.replace('.', '_')) public.ExecShell('wget -O {} {} &> {}'.format(composer_name, url, logs_path)) if os.path.exists(composer_name): public.ExecShell('chmod +x {}'.format(composer_name)) public.ExecShell('echo "{}" >> {}'.format('composer{}下载成功!'.format(composer_version), logs_path)) def install_dependence(self, webname, php_version, site_path, composer_version='2.7.3'): logs_path = '/tmp/{}_install_dependence.log'.format(webname) # 检查锁和所文件 if os.path.exists('{}/composer.lock'.format(site_path)): if os.path.exists('{}/vendor'.format(site_path)): public.ExecShell('echo "{}" >> {}'.format('composer.lock和vendor目录存在,不需要安装依赖!', logs_path)) try: project_list = public.M('sites').where('project_type=? and name=?', ('PHP', webname)).field('project_config').select()[0] project_config = json.loads(project_list['project_config']) project_config['dependence'] = 0 public.M('sites').where('project_type=? and name=?', ('PHP', webname)).setField('project_config', json.dumps(project_config)) except: pass if not os.path.exists('/www/server/phpa_project/logs'): public.ExecShell('mkdir -p /www/server/phpa_project/logs') public.ExecShell('chmod -R 755 /www/server/phpa_project/logs') RealServer().daemon_admin(str(webname), 'start') return else: public.ExecShell('echo "{}" >> {}'.format('composer.lock存在,但vendor目录不存在,开始安装依赖!', logs_path)) else: public.ExecShell('echo "{}" > {}'.format('composer.lock不存在,开始安装依赖!', logs_path)) self.install_composer(composer_version, logs_path) public.writeFile('/tmp/php_mod_{}.pl'.format(webname), str(os.getpid())) public.ExecShell('cp /www/server/php/{}/etc/php-cli.ini {}/'.format(php_version, site_path)) public.ExecShell("sed -i '/disable_functions/d' {}".format(os.path.join(site_path, 'php-cli.ini'))) # 尝试安装composer中的依赖 self.print_log(logs_path, '开始安装composer依赖!\n') # self.print_log(logs_path, 'cd {} && export COMPOSER_ALLOW_SUPERUSER=1 && /www/server/php/{}/bin/php -c {} /usr/bin/composer{} install --no-interaction &>> {}'.format(site_path, php_version, # os.path.join(site_path, # 'php-cli.ini'), # '_' + composer_version.replace( # '.', # '_') if composer_version != '' else '', # logs_path)) public.ExecShell( 'cd {} && export COMPOSER_ALLOW_SUPERUSER=1 && /www/server/php/{}/bin/php -c {} /usr/bin/composer{} install --no-interaction &>> {}'.format(site_path, php_version, os.path.join(site_path, 'php-cli.ini'), '_' + composer_version.replace('.', '_') if composer_version != '' else '', logs_path)) # 查看安装依赖报错 logs = public.readFile(logs_path) if "No composer.lock file present. Updating dependencies to latest instead of installing from lock file" in logs and not os.path.exists('{}/composer.lock'.format(site_path)): self.print_log(logs_path, '\ncomposer.lock文件不存在,安装依赖时需要使用此文件,请前往项目官网下载composer.lock文件放入{}目录后前往重新安装依赖!'.format(site_path)) public.ExecShell('cp /www/server/php/{}/etc/php-cli.ini {} >> {}'.format(php_version, site_path, logs_path)) public.ExecShell("sed -i '/disable_functions/d' {}".format(os.path.join(site_path, 'php-cli.ini'))) public.ExecShell("chown root:root {}".format(os.path.join(site_path, 'php-cli.ini'))) public.ExecShell("chomd 644 {}".format(os.path.join(site_path, 'php-cli.ini'))) self.print_log(logs_path, '安装依赖结束!') try: project_list = public.M('sites').where('project_type=? and name=?', ('PHP', webname)).field('project_config').select()[0] project_config = json.loads(project_list['project_config']) project_config['dependence'] = 0 public.M('sites').where('project_type=? and name=?', ('PHP', webname)).setField('project_config', json.dumps(project_config)) except: pass if not os.path.exists('/www/server/phpa_project/logs'): public.ExecShell('mkdir -p /www/server/phpa_project/logs') public.ExecShell('chmod -R 755 /www/server/phpa_project/logs') self.modify_project_run_state(public.to_dict_obj({'sitename': webname, 'project_action': 'start'})) def check_development_setup(self, php_version, name): try: # /ajax?action=GetPHPConfig import ajax a = ajax.ajax() get = public.to_dict_obj({}) get.version = php_version res = a.GetPHPConfig(get) for i in res['libs']: if i['name'] == name and i['status'] == True: return True return False except: return False # 域名编码转换 def ToPunycode(self, domain): import re if sys.version_info[0] == 2: domain = domain.encode('utf8') tmp = domain.split('.') newdomain = '' for dkey in tmp: if dkey == '*': continue # 匹配非ascii字符 match = re.search(u"[\x80-\xff]+", dkey) if not match: match = re.search(u"[\u4e00-\u9fa5]+", dkey) if not match: newdomain += dkey + '.' else: if sys.version_info[0] == 2: newdomain += 'xn--' + dkey.decode('utf-8').encode('punycode') + '.' else: newdomain += 'xn--' + dkey.encode('punycode').decode('utf-8') + '.' if tmp[0] == '*': newdomain = "*." + newdomain return newdomain[0:-1] def ToPunycodePath(self, path): if sys.version_info[0] == 2: path = path.encode('utf-8') if os.path.exists(path): return path import re match = re.search(u"[\x80-\xff]+", path) if not match: match = re.search(u"[\u4e00-\u9fa5]+", path) if not match: return path npath = '' for ph in path.split('/'): npath += '/' + self.ToPunycode(ph) return npath.replace('//', '/') # 添加到nginx def nginxAdd(self): if not os.path.exists('/www/server/panel/class/404_settings.json'): public.writeFile('/www/server/panel/class/404_settings.json', json.dumps({})) template = None use_template = public.readFile("{}/data/use_nginx_template.pl".format(public.get_panel_path())) if isinstance(use_template, str): template_file = "{}/data/nginx_template/{}".format(public.get_panel_path(), use_template) if os.path.isfile(template_file): template = public.readFile(template_file) with open('/www/server/panel/class/404_settings.json', 'r') as f: settings = json.load(f) status = settings.get('status', "0") filename = settings.get('filename', "404.html") error_page_line = 'error_page 404 /' + filename + ';' if status == "0": error_page_line = '#' + error_page_line else: error_page_line = 'error_page 404 /' + filename + ';' listen_ipv6 = '' if self.is_ipv6: listen_ipv6 = "\n listen [::]:%s;" % self.sitePort conf = r'''server {{ listen {listen_port};{listen_ipv6} server_name {site_name}; index index.php index.html index.htm default.php default.htm default.html; root {site_path}; #CERT-APPLY-CHECK--START # 用于SSL证书申请时的文件验证相关配置 -- 请勿删除 include /www/server/panel/vhost/nginx/well-known/{site_name}.conf; #CERT-APPLY-CHECK--END #SSL-START {ssl_start_msg} #error_page 404/404.html; #SSL-END #ERROR-PAGE-START {err_page_msg} {error_page_line} #error_page 502 /502.html; #ERROR-PAGE-END #PHP-INFO-START {php_info_start} include enable-php-{php_version}.conf; #PHP-INFO-END #REWRITE-START {rewrite_start_msg} include {setup_path}/panel/vhost/rewrite/{site_name}.conf; #REWRITE-END #禁止访问的文件或目录 location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) {{ return 404; }} #一键申请SSL证书验证目录相关设置 location ~ \.well-known{{ allow all; }} #禁止在证书验证目录放入敏感文件 if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {{ return 403; }} location ~ .*\\.(gif|jpg|jpeg|png|bmp|swf)$ {{ expires 30d; error_log /dev/null; access_log /dev/null; }} location ~ .*\\.(js|css)?$ {{ expires 12h; error_log /dev/null; access_log /dev/null; }} access_log {log_path}/{site_name}.log; error_log {log_path}/{site_name}.error.log; }}'''.format( listen_port=self.sitePort, listen_ipv6=listen_ipv6, site_path=self.sitePath, ssl_start_msg=public.getMsg('NGINX_CONF_MSG1'), err_page_msg=public.getMsg('NGINX_CONF_MSG2'), php_info_start=public.getMsg('NGINX_CONF_MSG3'), php_version=self.phpVersion, setup_path=self.setupPath, rewrite_start_msg=public.getMsg('NGINX_CONF_MSG4'), log_path=self.get_sites_log_path(), site_name=self.siteName, error_page_line=error_page_line ) template_conf = None if isinstance(template, str): try: template_conf = template.format( listen_port=self.sitePort, listen_ipv6=listen_ipv6, site_path=self.sitePath, ssl_start_msg=public.getMsg('NGINX_CONF_MSG1'), err_page_msg=public.getMsg('NGINX_CONF_MSG2'), php_info_start=public.getMsg('NGINX_CONF_MSG3'), php_version=self.phpVersion, setup_path=self.setupPath, rewrite_start_msg=public.getMsg('NGINX_CONF_MSG4'), log_path=self.get_sites_log_path(), site_name=self.siteName, error_page_line=error_page_line ) except: template_conf = None # 写配置文件 if not os.path.exists("/www/server/panel/vhost/nginx/well-known"): os.makedirs("/www/server/panel/vhost/nginx/well-known", 0o600) public.writeFile("/www/server/panel/vhost/nginx/well-known/{}.conf".format(self.siteName), "") filename = self.setupPath + '/panel/vhost/nginx/' + self.siteName + '.conf' if template_conf is not None: public.writeFile(filename, template_conf) else: public.writeFile(filename, conf) # 生成伪静态文件 urlrewritePath = self.setupPath + '/panel/vhost/rewrite' urlrewriteFile = urlrewritePath + '/' + self.siteName + '.conf' if not os.path.exists(urlrewritePath): os.makedirs(urlrewritePath) open(urlrewriteFile, 'w+').close() if not os.path.exists(urlrewritePath): public.writeFile(urlrewritePath, '') return True # 删除站点 def DeleteSite(self, get, multiple=None): try: proxyconf = [] if not os.path.exists(self.__proxyfile) else json.loads(public.readFile(self.__proxyfile)) id = get.id if public.M('sites').where('id=?', (id,)).count() < 1: return public.returnResult(False, '指定站点不存在!') siteName = get.webname get.siteName = siteName # 删除反向代理 for i in range(len(proxyconf) - 1, -1, -1): if proxyconf[i]["sitename"] == siteName: del proxyconf[i] public.writeFile(self.__proxyfile, json.dumps(proxyconf)) m_path = self.setupPath + '/panel/vhost/nginx/proxy/' + siteName if os.path.exists(m_path): public.ExecShell("rm -rf %s" % m_path) # 删除目录保护 _dir_aith_file = "%s/panel/data/site_dir_auth.json" % self.setupPath _dir_aith_conf = public.readFile(_dir_aith_file) # 删除保护目录 if _dir_aith_conf: try: _dir_aith_conf = json.loads(_dir_aith_conf) if siteName in _dir_aith_conf: del (_dir_aith_conf[siteName]) except: pass public.writeFile(_dir_aith_file, _dir_aith_conf) dir_aith_path = self.setupPath + '/panel/vhost/nginx/dir_auth/' + siteName if os.path.exists(dir_aith_path): public.ExecShell("rm -rf %s" % dir_aith_path) # 删除重定向 __redirectfile = "%s/panel/data/redirect.conf" % self.setupPath redirectconf = [] if not os.path.exists(__redirectfile) else json.loads(public.readFile(__redirectfile)) for i in range(len(redirectconf) - 1, -1, -1): if redirectconf[i]["sitename"] == siteName: del redirectconf[i] public.writeFile(__redirectfile, json.dumps(redirectconf)) m_path = self.setupPath + '/panel/vhost/nginx/redirect/' + siteName if os.path.exists(m_path): public.ExecShell("rm -rf %s" % m_path) # 删除配置文件 confPath = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' if os.path.exists(confPath): os.remove(confPath) # 删除伪静态文件 filename = '/www/server/panel/vhost/rewrite/' + siteName + '.conf' if os.path.exists(filename): os.remove(filename) public.ExecShell("rm -f " + confPath + '/rewrite/' + siteName + "_*") # 删除日志文件 filename = public.GetConfigValue('logs_path') + '/' + siteName + '*' public.ExecShell("rm -f " + filename) # 重载服务 public.serviceReload() # 从数据库删除 public.M('sites').where("id=?", (id,)).delete() public.M('binding').where("pid=?", (id,)).delete() public.M('domain').where("pid=?", (id,)).delete() public.WriteLog('TYPE_SITE', "SITE_DEL_SUCCESS", (siteName,)) # 是否删除关联数据库 if hasattr(get, 'database'): if get.database == '1': find = public.M('databases').where("pid=?", (id,)).field('id,name').find() if find: import database get.name = find['name'] get.id = find['id'] database.database().DeleteDatabase(get) # 是否删除关联FTP if hasattr(get, 'ftp'): if get.ftp == '1': find = public.M('ftps').where("pid=?", (id,)).field('id,name').find() if find: import ftp get.username = find['name'] get.id = find['id'] ftp.ftp().DeleteUser(get) try: # 删除项目 Redirect().remove_redirect_by_project_name(siteName) except: pass RealServer().del_daemon(siteName) remove_sites_service_config(siteName) return public.returnResult(True, '删除成功') except: return public.returnResult(False, traceback.format_exc()) def get_sites_log_path(get=None): log_path = public.readFile("{}/data/sites_log_path.pl".format(public.get_panel_path())) if isinstance(log_path, str) and os.path.isdir(log_path): return log_path return public.GetConfigValue('logs_path') # 检查端口是否被占用 def check_port_is_used(self, port, sock=False): ''' @name 检查端口是否被占用 @author sww @param port: int<端口> @return bool ''' if not isinstance(port, int): port = int(port) if port == 0: return False project_list = public.M('sites').where('status=? AND project_type=?', (1, 'PHP')).field( 'name,path,project_config').select() for project_find in project_list: project_config = json.loads(project_find['project_config']) if not 'port' in project_config: continue try: if int(project_config['port']) == port: return True except: continue if sock: return False return public.check_tcp('127.0.0.1', port) # 获取项目列表 def get_project_list(self, get): ''' @name 获取项目列表 @author sww @param get{ sitename: string<项目名称> } @return dict ''' try: if not 'p' in get: get.p = 1 if not 'limit' in get: get.limit = 20 if not 'callback' in get: get.callback = '' if not 'order' in get: get.order = 'id desc' type_id = None if "type_id" in get: try: type_id = int(get.type_id) except: type_id = None if 'search' in get: get.sitename = get.search.strip() search = "%{}%".format(get.sitename) if type_id is None: count = public.M('sites').where('project_type=? AND (name LIKE ? OR ps LIKE ?)', ('PHP', search, search)).count() data = public.get_page(count, int(get.p), int(get.limit), get.callback) data['data'] = public.M('sites').where('project_type=? AND (name LIKE ? OR ps LIKE ?)', ('PHP', search, search)).limit(data['shift'] + ',' + data['row']).order(get.order).select() else: count = public.M('sites').where('project_type=? AND (name LIKE ? OR ps LIKE ?) AND type_id = ?', ('PHP', search, search, type_id)).count() data = public.get_page(count, int(get.p), int(get.limit), get.callback) data['data'] = public.M('sites').where('project_type=? AND (name LIKE ? OR ps LIKE ?) AND type_id = ?', ('PHP', search, search, type_id)).limit( data['shift'] + ',' + data['row']).order(get.order).select() else: if type_id is None: count = public.M('sites').where('project_type=?', 'PHP').count() data = public.get_page(count, int(get.p), int(get.limit), get.callback) data['data'] = public.M('sites').where('project_type=?', 'PHP').limit(data['shift'] + ',' + data['row']).order(get.order).select() else: count = public.M('sites').where('project_type=? AND type_id = ?', ('PHP', type_id)).count() data = public.get_page(count, int(get.p), int(get.limit), get.callback) data['data'] = public.M('sites').where('project_type=? AND type_id = ?', ('PHP', type_id)).limit(data['shift'] + ',' + data['row']).order(get.order).select() for i in range(len(data['data'])): data['data'][i] = self.get_project_stat(data['data'][i]) logs_path = '/tmp/{}_install_dependence.log'.format(data['data'][i]['name']) if not os.path.exists(logs_path) or int(os.path.getmtime(logs_path)) - int(time.time()) > 20: data['data'][i]['dependence'] = 0 return public.returnResult(True, data=data) except: return public.returnResult(False, '获取项目列表失败') # 获取项目状态信息 def get_project_stat(self, project_info): ''' @name 获取项目状态信息 @author sww @param project_info 项目信息 @return list ''' project_info['project_config'] = json.loads(project_info['project_config']) # project_info['project_config']['bind_extranet'] = int(project_info['project_config']['bind_extranet']) # project_info['run'] = self.get_project_run_state(sitename=project_info['name']) # project_info['load_info'] = self.get_project_load_info(sitename=project_info['name']) project_info['run'] = RealServer().daemon_status(project_info['name'])['status'] project_info['ssl'] = self.get_ssl_end_date(sitename=project_info['name']) project_info['listen'] = [] project_info['listen_ok'] = True # if project_info['load_info']: # for pid in project_info['load_info'].keys(): # if not 'connections' in project_info['load_info'][pid]: # project_info['load_info'][pid]['connections'] = [] # if 'connections' in project_info['load_info'][pid]: # for conn in project_info['load_info'][pid]['connections']: # if not conn['status'] == 'LISTEN': continue # if not conn['local_port'] in project_info['listen']: # project_info['listen'].append(conn['local_port']) # if project_info['listen']: # project_info['listen_ok'] = project_info['project_config']['port'] in project_info['listen'] return project_info # 获取指定项目的域名列表 def project_get_domain(self, get): ''' @name 获取指定项目的域名列表 @author sww @param get{ sitename: string<项目名称> } @return dict ''' try: project_id = public.M('sites').where('name=?', (get.sitename,)).getField('id') domains = public.M('domain').where('pid=?', (project_id,)).order('id desc').select() # project_find = self.get_project_find(get.sitename) # if len(domains) != len(project_find['project_config']['domains']): # public.M('domain').where('pid=?', (project_id,)).delete() # if not project_find: return [] # for d in project_find['project_config']['domains']: # domain = {} # arr = d.split(':') # if len(arr) < 2: arr.append(80) # domain['name'] = arr[0] # domain['port'] = int(arr[1]) # domain['pid'] = project_id # domain['addtime'] = public.getDate() # public.M('domain').insert(domain) # if project_find['project_config']['domains']: # domains = public.M('domain').where('pid=?', (project_id,)).select() return public.returnResult(True, data=domains) except: return public.returnResult(False, traceback.format_exc()) # 获取指定项目配置 def get_project_find(self, sitename): ''' @name 获取指定项目配置 @author sww @param sitename 项目名称 @return dict ''' project_info = public.M('sites').where('project_type=? AND name=?', ('PHP', sitename)).find() if not project_info: return False project_info['project_config'] = json.loads(project_info['project_config']) if 'run_user' not in project_info['project_config'].keys(): project_info['run_user'] = 'www' return project_info # 获取项目SSL信息 def get_ssl_end_date(self, sitename): ''' @name 获取SSL信息 @author sww @param sitename 项目名称 @return dict ''' import data return data.data().get_site_ssl_info(sitename) # 删除指定项目中的域名 def project_remove_domain(self, get): ''' @name 为指定项目删除域名 @author sww @param get{ sitename: string<项目名称> domain: string<域名> } @return dict ''' try: project_find = self.get_project_find(get.sitename) if not project_find: return public.returnResult(False, 'The specified item does not exist') result = [] domain_list = json.loads(get.domain) project_id = public.M('sites').where('name=?', (get.sitename,)).getField('id') for domain in domain_list: if public.M('domain').where('pid=?', (project_id,)).count() == 1: result.append({'name': domain, 'msg': "项目中至少需要一个域名", "status": False}) continue domain_id = public.M('domain').where('name=? AND pid=?', (domain, project_id)).getField('id') if not domain_id: return public.returnResult(False, '指定域名不存在') public.M('domain').where('id=?', (domain_id,)).delete() public.WriteLog(self._log_name, '从项目:{},删除域名{}'.format(get.sitename, get.domain)) result.append({'name': domain, 'msg': "删除成功", "status": True}) domain_list = public.M('domain').where('pid=?', (project_id)).select() domain_list = [(domain['name'], str(domain['port'])) for domain in domain_list] NginxDomainTool().nginx_set_domain(get.sitename, *domain_list) return public.returnResult(True, data=result) except: return public.returnResult(False, traceback.format_exc()) # 为指定项目添加域名 def project_add_domain(self, get): ''' @name 为指定项目添加域名 @author sww @param get{ sitename: string<项目名称> domains: list<域名列表> } @return dict ''' try: project_find = self.get_project_find(get.sitename) if not project_find: return public.returnResult(False, 'The specified item does not exist') project_id = project_find['id'] domains = get.domains if not isinstance(domains, list): domains = json.loads(domains) flag = False res_domains = [] for domain in domains: domain = domain.strip() if not domain: continue if not self.check_domain(domain): res_domains.append({"name": domain, "status": False, "msg": '域名格式错误'}) continue domain_arr = domain.split(':') domain_arr[0] = check_domain(domain_arr[0]) domain_arr[0] = self.ToPunycode(domain_arr[0]) if domain_arr[0] is False: res_domains.append({"name": domain, "status": False, "msg": '域名格式错误'}) continue if len(domain_arr) == 1: domain_arr.append("") if domain_arr[1] == "": domain_arr[1] = 80 domain += ':80' try: if not (0 < int(domain_arr[1]) < 65535): res_domains.append({"name": domain, "status": False, "msg": '域名格式错误'}) continue except ValueError: res_domains.append({"name": domain, "status": False, "msg": '域名格式错误'}) continue if not public.M('domain').where('name=?', (domain_arr[0],)).count(): public.M('domain').add('name,pid,port,addtime', (domain_arr[0], project_id, domain_arr[1], public.getDate())) if not domain in project_find['project_config']['domains']: project_find['project_config']['domains'].append(domain) public.WriteLog(self._log_name, '成功添加域名{}到项目{}'.format(domain, get.sitename)) res_domains.append({"name": domain_arr[0], "status": True, "msg": '添加成功'}) flag = True else: public.WriteLog(self._log_name, '添加域名错误,域名{}已存在'.format(domain)) res_domains.append({"name": domain_arr[0], "status": False, "msg": '添加失败,域名{}已存在'.format(domain)}) if flag: public.M('sites').where('id=?', (project_id,)).save('project_config', json.dumps(project_find['project_config'])) domain_list = public.M('domain').where('pid=?', (project_id)).select() domain_list = [(domain['name'], str(domain['port'])) for domain in domain_list] NginxDomainTool().nginx_set_domain(get.sitename, *domain_list) return self._ckeck_add_domain(get.sitename, res_domains) except: return public.returnResult(False, traceback.format_exc()) def _ckeck_add_domain(self, site_name, domains): from panelSite import panelSite ssl_data = panelSite().GetSSL(type("get", tuple(), {"siteName": site_name})()) if not ssl_data["status"]: return public.returnResult(True, data={"domains": domains}, msg="添加成功") domain_rep = [] for i in ssl_data["cert_data"]["dns"]: if i.startswith("*"): _rep = r"^[^\.]+\." + i[2:].replace(".", r"\.") else: _rep = "^" + i.replace(".", r"\.") domain_rep.append(_rep) no_ssl = [] for domain in domains: if not domain["status"]: continue for _rep in domain_rep: if re.search(_rep, domain["name"]): break else: no_ssl.append(domain["name"]) if no_ssl: return public.returnResult(True, data={ "domains": domains, "not_ssl": no_ssl, "tip": "本站点已启用SSL证书,但本次添加的域名:{},无法匹配当前证书,如有需求,请重新申请证书。".format(str(no_ssl)) }, msg="添加成功") return public.returnResult(True, data={"domains": domains}, msg="添加成功") # 获取项目状态 def get_project_run_state(self, get): ''' @name 获取项目运行状态信息 @param sitename 项目名称 @return dict ''' try: sitename = get.sitename.strip() result = {} project_info = self.get_project_find(sitename) res = RealServer().daemon_status(sitename) result['status'] = res['status'] result['sitename'] = sitename result['is_power_on'] = int(project_info["project_config"]['is_power_on']) result['project_path'] = project_info['path'] # type : str result['project_cmd'] = project_info["project_config"].get('start_cmd', '') result['project_port'] = project_info['project_config']['port'] if result['project_path'][-1] != "/": result['project_path'] += "/" result['site_run_path'] = DirTool().get_site_run_path(sitename).replace(result['project_path'].rstrip("/"), "") if not result['site_run_path']: result['site_run_path'] = "/" result['php_version'] = project_info['project_config']['php_version'] result['run_user'] = project_info['project_config'].get('run_user', 'www') result['ps'] = project_info['ps'] result['pid'] = RealServer().get_daemon_pid(sitename)["data"] if result['pid'] in [0, '0', ''] and result['status']: res = public.ExecShell("""ps -aux |grep "{}"|grep -v "grep"|awk '{{print $2}}'""".format(result['project_cmd']))[0] res = res.strip().split("\n") if res and len(res) >= 1: res.sort() result['pid'] = res[0] result['Listen'] = [] if result['pid']: children = RealProcess().get_process_tree(result['pid'])["data"] for chi in children: for i in chi.get("connections", []): port = i.get("local_port", '') addr = i.get("local_addr", '') status = i.get("status", 'dasd') if addr == '0.0.0.0': addr = public.GetLocalIp() if port and addr and status.lower() == 'listen': result['Listen'].append((addr, port)) result['Listen'] = list(set(result['Listen'])) return public.returnResult(True, data=result) except: public.print_log(traceback.format_exc()) return public.returnResult(False, '获取项目状态失败') def domain_to_puny_code(self, domain: str) -> str: new_domain = '' for dkey in domain.split('.'): if dkey == '*' or dkey == "": continue # 匹配非ascii字符 match = re.search(u"[\x80-\xff]+", dkey) if not match: match = re.search(u"[\u4e00-\u9fa5]+", dkey) if not match: new_domain += dkey + '.' else: new_domain += 'xn--' + dkey.encode('punycode').decode('utf-8') + '.' if domain.startswith('*.'): new_domain = "*." + new_domain return new_domain[:-1] def check_domain(self, domain: str): domain = self.domain_to_puny_code(domain) # 判断通配符域名格式 if domain.find('*') != -1 and domain.find('*.') == -1: return None # 判断域名格式 rep_domain = re.compile(r"^([\w\-*]{1,100}\.){1,24}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$") if not rep_domain.match(domain): return None return domain # 修改项目运行状态 def modify_project_run_state(self, get): sitename = get.sitename project_action = get.project_action logs_path = "/www/server/phpa_project/logs" if not os.path.exists(logs_path): public.ExecShell('mkdir -p {}'.format(logs_path)) public.ExecShell('chmod 777 -R {}'.format(logs_path)) if project_action in ['start', 'restart']: self.async_dependence_config(sitename) res = RealServer().daemon_admin(sitename, project_action) if res['status'] and project_action in ['start', 'restart']: public.M('sites').where('name=? and project_type=?', (sitename, 'PHP')).setField('status', 1) elif res['status'] or project_action == 'stop': public.M('sites').where('name=? and project_type=?', (sitename, 'PHP')).setField('status', 0) # if project_action in ['start', 'restart'] and not res['status']: # log = os.path.join(self._phpa_logs, sitename + '.log') # logs = public.GetNumLines(log, 5) # res['msg'] += '\n' + logs # res['msg'] += '\n 详情前往日志-项目日志查看' if project_action in ['start', 'restart']: site_info = self.get_project_find(sitename) for i in range(20): time.sleep(0.1) conn = public.ExecShell('systemctl status {}'.format(sitename)) public.print_log(conn) if 'deactivating' in conn[0]: # 开启forking模式 sys_conf = '/usr/lib/systemd/system/{}.service'.format(sitename) sys_conf = public.readFile(sys_conf) public.print_log(sys_conf) if 'Type=forking' not in sys_conf: sys_conf = sys_conf.replace('Type=simple', 'Type=forking') public.writeFile('/usr/lib/systemd/system/{}.service'.format(sitename), sys_conf) public.ExecShell('systemctl daemon-reload') public.ExecShell('systemctl restart {}'.format(sitename)) pid = RealServer().get_daemon_pid(sitename)["data"] if pid in [0, '0', '']: res = public.ExecShell("""ps -aux |grep "{}"|grep -v "grep"|awk '{{print $2}}'""".format(site_info['project_config']['project_cmd']))[0] res = res.strip().split("\n") if res and len(res) >= 1: res.sort() pid = res[0] if pid not in [0, '0', '']: pids = psutil.Process(int(pid)).children(recursive=True) pids = [str(i.pid) for i in pids] pids.append(str(pid)) if pid not in [0, '0']: res = public.ExecShell('lsof -i |grep -E "{}" |grep LISTEN'.format('|'.join(pids))) if res[0]: return public.returnResult(True, '启动成功') return public.returnResult(False, '启动失败') return public.returnResult(True, '关闭成功') def async_dependence_config(self, sitename): """ 增加安装依赖的配置文件 """ try: config = self.get_project_find(sitename) # public.ExecShell("chown -R {}:{} {}".format(config['project_config'].run_user.strip(), config['project_config'].run_user.strip(), get.site_path)) # public.ExecShell("chmod -R 755 {}".format(config['path'])) php_version = config['project_config']['php_version'] php_ini_path = os.path.join(config['path'], 'php-cli.ini') public.ExecShell('cp /www/server/php/{}/etc/php-cli.ini {}'.format(php_version, config['path'])) public.ExecShell('chown root:root {}'.format(php_ini_path)) public.ExecShell('chmod 644 {}'.format(php_ini_path)) public.ExecShell("sed -i '/disable_functions/d' {}".format(php_ini_path)) public.writeFile(php_ini_path, php_cli_ini) except: pass # 修改项目网站运行目录 def modify_project_path(self, get): if not hasattr(get, 'new_run_path_sub'): return public.returnResult(False, '新的运行目录不能为空') new_run_path_sub = get.new_run_path_sub sitename = get.sitename.strip() project_info = self.get_project_find(sitename) DirTool().modify_site_run_path(sitename, project_info['path'], new_run_path_sub) return public.returnResult(True, '修改成功') def modify_project(self, get): try: if not hasattr(get, 'sitename'): return public.returnResult(False, 'The project name cannot be empty') if not hasattr(get, 'project_cmd'): return public.returnResult(False, 'project_cmd不能为空') if not hasattr(get, 'project_path'): return public.returnResult(False, 'project_path不能为空') if not hasattr(get, 'site_run_path'): return public.returnResult(False, 'site_run_path不能为空') if not hasattr(get, 'run_user'): get.run_user = 'www' public.ExecShell('chown -R {}:{} {}'.format(get.run_user, get.run_user, get.project_path)) public.ExecShell('chmod 755 {}'.format(get.project_path)) config = self.get_project_find(get.sitename) config['project_config']['run_user'] = get.run_user start_cmd = get.project_cmd if 'php' == get.project_cmd[:3]: get.project_cmd = '/www/server/php/{}/bin/php -c {}/php-cli.ini {}'.format(get.php_version, get.project_path, get.project_cmd[3:]) else: get.project_cmd = '/www/server/php/{}/bin/php -c {}/php-cli.ini {}'.format(get.php_version, get.project_path, get.project_cmd) if config['project_config'].get('start_cmd') != start_cmd: config['project_config']['start_cmd'] = start_cmd if config['path'] != get.project_path: if not os.path.exists(get.project_path): return public.returnResult(False, '项目目录不存在') config['path'] = get.project_path config['project_config']['project_path'] = get.project_path # get.site_run_path = os.path.join(config['path'], get.site_run_path.lstrip("/")) config['project_config']['site_run_path'] = get.site_run_path.rstrip('/') get.new_run_path_sub = config['project_config']['site_run_path'] self.modify_project_path(get) if config['project_config']['php_version'] != get.php_version: config['project_config']['php_version'] = get.php_version public.ExecShell('cp /www/server/php/{}/etc/php-cli.ini {}'.format(get.php_version, config['path'])) public.ExecShell('chown root:root {}'.format(os.path.join(config['path'], 'php-cli.ini'))) public.ExecShell('chmod 644 {}'.format(os.path.join(config['path'], 'php-cli.ini'))) public.ExecShell("sed -i '/disable_functions/d' {}".format(os.path.join(config['path'], 'php-cli.ini'))) if config['project_config']['project_cmd'] != get.project_cmd or config['project_config']['is_power_on'] != get.get('is_power_on', 1) or config['project_config'][ 'run_user'] != get.run_user: config['project_config']['is_power_on'] = get.get('is_power_on', 1) config['project_config']['project_cmd'] = get.project_cmd systemd_conf = '/usr/lib/systemd/system/{}.service'.format(get.sitename) conf = public.readFile(systemd_conf) is_fork = 0 if conf: if 'Type=forking' in conf: is_fork = 1 realserver = RealServer() realserver.create_daemon(get.sitename, '', get.project_cmd, get.project_path, config['project_config'].get('run_user', 'www'), get.is_power_on, logs_file=os.path.join(self._phpa_logs, get.sitename + '.log'), is_fork=is_fork) if config['ps'] != get.get('ps', '') and get.get('ps', ''): public.M('sites').where('name=? and project_type=?', (get.sitename, 'PHP')).setField('ps', get.get('ps', '')) config['project_config']['port'] = get.get('project_port', 0) public.M('sites').where('name=? and project_type=?', (get.sitename, 'PHP')).setField('project_config', json.dumps(config['project_config'])) return public.returnResult(True, '修改成功') except: return public.returnResult(False, traceback.format_exc()) def get_project_log(self, get): log_file = self._phpa_logs + '/' + get.sitename + '.log' if not os.path.exists(log_file): return public.returnResult(status=True, msg='暂无项目日志') return public.returnResult(True, msg=public.GetNumLines(log_file, 1000)) def get_access_log(self, get): from logsModel.siteModel import main as siteModel get.sitename = get.siteName.strip() sitelog = siteModel() return public.returnResult(True, data=sitelog.get_site_access_logs(get)['msg']) def get_error_log(self, get): from logsModel.siteModel import main as siteModel get.sitename = get.siteName.strip() sitelog = siteModel() return public.returnResult(True, data=sitelog.get_site_error_logs(get)['msg']) def get_config_file(self, get): result = {} site_name = get.sitename conf = self.get_project_find(site_name) result['nginx配置文件'] = self.setupPath + '/panel/vhost/nginx/' + site_name + '.conf' result['php-cli配置文件'] = os.path.join(conf['path'], 'php-cli.ini') env_path = os.path.join(conf['path'], '.env') if os.path.exists(env_path): result['.env配置文件'] = env_path result['伪静态配置文件'] = "/www/server/panel/vhost/rewrite/{}.conf".format(site_name) composer_path = os.path.join(conf['path'], 'composer.json') if os.path.exists(composer_path): result['composer配置文件'] = composer_path return public.returnResult(True, data=result) # 上传版本 def upload_version(self, get): """ 上传压缩包并存储为版本 """ if not hasattr(get, 'sitename'): return public.returnResult(False, 'The project name cannot be empty') if not hasattr(get, 'version'): return public.returnResult(False, '版本号不能为空') if not hasattr(get, 'ps'): get.ps = '' try: upload_files = os.path.join("/tmp", get.f_name) from files import files fileObj = files() ff = fileObj.upload(get) if type(ff) == int: return ff if not ff['status']: return public.returnResult(False, ff['msg']) output_dir = str(os.path.join('/tmp', public.GetRandomString(16))) os.makedirs(output_dir, 777) if not self.extract_archive(upload_files, output_dir)[0]: return public.returnResult(False, '解压失败,仅支持zip,tar.gz,tar,bz2,gz,xz格式的压缩包') if len(os.listdir(output_dir)) == 1: output_dir = str(os.path.join(output_dir, os.listdir(output_dir)[0])) versiontool = VersionTool() res = versiontool.publish_by_src_path(get.sitename, output_dir, get.version, get.ps, sync=True) public.ExecShell('rm -rf {}'.format(output_dir)) public.ExecShell('rm -rf {}'.format(upload_files)) if res is None: return public.returnResult(True, '添加成功') return public.returnResult(False, '添加失败' + res) except: return public.returnResult(False, traceback.format_exc()) def extract_archive(self, file_path, output_dir): name = os.path.basename(file_path) if name.endswith('.tar.gz'): with tarfile.open(file_path, 'r:gz') as tar: tar.extractall(output_dir) elif name.endswith('.zip'): with zipfile.ZipFile(file_path, 'r') as zip_ref: zip_ref.extractall(output_dir) elif name.endswith('.gz'): with gzip.open(file_path, 'rb') as f_in, open(os.path.join(output_dir, name[:-3]), 'wb') as f_out: shutil.copyfileobj(f_in, f_out) elif name.endswith('.tar'): with tarfile.open(file_path, 'r') as tar: tar.extractall(output_dir) elif name.endswith('.bz2'): with open(file_path, 'rb') as f_in, open(os.path.join(output_dir, name[:-4]), 'wb') as f_out: with bz2.BZ2File(f_in) as bz: shutil.copyfileobj(bz, f_out) # elif name.endswith('.xz'): # with lzma.open(file_path, 'rb') as f_in, open(os.path.join(output_dir, name[:-3]), 'wb') as f_out: # shutil.copyfileobj(f_in, f_out) else: return False, '文件格式错误.' return True, '解压成功' # 获取列表 def get_version_list(self, get): if not hasattr(get, 'sitename'): return public.returnResult(False, 'The project name cannot be empty') versiontool = VersionTool() return public.returnResult(True, data=versiontool.version_list(get.sitename)) # 删除版本 def remove_version(self, get): if not hasattr(get, 'sitename'): return public.returnResult(False, 'The project name cannot be empty') if not hasattr(get, 'version'): return public.returnResult(False, '版本号不能为空') versiontool = VersionTool() if versiontool.remove(get.sitename, get.version) is None: return public.returnResult(True, '删除成功') return public.returnResult(False, '删除失败') # 恢复版本 def recover_version(self, get): try: if not hasattr(get, 'sitename'): return public.returnResult(False, 'The project name cannot be empty') if not hasattr(get, 'version'): return publihuoc.returnResult(False, '版本号不能为空') conf = self.get_project_find(get.sitename) versiontool = VersionTool() res = versiontool.recover(get.sitename, get.version, conf['path'], DirTool().get_site_run_path(get.sitename)) if res is not True: return public.returnResult(False, res) return public.returnResult(True, '恢复成功') except: return public.returnResult(False, traceback.format_exc()) def now_file_backup(self, get): try: if not hasattr(get, 'sitename'): return public.returnResult(False, 'The project name cannot be empty') if not hasattr(get, 'version'): return public.returnResult(False, '版本号不能为空') if not hasattr(get, 'ps'): get.ps = '' config = self.get_project_find(get.sitename) path = config['path'] versiontool = VersionTool() res = versiontool.publish_by_src_path(get.sitename, path, get.version, get.ps, sync=True) if res is None: return public.returnResult(True, '添加成功') return public.returnResult(False, '添加失败' + res) except: return public.returnResult(False, traceback.format_exc()) def set_version_ps(self, get): if not hasattr(get, 'sitename'): return public.returnResult(False, 'The project name cannot be empty') if not hasattr(get, 'version'): return public.returnResult(False, '版本号不能为空') if not hasattr(get, 'ps'): get.ps = '' versiontool = VersionTool() versiontool.set_ps(get.sitename, get.version, get.ps) return public.returnResult(True, '设置成功') def get_setup_log(self, get): log_file = "/tmp/{}_install_dependence.log".format(get.sitename) if not os.path.exists(log_file): return public.returnResult(True, data='暂无项目日志') return public.returnResult(True, data=public.GetNumLines(log_file, 1000)) def add_crontab(self, get): try: if not hasattr(get, 'sitename'): return public.returnResult(False, 'The project name cannot be empty') if not hasattr(get, 'cron_name'): return public.returnResult(False, 'The timed task name cannot be empty') if not hasattr(get, 'sBody'): return public.returnResult(False, '定时任务内容不能为空') args = { "name": get.cron_name, "type": get.type, "where1": get.where1, "hour": get.hour, "minute": get.minute, "week": get.week, "sType": "toShell", "sName": "", "backupTo": "localhost", "save": get.sitename, "sBody": get.sBody, "urladdress": "", "flock": get.flock } import crontab res = crontab.crontab().AddCrontab(public.to_dict_obj(args)) if res['status']: return public.returnResult(True, msg='添加成功!') return public.returnResult(False, res['msg']) except: return public.returnResult(False, traceback.format_exc()) def get_crontab_list(self, get): try: if not hasattr(get, 'sitename'): return public.returnResult(False, 'The project name cannot be empty') cront = public.M('crontab').where('save=?', (get.sitename,)).select() data = [] for i in range(len(cront)): tmp = cront[i] if cront[i]['type'] == "day": tmp['type_zh'] = public.getMsg('CRONTAB_TODAY') tmp['cycle'] = public.getMsg('CRONTAB_TODAY_CYCLE', (str(cront[i]['where_hour']), str(cront[i]['where_minute']))) elif cront[i]['type'] == "day-n": tmp['type_zh'] = public.getMsg('CRONTAB_N_TODAY', (str(cront[i]['where1']),)) tmp['cycle'] = public.getMsg('CRONTAB_N_TODAY_CYCLE', ( str(cront[i]['where1']), str(cront[i]['where_hour']), str(cront[i]['where_minute']))) elif cront[i]['type'] == "hour": tmp['type_zh'] = public.getMsg('CRONTAB_HOUR') tmp['cycle'] = public.getMsg('CRONTAB_HOUR_CYCLE', (str(cront[i]['where_minute']),)) elif cront[i]['type'] == "hour-n": tmp['type_zh'] = public.getMsg('CRONTAB_N_HOUR', (str(cront[i]['where1']),)) tmp['cycle'] = public.getMsg('CRONTAB_N_HOUR_CYCLE', (str(cront[i]['where1']), str(cront[i]['where_minute']))) elif cront[i]['type'] == "minute-n": tmp['type_zh'] = public.getMsg('CRONTAB_N_MINUTE', (str(cront[i]['where1']),)) tmp['cycle'] = public.getMsg('CRONTAB_N_MINUTE_CYCLE', (str(cront[i]['where1']),)) elif cront[i]['type'] == "week": tmp['type_zh'] = public.getMsg('CRONTAB_WEEK') if not cront[i]['where1']: cront[i]['where1'] = '0' tmp['cycle'] = public.getMsg('CRONTAB_WEEK_CYCLE', ( self.toWeek(int(cront[i]['where1'])), str(cront[i]['where_hour']), str(cront[i]['where_minute']))) elif cront[i]['type'] == "month": tmp['type_zh'] = public.getMsg('CRONTAB_MONTH') tmp['cycle'] = public.getMsg('CRONTAB_MONTH_CYCLE', ( str(cront[i]['where1']), str(cront[i]['where_hour']), str(cront[i]['where_minute']))) log_file = '/www/server/cron/{}.log'.format(tmp['echo']) if os.path.exists(log_file): tmp['addtime'] = self.get_last_exec_time(log_file) data.append(tmp) for i in data: if i['backup_mode'] == "1": i['backup_mode'] = 1 else: i['backup_mode'] = 0 if i['db_backup_path'] == "": i['db_backup_path'] = "/www/backup" if not i.get('rname', ''): i['rname'] = i['name'] if i['time_type'] == 'sweek': i['type'] = 'sweek' week_str = self.toweek(i['time_set']) if week_str: # 检查week_str是否为空 i['type_zh'] = week_str i['cycle'] = "每" + week_str + i['special_time'] + "执行" elif i['time_type'] == 'sday': i['type'] = 'sweek' i['type_zh'] = i['special_time'] i['cycle'] = "每天" + i['special_time'] + "执行" elif i['time_type'] == 'smonth': i['type'] = 'sweek' i['type_zh'] = i['special_time'] i['cycle'] = "每月" + i['time_set'] + "号" + i['special_time'] + "执行" if i['sType'] == 'site_restart': i['cycle'] = "每天" + i['special_time'] + "执行" return public.returnResult(True, data=data) except: return public.returnResult(False, traceback.format_exc()) # 转换大写星期 def toWeek(self, num): wheres = { 0: public.getMsg('CRONTAB_SUNDAY'), 1: public.getMsg('CRONTAB_MONDAY'), 2: public.getMsg('CRONTAB_TUESDAY'), 3: public.getMsg('CRONTAB_WEDNESDAY'), 4: public.getMsg('CRONTAB_THURSDAY'), 5: public.getMsg('CRONTAB_FRIDAY'), 6: public.getMsg('CRONTAB_SATURDAY') } try: return wheres[num] except: return '' def get_last_exec_time(self, log_file): ''' @name 获取上次执行时间 @author hwliang @param log_file 日志文件路径 @return format_date ''' exec_date = '' try: log_body = public.GetNumLines(log_file, 20) if log_body: log_arr = log_body.split('\n') date_list = [] for i in log_arr: if i.find('★') != -1 and i.find('[') != -1 and i.find(']') != -1: date_list.append(i) if date_list: exec_date = date_list[-1].split(']')[0].split('[')[1] except: pass finally: if not exec_date: exec_date = public.format_date(times=int(os.path.getmtime(log_file))) return exec_date def start_task(self, get): try: if not hasattr(get, 'id'): return public.returnResult(False, 'ID不能为空') import crontab res = crontab.crontab().StartTask(get) if res['status']: return public.returnResult(True, '启动成功') return public.returnResult(False, res['msg']) except: return public.returnResult(False, traceback.format_exc()) def modify_crontab_status(self, get): try: if not hasattr(get, 'id'): return public.returnResult(False, 'ID不能为空') if hasattr(get, 'status'): cronInfo = public.M('crontab').where('id=?', (get.id,)).select()[0] if int(cronInfo['status']) == int(get.status): return public.returnResult(True, '修改成功') get.if_stop = False import crontab res = crontab.crontab().set_cron_status(get) if res['status']: return public.returnResult(True, '修改成功') return public.returnResult(False, res['msg']) except: return public.returnResult(False, traceback.format_exc()) def remove_crontab(self, get): try: if not hasattr(get, 'id'): return public.returnResult(False, 'ID不能为空') import crontab res = crontab.crontab().DelCrontab(get) if res['status']: return public.returnResult(True, '删除成功') return public.returnResult(False, res['msg']) except: return public.returnResult(False, traceback.format_exc()) def modify_crontab(self, get): try: if not hasattr(get, 'id'): return public.returnResult(False, 'ID不能为空') self.remove_crontab(get) res = self.add_crontab(get) res['msg'] = res['msg'].replace('添加', '修改') return res except: return public.returnResult(False, traceback.format_exc()) def get_crontab_log(self, get): try: if not hasattr(get, 'id'): return public.returnResult(False, 'ID不能为空') import crontab res = crontab.crontab().GetLogs(get) return public.returnResult(True, res['msg']) except: return public.returnResult(False, traceback.format_exc()) def clearn_logs(self, get): try: if not hasattr(get, 'id'): return public.returnResult(False, 'The project name cannot be empty') import crontab crontab.crontab().DelLogs(get) return public.returnResult(True, '清空成功') except: return public.returnResult(False, traceback.format_exc()) def get_group_file(self): config = json.loads(public.readFile(self.group_file)) return config def get_group_list(self, get): try: config = self.get_group_file() result = [] for i, j in config.items(): tmp = [] for k in j['project_list']: con = self.get_project_run_state(public.to_dict_obj({'sitename': k}))['data'] if con: tmp.append(con) else: self.group_remove_project(public.to_dict_obj({'group_name': i, 'sitename': k})) j['project_list'] = tmp j['name'] = i result.append(j) for i in result: for j in i['project_list']: if not j: print(j) continue if not j['status']: i['status'] = 0 break if len(i['project_list']) == 0: i['status'] = 0 return public.returnResult(True, data=result) except: return public.returnResult(False, traceback.format_exc()) def set_order(self, get): project_list = get.project_list.strip(',').split(',') group_name = get.group_name config = self.get_group_file() if group_name not in config: return public.returnResult(False, '组名不存在') config[group_name]['project_list'] = project_list public.writeFile(self.group_file, json.dumps(config)) return public.returnResult(True, '设置成功') def create_group(self, get): try: if not hasattr(get, 'group_name'): return public.returnResult(False, '组名不能为空') config = self.get_group_file() if get.group_name in config: return public.returnResult(False, '组名已存在') config[get.group_name] = {'interval': get.get('interval', 30), 'project_list': [], 'status': 0} public.writeFile(self.group_file, json.dumps(config)) return public.returnResult(True, '添加成功') except: return public.returnResult(False, traceback.format_exc()) def remove_group(self, get): if not hasattr(get, 'group_name'): return public.returnResult(False, '组名不能为空') config = self.get_group_file() if get.group_name not in config: return public.returnResult(False, '组名不存在') del config[get.group_name] public.writeFile(self.group_file, json.dumps(config)) return public.returnResult(True, '删除成功') def group_add_project(self, get): try: if not hasattr(get, 'group_name'): return public.returnResult(False, '组名不能为空') if not hasattr(get, 'sitename'): return public.returnResult(False, '项目名不能为空') config = self.get_group_file() if get.group_name not in config: return public.returnResult(False, '组名不存在') if get.sitename not in config[get.group_name]['project_list']: config[get.group_name]['project_list'].append(get.sitename) flag = 1 for i in config[get.group_name]['project_list']: if not self.get_project_run_state(public.to_dict_obj({'sitename': i}))['status']: flag = 0 break if flag: config[get.group_name]['status'] = 1 public.writeFile(self.group_file, json.dumps(config)) return public.returnResult(True, '添加成功') except: return public.returnResult(False, traceback.format_exc()) def group_remove_project(self, get): try: if not hasattr(get, 'group_name'): return public.returnResult(False, '组名不能为空') if not hasattr(get, 'sitename'): return public.returnResult(False, '项目名不能为空') config = self.get_group_file() if get.group_name not in config: return public.returnResult(False, '组名不存在') if get.sitename not in config[get.group_name]['project_list']: return public.returnResult(False, 'The project does not exist') config[get.group_name]['project_list'].remove(get.sitename) flag = 1 for i in config[get.group_name]['project_list']: if not self.get_project_run_state(public.to_dict_obj({'sitename': i}))['status']: flag = 0 break if flag: config[get.group_name]['status'] = 1 else: config[get.group_name]['status'] = 0 public.writeFile(self.group_file, json.dumps(config)) return public.returnResult(True, '删除成功') except: return public.returnResult(False, traceback.format_exc()) def set_group_interval(self, get): if not hasattr(get, 'group_name'): return public.returnResult(False, '组名不能为空') if not hasattr(get, 'interval'): return public.returnResult(False, '间隔时间不能为空') config = self.get_group_file() if get.group_name not in config: return public.returnResult(False, '组名不存在') config[get.group_name]['interval'] = get.interval public.writeFile(self.group_file, json.dumps(config)) return public.returnResult(True, '设置成功') def set_group_status(self, get): if not hasattr(get, 'group_name'): return public.returnResult(False, '组名不能为空') if not hasattr(get, 'status'): return public.returnResult(False, '状态不能为空') config = self.get_group_file() if get.group_name not in config: return public.returnResult(False, '组名不存在') if get.status == 'stop': public.run_thread(self.stop_groups, (get.group_name,)) return public.returnResult(True, '开始停止项目') if get.status == 'start': public.run_thread(self.start_groups, (get.group_name,)) return public.returnResult(True, '开始启动项目') return public.returnResult(True, '设置成功') def stop_groups(self, group): config = self.get_group_file() if group not in config: return public.returnResult(False, '组名不存在') config[group]['status'] = 0 for i in config[group]['project_list']: self.modify_project_run_state(public.to_dict_obj({'sitename': i, 'project_action': 'stop'})) public.writeFile(self.group_file, json.dumps(config)) def start_groups(self, group): config = self.get_group_file() self.stop_groups(group) if group not in config: return public.returnResult(False, '组名不存在') for i in config[group]['project_list']: self.modify_project_run_state(public.to_dict_obj({'sitename': i, 'project_action': 'start'})) if config[group]['project_list'].index(i) == len(config[group]['project_list']) - 1: continue time.sleep(int(config[group]['interval'])) flag = 1 for i in config[group]['project_list']: if not self.get_project_run_state(public.to_dict_obj({'sitename': i}))['status']: flag = 0 break if flag: config[group]['status'] = 1 else: config[group]['status'] = 0 public.writeFile(self.group_file, json.dumps(config)) # 取代理配置文件 def get_proxy_file(self, get): try: import files conf = [] if not os.path.exists(self.__proxyfile) else json.loads(public.readFile(self.__proxyfile)) get.webserver = public.GetWebServer() sitename = get.sitename proxyname = get.proxyname proxyname_md5 = self.__calc_md5(proxyname) get.path = "%s/panel/vhost/%s/proxy/%s/%s_%s.conf" % (self.setupPath, get.webserver, sitename, proxyname_md5, sitename) for i in conf: if proxyname == i["proxyname"] and sitename == i["sitename"] and i["type"] != 1: return public.returnResult(False, '代理已暂停') f = files.files() return public.returnResult(True, data=(f.GetFileBody(get), get.path)) except: return public.returnResult(False, traceback.format_exc()) # 计算proxyname md5 def __calc_md5(self, proxyname): md5 = hashlib.md5() md5.update(proxyname.encode('utf-8')) return md5.hexdigest() # 保存重定向配置文件 def save_proxy_file(self, get): import files f = files.files() get.data = get.config return public.returnResult(True, f.SaveFileBody(get)) def set_cron_status_all(self, get): import crontab return crontab.crontab().set_cron_status_all(get) def get_index_conf(self, get): if not hasattr(get, 'id'): return public.returnResult(False, msg='请输入id') sitename = public.M('sites').where('id=?', (get.id,)).getField('name') if not sitename: return public.returnResult(False, msg='The project does not exist') res = DirTool().get_index_conf(sitename) if str(res) == str: return public.returnResult(False, msg='获取失败' + res) else: return public.returnResult(True, data=res) def set_index_conf(self, get): if not hasattr(get, 'id'): return public.returnResult(False, msg='请输入id') if not hasattr(get, 'Index'): return public.returnResult(False, msg='配置不能为空') try: sitename = public.M('sites').where('id=?', (get.id,)).getField('name') index = get.Index if type(get.Index) == str: index = get.Index.split(',') DirTool().set_index_conf(sitename, file_list=index) return public.returnResult(True, msg='设置成功') except: return public.returnResult(False, msg='设置失败') def get_referer_security(self, get): if not hasattr(get, 'id'): return public.returnResult(False, msg='请输入id') sitename = public.M('sites').where('id=?', (get.id,)).getField('name') if not sitename: return public.returnResult(False, msg='The project does not exist') get.site_name = sitename return Referer('').get_referer_security(get) def set_referer_security(self, get): return Referer('').set_referer_security(get) def get_system_user_list(self, get=None): """ 默认只返回uid>= 1000 的用户 和 root get中包含 sys_user 返回 uid>= 100 的用户 和 root get中包含 all_user 返回所有的用户 """ sys_user = False all_user = False if get is not None: if hasattr(get, "sys_user"): sys_user = True if hasattr(get, "all_user"): all_user = True user_set = set() with open('/etc/passwd') as fp: for line in fp.readlines(): tmp = line.split(':') user_name = tmp[0] uid = int(tmp[2]) if uid == 0: user_set.add(user_name) continue if uid >= 1000: user_set.add(user_name) continue if uid >= 100 and sys_user: user_set.add(user_name) continue if all_user: user_set.add(user_name) continue return list(user_set) if __name__ == "__main__": m = main() m.install_dependence("555.com", "82", "/xiaopacai/laravel11.5.0/", composer_version="2.7.3")