# coding: utf-8 # ------------------------------------------------------------------- # 宝塔Linux面板 # ------------------------------------------------------------------- # Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. # ------------------------------------------------------------------- # Author: wzz # ------------------------------------------------------------------- # ------------------------------ # docker模型 - docker runtime 基类 # ------------------------------ import json import os import sys import time from datetime import datetime, timedelta if "/www/server/panel/class" not in sys.path: sys.path.insert(0, "/www/server/panel/class") import public from btdockerModel import dk_public as dp from mod.project.docker.composeMod import main as composeMod class Sites(composeMod): def __init__(self): super(Sites, self).__init__() self.project_path = '/www/dk_project' self.sites_config_path = self.project_path + '/sites' self.site_config_path = None self.site_conf_file = None self.default_index_body = ''' 恭喜,站点创建成功!

恭喜, 站点创建成功!

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

''' self.default_404_body = ''' 404 Not Found

404 Not Found


Power by 堡塔 (免费,高效和安全的托管控制面板)
''' self.default_stop_body = ''' 抱歉,站点已暂停

抱歉!该站点已经被管理员停止运行,请联系管理员了解详情!

Power by 堡塔 (免费,高效和安全的托管控制面板)
''' self.default_not_found_body = ''' 404 Not Found

404 Not Found


nginx
''' if not os.path.exists(self.sites_config_path): public.ExecShell('mkdir -p {}'.format(self.sites_config_path)) public.ExecShell('chown -R www:www {}'.format(self.sites_config_path)) public.ExecShell('chmod -R 755 {}'.format(self.sites_config_path)) self.init_site_json = { "site_name": "", "domain_list": [], "primary_port": "", "site_port": [], "https_port": "443", "ipv4_port_conf": "listen {listen_port}{default_site};", "ipv6_status": False, "ipv6_port_conf": "listen [::]:{listen_port}{default_site};", "port_conf": "listen {listen_port};{listen_ipv6}", "ipv4_ssl_port_conf": "{ipv4_port_conf}\n listen {https_port} ssl http2 {default_site};", "ipv6_ssl_port_conf": "{ipv6_port_conf}\n listen [::]:{https_port} ssl http2 {default_site};", "ipv4_http3_ssl_port_conf": "{ipv4_port_conf}\n listen {https_port} quic{default_site};\n listen {https_port} ssl{default_site};", "ipv6_http3_ssl_port_conf": "{ipv6_port_conf}\n listen [::]:{https_port} quic{default_site};\n listen [::]:{https_port} ssl {default_site};", "site_path": "", "run_path": "/", "ssl_info": { "ssl_status": False, "ssl_default_conf": "#error_page 404/404.html;", "ssl_conf": '#error_page 404/404.html;\n ssl_certificate /www/server/panel/vhost/cert/{site_name}/fullchain.pem;\n ssl_certificate_key /www/server/panel/vhost/cert/{site_name}/privkey.pem;\n ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;\n ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;\n ssl_prefer_server_ciphers on;\n ssl_session_cache shared:SSL:10m;\n ssl_session_timeout 10m;\n add_header Strict-Transport-Security "max-age=31536000";\n error_page 497 https://$host$request_uri;', "force_ssl_conf": '#error_page 404/404.html;{force_conf}\n ssl_certificate /www/server/panel/vhost/cert/{site_name}/fullchain.pem;\n ssl_certificate_key /www/server/panel/vhost/cert/{site_name}/privkey.pem;\n ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;\n ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;\n ssl_prefer_server_ciphers on;\n ssl_session_cache shared:SSL:10m;\n ssl_session_timeout 10m;\n add_header Strict-Transport-Security "max-age=31536000";\n error_page 497 https://$host$request_uri;', "force_https": False, "force_conf": " #HTTP_TO_HTTPS_START\n set $isRedcert 1;\n if ($server_port != 443) {\n set $isRedcert 2;\n }\n if ( $uri ~ /\.well-known/ ) {\n set $isRedcert 1;\n }\n if ($isRedcert != 1) {\n rewrite ^(/.*)$ https://$host$1 permanent;\n }\n #HTTP_TO_HTTPS_END", }, "err_age_404": "", "err_age_502": "", "ip_limit": { "ip_black": [], "ip_white": [], }, "basic_auth": [], "proxy_cache": { "cache_status": False, "cache_zone": "", "static_cache": "", "expires": "1d", "cache_conf": "", }, "gzip": { "gzip_status": False, "gzip_min_length": "1k", "gzip_comp_level": "6", "gzip_types": "text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/json image/jpeg image/gif image/png font/ttf font/otf image/svg+xml application/xml+rss text/x-js", "gzip_conf": "gzip on;\n gzip_min_length 10k;\n gzip_buffers 4 16k;\n gzip_http_version 1.1;\n gzip_comp_level 2;\n gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;\n gzip_vary on;\n gzip_proxied expired no-cache no-store private auth;\n gzip_disable \"MSIE [1-6]\.\";", }, "subs_filter": False, "sub_filter": { "sub_filter_str": [], }, "websocket": { "websocket_status": True, "websocket_conf": "proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection $connection_upgrade;", }, "security": { "security_status": False, "static_resource": "jpg|jpeg|gif|png|js|css", "return_resource": "404", "http_status": False, "domains": "", "security_conf": " #SECURITY-START 防盗链配置" "\n location ~ .*\.({static_resource})$" "\n {{\n expires {expires};" "\n access_log /dev/null;" "\n valid_referers {domains};" "\n if ($invalid_referer){{" "\n return {return_resource};" "\n }}" "\n }}\n #SECURITY-END", }, "redirect": { "redirect_status": False, "redirect_conf": " #引用重定向规则,注释后配置的重定向代理将无效\n include /www/server/panel/vhost/nginx/redirect/{site_name}/*.conf;", }, "proxy_log": { "log_type": "default", "server_port": "", "log_path": "", "log_conf": "\naccess_log {log_path}/{site_name}.log;\n error_log {log_path}/{site_name}.error.log;", }, "default_cache": "proxy_cache_path {site_config_path}/proxy_cache_dir levels=1:2 keys_zone={cache_name}_cache:20m inactive=1d max_size=5g;", "default_describe": "# 如果反代网站访问异常且这里已经配置了内容,请优先排查此处的配置是否正确\n", "http_block": "", "server_block": "", "remark": "", "enable_php_conf": "", "rewrite_conf": "", "index_conf": "index.php index.html index.htm default.php default.htm default.html", "redirect_conf_404": "", "stop_site": False, "stop_site_conf": '''rewrite ^/(?!bt-stop\.html$).* /bt-stop.html last; location = /bt-stop.html { # 网站停止,并设置网站停止页面 root /www/server/stop; }''', "proxy_info": [], "classify": 0, } self._template_conf = '''{http_block} server {{ {port_conf} server_name {domains}; index {index_conf}; root {site_path}{run_path}; {stop_site_conf} #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} {ssl_info} #SSL-END #REDIRECT START {redirect_conf_404} {redirect_conf} #REDIRECT END #ERROR-PAGE-START {err_page_msg} {err_age_404} {err_age_502} #ERROR-PAGE-END #PHP-INFO-START PHP引用配置,可以注释或修改 {security_conf} {enable_php_conf} #PHP-INFO-END #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效 {rewrite_conf} #REWRITE-END #IP-RESTRICT-START 限制访问ip的配置,IP黑白名单 {ip_limit_conf} #IP-RESTRICT-END #BASICAUTH START {auth_conf} #BASICAUTH END #SUB_FILTER START {sub_filter} #SUB_FILTER END #GZIP START {gzip_conf} #GZIP END #GLOBAL-CACHE START {proxy_cache} #GLOBAL-CACHE END #WEBSOCKET-SUPPORT START {websocket_support} #WEBSOCKET-SUPPORT END #PROXY-CONF-START {proxy_conf} #PROXY-CONF-END #SERVER-BLOCK START {server_block} #SERVER-BLOCK END # 禁止访问的敏感文件 location ~* (\.user.ini|\.htaccess|\.htpasswd|\.env.*|\.project|\.bashrc|\.bash_profile|\.bash_logout|\.DS_Store|\.gitignore|\.gitattributes|LICENSE|README\.md|CLAUDE\.md|CHANGELOG\.md|CHANGELOG|CONTRIBUTING\.md|TODO\.md|FAQ\.md|composer\.json|composer\.lock|package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|\.\w+~|\.swp|\.swo|\.bak(up)?|\.old|\.tmp|\.temp|\.log|\.sql(\.gz)?|docker-compose\.yml|docker\.env|Dockerfile|\.csproj|\.sln|Cargo\.toml|Cargo\.lock|go\.mod|go\.sum|phpunit\.xml|phpunit\.xml|pom\.xml|build\.gradl|pyproject\.toml|requirements\.txt|application(-\w+)?\.(ya?ml|properties))$ {{ return 404; }} # 禁止访问的敏感目录 location ~* /(\.git|\.svn|\.bzr|\.vscode|\.claude|\.idea|\.ssh|\.github|\.npm|\.yarn|\.pnpm|\.cache|\.husky|\.turbo|\.next|\.nuxt|node_modules|runtime)/ {{ 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; }} #LOG START {server_log} {monitor_conf} #LOG END }}''' self._template_proxy_conf = '''location ^~ {proxy_path} {{ {ip_limit} {basic_auth} proxy_pass {proxy_pass}; proxy_set_header Host {proxy_host}; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-Port $remote_port; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header REMOTE-HOST $remote_addr; {SNI} {PROXY_BUFFERING} {timeout_conf} {websocket_support} {custom_conf} {proxy_cache} {gzip} {sub_filter} {server_log} }}''' def structure_proxy_conf(self, get): ''' @name @author wzz <2024/4/19 下午4:29> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' sni_conf = "" if get.proxy_pass.startswith("https://"): sni_conf = "proxy_ssl_server_name on;" get.proxy_conf = self._template_proxy_conf.format( ip_limit="", gzip="", proxy_cache="", sub_filter="", server_log="", basic_auth="", proxy_pass=get.proxy_pass, proxy_host=get.proxy_host, proxy_path=get.proxy_path, SNI=sni_conf, PROXY_BUFFERING="", custom_conf="", timeout_conf=get.proxy_timeout, websocket_support=self.init_site_json["websocket"]["websocket_conf"], ) get.proxy_info = { "proxy_type": get.proxy_type, "proxy_path": get.proxy_path, "proxy_pass": get.proxy_pass, "proxy_host": get.proxy_host, "ip_limit": { "ip_black": [], "ip_white": [], }, "basic_auth": {}, "proxy_cache": { "cache_status": False, "cache_zone": get.site_name.replace(".", "_") + "_cache", "static_cache": "", "expires": "1d", "cache_conf": "", }, "gzip": { "gzip_status": False, "gzip_min_length": "1k", "gzip_comp_level": "6", "gzip_types": "text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/json image/jpeg image/gif image/png font/ttf font/otf image/svg+xml application/xml+rss text/x-js", "gzip_conf": "gzip on;\n gzip_min_length 10k;\n gzip_buffers 4 16k;\n gzip_http_version 1.1;\n gzip_comp_level 2;\n gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;\n gzip_vary on;\n gzip_proxied expired no-cache no-store private auth;\n gzip_disable \"MSIE [1-6]\.\";", }, "sub_filter": { "sub_filter_str": [], }, "websocket": { "websocket_status": True, "websocket_conf": "proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection $connection_upgrade;", }, "proxy_log": { "log_type": "off", "log_conf": get.server_log, }, "timeout": { "proxy_connect_timeout": "60", "proxy_send_timeout": "600", "proxy_read_timeout": "600", "timeout_conf": "proxy_connect_timeout 60s;\n proxy_send_timeout 600s;\n proxy_read_timeout 600s;", }, "custom_conf": "", "proxy_conf": get.proxy_conf, "remark": "", "template_proxy_conf": self._template_proxy_conf, "proxy_buffering": True, } # 2024/4/18 上午10:53 构造反向代理的配置文件 def structure_nginx(self, get): ''' @name 构造反向代理的配置文件 @author wzz <2024/4/18 上午10:54> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' if not os.path.exists(os.path.join(self.sites_config_path, "no_auto_create_404.pl")): get.err_age_404 = "error_page 404 /404.html;" else: get.err_age_404 = "#error_page 404 /404.html;" get.err_age_502 = get.get("err_age_502", "#error_page 502 /502.html;") get.proxy_info = get.get("proxy_info", "") get.server_log =self.init_site_json["proxy_log"]["log_conf"].format( log_path=public.get_logs_path(), site_name=get.site_name ) if get.site_port != "80": get.server_log = self.init_site_json["proxy_log"]["log_conf"].format( log_path=public.get_logs_path(), site_name="{}_{}".format(get.site_name, get.site_port) ) get.remark = get.get("remark", "") get.server_block = get.get("server_block", "") get.websocket_status = get.get("websocket_status", True) get.proxy_timeout = "proxy_connect_timeout 60s;\n proxy_send_timeout 600s;\n proxy_read_timeout 600s;" get.proxy_conf = "" if get.type != "php": self.structure_proxy_conf(get) is_subs = public.ExecShell("nginx -V 2>&1|grep 'ngx_http_substitutions_filter' -o")[0] self.init_site_json["subs_filter"] = True if is_subs != "" else False self.init_site_json["site_name"] = get.domain_name self.init_site_json["domain_list"] = get.domain_list self.init_site_json["site_port"] = get.port_list self.init_site_json["site_path"] = get.site_path self.init_site_json["err_age_404"] = get.err_age_404 self.init_site_json["err_age_502"] = get.err_age_502 self.init_site_json["proxy_log"]["log_conf"] = get.server_log self.init_site_json["remark"] = get.remark self.init_site_json["http_block"] = "" self.init_site_json["enable_php_conf"] = get.enable_php_conf self.init_site_json["proxy_info"] = [] if not get.proxy_info else [get.proxy_info] self.init_site_json["proxy_cache"]["cache_zone"] = get.site_name.replace(".", "_") + "_cache" if get.site_port == "80" else "{}_{}_cache".format(get.site_name.replace(".", "_"), get.site_port) self.init_site_json["primary_port"] = get.site_port self.init_site_json["type"] = get.type # 2024/4/18 上午10:35 写入Nginx配置文件 def write_nginx_conf(self, get): ''' @name 写入Nginx配置文件 @author wzz <2024/4/18 上午10:36> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' # 这里可能会报错 self.structure_nginx(get) if len(get.port_list) > 1: ipv4_port_conf = "" for p in get.port_list: ipv4_port_conf += self.init_site_json["ipv4_port_conf"].format(listen_port=p, default_site="") + "\n " else: ipv4_port_conf = self.init_site_json["ipv4_port_conf"].format(listen_port=get.port_list[0], default_site="") port_conf = ipv4_port_conf + "\n" # 2024/6/4 下午4:20 兼容新版监控报表的配置 monitor_conf = "" if os.path.exists("/www/server/panel/plugin/monitor/monitor_main.py"): monitor_conf = '''#Monitor-Config-Start 网站监控报表日志发送配置 access_log syslog:server=unix:/tmp/bt-monitor.sock,nohostname,tag=dp{pid}__access monitor; error_log syslog:server=unix:/tmp/bt-monitor.sock,nohostname,tag=dp{pid}__error; #Monitor-Config-End'''.format(pid=get.pid) import PluginLoader args = public.dict_obj() args.site_id = get.pid args.site_name = get.site_name PluginLoader.plugin_run("monitor", "init_docker_site_config", args) conf = self._template_conf.format( http_block=get.http_block, server_block="", port_conf=port_conf, index_conf=self.init_site_json["index_conf"], ssl_start_msg=public.getMsg('NGINX_CONF_MSG1'), err_page_msg=public.getMsg('NGINX_CONF_MSG2'), php_info_start=public.getMsg('NGINX_CONF_MSG3'), rewrite_start_msg=public.getMsg('NGINX_CONF_MSG4'), rewrite_conf="", redirect_conf_404="", log_path=public.get_logs_path(), domains=' '.join(get.domain_list) if len(get.domain_list) > 1 else get.site_name, site_name=get.site_name, ssl_info="#error_page 404/404.html;", err_age_404=get.err_age_404, err_age_502=get.err_age_502, ip_limit_conf="", auth_conf="", sub_filter="", gzip_conf="", redirect_conf="", security_conf="", enable_php_conf=get.enable_php_conf, proxy_conf=get.proxy_conf, server_log=get.server_log, site_path=get.site_path, run_path="", stop_site_conf="", proxy_cache="", websocket_support=self.init_site_json["websocket"]["websocket_conf"], monitor_conf=monitor_conf, ) # 写配置文件 well_known_path = "{}/vhost/nginx/well-known".format(public.get_panel_path()) if not os.path.exists(well_known_path): os.makedirs(well_known_path, 0o600) public.writeFile("{}/{}.conf".format(well_known_path, get.domain_name), "") get.filename = public.get_setup_path() + '/panel/vhost/nginx/' + get.domain_name + '.conf' return public.writeFile(get.filename, conf) # 2024/7/29 下午2:39 创建必须目录 def create_must_dir(self, get): ''' @name @author wzz <2024/7/29 下午2:40> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' if not os.path.exists(self.site_config_path): public.ExecShell('mkdir -p {}'.format(self.site_config_path)) public.ExecShell('chown -R www:www {}'.format(self.site_config_path)) public.ExecShell('chmod -R 755 {}'.format(self.site_config_path)) if not os.path.exists(get.site_path): public.ExecShell('mkdir -p {}'.format(get.site_path)) if get.type == "php": public.ExecShell("chown -R 1000:1000 {}".format(get.site_path)) else: public.ExecShell('chown -R www:www {}'.format(get.site_path)) public.ExecShell('chmod -R 755 {}'.format(get.site_path)) public.ExecShell("chown -R www:www /www/dk_project/wwwroot") public.ExecShell("chmod -R 755 /www/dk_project/wwwroot") if not os.path.exists(self.site_config_path + "/proxy_cache_dir"): public.ExecShell('mkdir -p {}'.format(self.site_config_path + "/proxy_cache_dir")) public.ExecShell('chown -R www:www {}'.format(self.site_config_path + "/proxy_cache_dir")) public.ExecShell('chmod -R 755 {}'.format(self.site_config_path + "/proxy_cache_dir")) # 2024/7/29 上午11:46 创建完成后置检测 def check_after_create(self, get): ''' @name 创建完成后置检测 @author wzz <2024/7/29 上午11:47> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' wc_err = public.checkWebConfig() if not wc_err: public.ExecShell("rm -f {}".format(self.site_conf_file)) public.ExecShell("rm -rf {}".format(get.filename)) dp.sql("docker_sites").where('id=?', (get.pid,)).delete() dp.sql("docker_domain").where('pid=?', (get.pid,)).delete() return public.returnResult( status=False, msg='ERROR: 检测到配置文件有错误,请先排除后再操作

' + wc_err.replace("\n", '
') + '
' ) if type(wc_err) != bool and "test failed" in wc_err: public.ExecShell("rm -f {}".format(self.site_conf_file)) public.ExecShell("rm -rf {}".format(get.filename)) dp.sql("docker_sites").where('id=?', (get.pid,)).delete() dp.sql("docker_domain").where('pid=?', (get.pid,)).delete() return public.returnResult( status=False, msg='ERROR: 检测到配置文件有错误,请先排除后再操作

' + wc_err.replace("\n", '
') + '
' ) return public.returnResult(True) # 2024/7/29 上午11:31 插入sites表 def insert_sites(self, get): ''' @name 插入sites或dk_sites表 @author wzz <2024/7/29 上午11:32> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' addtime = public.getDate() pdata = { "name": get.domain_name, "path": get.site_path, "run_path": "/", "remark": get.remark, "type": get.type, "service_info": json.dumps(get.service_info), "addtime": addtime, "container_id": get.container_id, } get.pid = dp.sql("docker_sites").insert(pdata) if not get.pid: return public.returnResult(False, '添加失败,无法将数据插入到网站数据库中') for domain in get.domain_list: dp.sql("docker_domain").insert({ "name": domain, "pid": get.pid, "port": "80" if ":" not in domain else domain.split(":")[1], "addtime": addtime, }) return public.returnResult(True) def read_json_conf(self, get): ''' @name @author wzz <2024/4/18 下午9:53> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' conf_path = "{path}/{site_name}/{site_name}.json".format( path=self.sites_config_path, site_name=get.site_name ) try: proxy_json_conf = json.loads(public.readFile(conf_path)) except Exception as e: proxy_json_conf = {} return proxy_json_conf # 2024/4/26 下午4:57 获取证书的部署状态 def get_site_ssl_info(self, siteName): ''' @name 获取证书的部署状态 @author wzz <2024/4/26 下午4:58> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' try: import time import re s_file = 'vhost/nginx/{}.conf'.format(siteName) is_apache = False if not os.path.exists(s_file): s_file = 'vhost/apache/{}.conf'.format(siteName) is_apache = True if not os.path.exists(s_file): return -1 s_conf = public.readFile(s_file) if not s_conf: return -1 ssl_file = None if is_apache: if s_conf.find('SSLCertificateFile') == -1: return -1 s_tmp = re.findall(r"SSLCertificateFile\s+(.+\.pem)", s_conf) if not s_tmp: return -1 ssl_file = s_tmp[0] else: if s_conf.find('ssl_certificate') == -1: return -1 s_tmp = re.findall(r"ssl_certificate\s+(.+\.pem);", s_conf) if not s_tmp: return -1 ssl_file = s_tmp[0] ssl_info = public.get_cert_data(ssl_file) if not ssl_info: return -1 ssl_info['endtime'] = int( int(time.mktime(time.strptime(ssl_info['notAfter'], "%Y-%m-%d")) - time.time()) / 86400) return ssl_info except: return -1 # 2024/4/20 下午3:11 根据proxy_json_conf,填入self._template_conf,然后生成nginx配置,保存到指定网站的conf文件中 def generate_config(self, get): """ @name 根据proxy_json_conf,填入self._template_conf,然后生成nginx配置,保存到指定网站的conf文件中 @param get: @return: """ # 2024/4/20 下午3:36 构造ip黑白名单 ip_black = "" ip_white = "" for ip in get.proxy_json_conf["ip_limit"]["ip_black"]: ip_black += ("deny {};\n ").format(ip) for ip in get.proxy_json_conf["ip_limit"]["ip_white"]: ip_white += ("allow {};\n ").format(ip) if ip_white != "": ip_white += "deny all;" ip_limit_conf = ip_black + "\n " + ip_white proxy_conf = "" ignore_path = [] if len(get.proxy_json_conf["proxy_info"]) != 0 and not get.proxy_json_conf["stop_site"]: for info in get.proxy_json_conf["proxy_info"]: proxy_auth_conf = "" if info["basic_auth"]: proxy_auth_conf = ("auth_basic \"Authorization\";" "\n auth_basic_user_file {auth_file};").format( auth_path=info["basic_auth"]["auth_path"], auth_file=info["basic_auth"]["auth_file"], ) if len(get.proxy_json_conf["basic_auth"]) != 0: for auth in get.proxy_json_conf["basic_auth"]: if info["proxy_path"] == auth["auth_path"]: ignore_path.append(auth["auth_path"]) proxy_auth_conf = ("auth_basic \"Authorization\";" "\n auth_basic_user_file {auth_file};").format( auth_path=auth["auth_path"], auth_file=auth["auth_file"], ) break p_ip_black = "" p_ip_white = "" for ip in info["ip_limit"]["ip_black"]: p_ip_black += ("deny {};\n ").format(ip) for ip in info["ip_limit"]["ip_white"]: p_ip_white += ("allow {};\n ").format(ip) if p_ip_white != "": p_ip_white += "deny all;" p_ip_limit_conf = p_ip_black + "\n " + p_ip_white if p_ip_black == "" and p_ip_white == "": p_ip_limit_conf = "" p_gzip_conf = "" if info["gzip"]["gzip_status"]: p_gzip_conf = info["gzip"]["gzip_conf"] p_sub_filter = "" if len(info["sub_filter"]["sub_filter_str"]) != 0: p_sub_filter = 'proxy_set_header Accept-Encoding \"\";' if not "subs_filter" in get.proxy_json_conf: get.proxy_json_conf["subs_filter"] = \ public.ExecShell("nginx -V 2>&1|grep 'ngx_http_substitutions_filter' -o")[0] != "" if not get.proxy_json_conf["subs_filter"]: for filter in info["sub_filter"]["sub_filter_str"]: p_sub_filter += "\n sub_filter {oldstr} {newstr};".format( oldstr=filter["oldstr"] if filter["oldstr"] != "" else "\"\"", newstr=filter["newstr"] if filter["newstr"] != "" else "\"\"", ) p_sub_filter += "\n sub_filter_once off;" else: for filter in info["sub_filter"]["sub_filter_str"]: p_sub_filter += "\n subs_filter {oldstr} {newstr} {sub_type};".format( oldstr=filter["oldstr"] if filter["oldstr"] != "" else "\"\"", newstr=filter["newstr"] if filter["newstr"] != "" else "\"\"", sub_type=filter["sub_type"] if "sub_type" in filter and filter[ "sub_type"] != "" else "\"\"", ) p_websocket_support = "" if info["websocket"]["websocket_status"]: p_websocket_support = info["websocket"]["websocket_conf"] timeout_conf = ("proxy_connect_timeout {proxy_connect_timeout};" "\n proxy_send_timeout {proxy_send_timeout};" "\n proxy_read_timeout {proxy_read_timeout};").format( proxy_connect_timeout=info["timeout"]["proxy_connect_timeout"].replace("s", "") + "s", proxy_send_timeout=info["timeout"]["proxy_send_timeout"].replace("s", "") + "s", proxy_read_timeout=info["timeout"]["proxy_read_timeout"].replace("s", "") + "s", ) sni_conf = "" if info["proxy_pass"].startswith("https://"): sni_conf = "proxy_ssl_server_name on;" tmp_conf = info["template_proxy_conf"].format( basic_auth=proxy_auth_conf, ip_limit=p_ip_limit_conf, gzip=p_gzip_conf, sub_filter=p_sub_filter, proxy_cache=info["proxy_cache"]["cache_conf"], server_log="", proxy_pass=info["proxy_pass"], proxy_host=info["proxy_host"], proxy_path=info["proxy_path"], custom_conf=info["custom_conf"], timeout_conf=timeout_conf, websocket_support=p_websocket_support, SNI=sni_conf, PROXY_BUFFERING="proxy_buffering off;" if "proxy_buffering" in info and info["proxy_buffering"] else "" and not info["proxy_buffering"], ) info["proxy_conf"] = tmp_conf proxy_conf += tmp_conf + "\n " # 2024/4/20 下午3:37 构造basicauth auth_conf = "" if len(get.proxy_json_conf["basic_auth"]) != 0: for auth in get.proxy_json_conf["basic_auth"]: if auth["auth_path"] not in ignore_path: tmp_conf = ("location ^~ {auth_path} {{" "\n auth_basic \"Authorization\";" "\n auth_basic_user_file {auth_file};" "\n }}").format(auth_path=auth["auth_path"], auth_file=auth["auth_file"]) auth_conf += tmp_conf + "\n " websocket_support = "" if get.proxy_json_conf["websocket"]["websocket_status"]: websocket_support = get.proxy_json_conf["websocket"]["websocket_conf"] gzip_conf = "" if get.proxy_json_conf["gzip"]["gzip_status"]: gzip_conf = get.proxy_json_conf["gzip"]["gzip_conf"] ssl_conf = "#error_page 404/404.html;" if not "https_port" in get.proxy_json_conf or get.proxy_json_conf["https_port"] == "": get.proxy_json_conf["https_port"] = "443" if not "ipv4_port_conf" in get.proxy_json_conf: get.proxy_json_conf["ipv4_port_conf"] = "listen {listen_port};" if not "ipv6_port_conf" in get.proxy_json_conf: get.proxy_json_conf["ipv6_port_conf"] = "listen [::]:{listen_port};" if not "ipv4_http3_ssl_port_conf" in get.proxy_json_conf: get.proxy_json_conf[ "ipv4_http3_ssl_port_conf"] = "{ipv4_port_conf}\n listen {https_port} quic;\n listen {https_port} ssl;" if not "ipv6_http3_ssl_port_conf" in get.proxy_json_conf: get.proxy_json_conf[ "ipv6_http3_ssl_port_conf"] = "{ipv6_port_conf}\n listen [::]:{https_port} quic;\n listen [::]:{https_port} ssl ;" if not "ipv4_ssl_port_conf" in get.proxy_json_conf: get.proxy_json_conf["ipv4_ssl_port_conf"] = "{ipv4_port_conf}\n listen {https_port} ssl http2 ;" if not "ipv6_ssl_port_conf" in get.proxy_json_conf: get.proxy_json_conf["ipv6_ssl_port_conf"] = "{ipv6_port_conf}\n listen [::]:{https_port} ssl http2 ;" if not "ipv6_status" in get.proxy_json_conf: get.proxy_json_conf["ipv6_status"] = False ipv4_port_conf = "" ipv6_port_conf = "" default_site = public.readFile('/www/server/panel/data/defaultSite.pl') if not default_site: default_server = "" else: if default_site == get.site_name: default_server = " default_server" else: default_server = "" for p in get.proxy_json_conf["site_port"]: ipv4_port_conf += get.proxy_json_conf["ipv4_port_conf"].format( listen_port=p, default_site=default_server, ) + "\n " if get.proxy_json_conf["ipv6_status"]: ipv6_port_conf += get.proxy_json_conf["ipv6_port_conf"].format( listen_port=p, default_site=default_server, ) + "\n " if get.proxy_json_conf["ssl_info"]["ssl_status"]: if public.is_nginx_http3(): ipv4_http3_ssl_port_conf = get.proxy_json_conf["ipv4_http3_ssl_port_conf"].format( ipv4_port_conf=ipv4_port_conf, https_port=get.proxy_json_conf["https_port"], default_site=default_server, ) + "\n " ipv6_http3_ssl_port_conf = "" if get.proxy_json_conf["ipv6_status"]: ipv6_http3_ssl_port_conf = get.proxy_json_conf["ipv6_http3_ssl_port_conf"].format( ipv6_port_conf=ipv6_port_conf, https_port=get.proxy_json_conf["https_port"], default_site=default_server, ) + "\n " port_conf = ipv4_http3_ssl_port_conf + ipv6_http3_ssl_port_conf + "\n http2 on;\n http3 on;" else: ipv4_ssl_port_conf = get.proxy_json_conf["ipv4_ssl_port_conf"].format( ipv4_port_conf=ipv4_port_conf, https_port=get.proxy_json_conf["https_port"], default_site=default_server, ) + "\n " ipv6_ssl_port_conf = "" if get.proxy_json_conf["ipv6_status"]: ipv6_ssl_port_conf = get.proxy_json_conf["ipv6_ssl_port_conf"].format( ipv6_port_conf=ipv6_port_conf, https_port=get.proxy_json_conf["https_port"], default_site=default_server, ) + "\n " port_conf = ipv4_ssl_port_conf + ipv6_ssl_port_conf ssl_conf = get.proxy_json_conf["ssl_info"]["ssl_conf"].format(site_name=get.site_name) if get.proxy_json_conf["ssl_info"]["force_https"]: ssl_conf = get.proxy_json_conf["ssl_info"]["force_ssl_conf"].format( site_name=get.site_name, force_conf=get.proxy_json_conf["ssl_info"]["force_conf"] ) if public.is_nginx_http3(): http3_header = '''\n add_header Alt-Svc 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"';''' ssl_conf += http3_header + "\n quic_retry on;\n quic_gso on;" if public.ng_ssl_early_data_enabled(): ssl_conf += "\n ssl_early_data on;" else: port_conf = ipv4_port_conf + "\n" + ipv6_port_conf redirect_conf = "" if get.proxy_json_conf["redirect"]["redirect_status"]: redirect_conf = get.proxy_json_conf["redirect"]["redirect_conf"].format(site_name=get.site_name) security_conf = "" if get.proxy_json_conf["security"]["security_status"]: domains = get.proxy_json_conf["security"]["domains"] if not get.proxy_json_conf["security"][ "http_status"] else "none blocked " + get.proxy_json_conf["security"]["domains"] security_conf = get.proxy_json_conf["security"]["security_conf"].format( static_resource=get.proxy_json_conf["security"]["static_resource"], expires="30d", domains=domains.replace(",", " "), return_resource=get.proxy_json_conf["security"]["return_resource"], ) self.site_config_path = os.path.join(self.sites_config_path, get.site_name) if "/www/wwwroot/{site_name}" in get.proxy_json_conf["default_cache"]: get.proxy_json_conf["default_cache"] = self.init_site_json["default_cache"] if not os.path.exists(self.site_config_path + "/proxy_cache_dir"): public.ExecShell('mkdir -p {}'.format(self.site_config_path + "/proxy_cache_dir")) public.ExecShell('chown -R www:www {}'.format(self.site_config_path + "/proxy_cache_dir")) public.ExecShell('chmod -R 755 {}'.format(self.site_config_path + "/proxy_cache_dir")) default_cache = get.proxy_json_conf["default_cache"].format( site_config_path=self.site_config_path, cache_name=get.site_name.replace(".", "_") ) get.http_block = default_cache + "\n" + get.proxy_json_conf["http_block"] # 2024/6/4 下午4:20 兼容新版监控报表的配置 monitor_conf = "" if (os.path.exists("/www/server/panel/plugin/monitor/monitor_main.py") and os.path.exists("/www/server/monitor/config/sites.json")): try: sites_data = json.loads(public.readFile("/www/server/monitor/config/sites.json")) if sites_data[get.site_name]["open"]: id = public.M('docker_domain').where("name=?", (get.site_name,)).getField('pid') if id: monitor_conf = '''#Monitor-Config-Start 网站监控报表日志发送配置 access_log syslog:server=unix:/tmp/bt-monitor.sock,nohostname,tag=dp{sid}__access {log_format}; error_log syslog:server=unix:/tmp/bt-monitor.sock,nohostname,tag=dp{sid}__error; #Monitor-Config-End'''.format(sid=id, log_format=sites_data[get.site_name].get("log_format", "monitor")) except: pass if not "index_conf" in get.proxy_json_conf: get.proxy_json_conf["index_conf"] = self.init_site_json["index_conf"] if not "rewrite_conf" in get.proxy_json_conf: get.proxy_json_conf["rewrite_conf"] = "" if not "redirect_conf_404" in get.proxy_json_conf: get.proxy_json_conf["redirect_conf_404"] = "" if not "run_path" in get.proxy_json_conf: get.proxy_json_conf["run_path"] = "" if not "enable_php_conf" in get.proxy_json_conf: get.proxy_json_conf["enable_php_conf"] = False get.site_conf = self._template_conf.format( http_block=get.http_block, server_block=get.proxy_json_conf["server_block"], port_conf=port_conf, index_conf=get.proxy_json_conf["index_conf"], ssl_start_msg=public.getMsg('NGINX_CONF_MSG1'), err_page_msg=public.getMsg('NGINX_CONF_MSG2'), php_info_start=public.getMsg('NGINX_CONF_MSG3'), rewrite_start_msg=public.getMsg('NGINX_CONF_MSG4'), rewrite_conf=get.proxy_json_conf["rewrite_conf"], redirect_conf_404=get.proxy_json_conf["redirect_conf_404"], domains=' '.join(get.proxy_json_conf["domain_list"]) if len(get.proxy_json_conf["domain_list"]) > 1 else get.site_name, site_name=get.site_name, ssl_info=ssl_conf, err_age_404=get.proxy_json_conf["err_age_404"], err_age_502=get.proxy_json_conf["err_age_502"], ip_limit_conf=ip_limit_conf, auth_conf=auth_conf, sub_filter="", gzip_conf=gzip_conf, security_conf=security_conf, redirect_conf=redirect_conf, proxy_conf=proxy_conf, proxy_cache=get.proxy_json_conf["proxy_cache"]["cache_conf"], server_log=get.proxy_json_conf["proxy_log"]["log_conf"], site_path=get.proxy_json_conf["site_path"] if not get.proxy_json_conf["stop_site"] else "{}/stop".format(self.sites_config_path), run_path=get.proxy_json_conf["run_path"] if get.proxy_json_conf["run_path"] != "/" else "", stop_site_conf=get.proxy_json_conf["stop_site_conf"] if get.proxy_json_conf["stop_site"] else "", websocket_support=websocket_support, monitor_conf=monitor_conf, enable_php_conf=get.proxy_json_conf["enable_php_conf"], ) def xsssec(self, text): replace_list = { "<": "<", ">": ">", "'": "'", '"': """, } for k, v in replace_list.items(): text = text.replace(k, v) return public.xssencode2(text) # 域名编码转换 def ToPunycode(self, domain): try: # import OpenSSL import idna except: os.system("btpip install idna -I") import idna import re match = re.search(u"[^u\0000-u\001f]+", domain) if not match: return domain try: if domain.startswith("*."): return "*." + idna.encode(domain[2:]).decode("utf8") else: return idna.encode(domain).decode("utf8") except: return domain # 2024/4/23 下午2:12 保存并重新生成新的nginx配置文件 def update_nginx_conf(self, get): ''' @name @author wzz <2024/4/23 下午2:13> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' get.conf_file = public.get_setup_path() + '/panel/vhost/nginx/' + get.site_name + '.conf' self.generate_config(get) get.data = get.site_conf get.encoding = "utf-8" get.path = get.conf_file import files f = files.files() save_result = f.SaveFileBody(get) if save_result["status"] == False: return public.returnResult(status=False, msg=save_result["msg"]) self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( path=self.sites_config_path, site_name=get.site_name ) public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) return public.returnResult(msg="保存成功!")