Initial YakPanel commit

This commit is contained in:
Niranjan
2026-04-07 02:04:22 +05:30
commit 2826d3e7f3
5359 changed files with 1390724 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
# coding: utf-8
# -------------------------------------------------------------------
# yakpanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 yakpanel(http://www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
import os
import subprocess
import sys
import threading
def run_cmd(cmd):
try:
subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return True
except:
return False
def load_iptables():
"""
恢复 iptables 规则
"""
if run_cmd("iptables -C INPUT -j IN_BT"):
print("iptables existed")
else:
if run_cmd("iptables-restore --noflush < /www/server/panel/data/iptablesdata"):
print("iptables restored")
def load_ipset():
"""
恢复 ipset 规则
"""
if run_cmd("ipset restore < /www/server/panel/data/ipsetdata"):
print("ipset restored")
else:
print("ipset existed")
def save_iptables():
"""
保存 iptables 规则
"""
if run_cmd("iptables -C INPUT -j IN_BT"):
if run_cmd(
"iptables-save | grep -E 'IN_BT|OUT_BT|FORWARD_BT|^\*|^COMMIT' | sed 's/^-A INPUT/-I INPUT/; s/^-A OUTPUT/-I OUTPUT/; s/^-A PREROUTING/-I PREROUTING/' > /www/server/panel/data/iptablesdata"):
print("iptables saved")
def save_ipset():
"""
保存 ipset 规则
"""
if run_cmd("ipset save | grep -E '_bt_' > /www/server/panel/data/ipsetdata"):
print("ipset saved")
def dbus_listener():
if not os.path.exists("/sbin/firewalld"):
print("is not Firewalld")
return
cmd = [
"dbus-monitor",
"--system",
"type='signal',path='/org/fedoraproject/FirewallD1',interface='org.fedoraproject.FirewallD1',member='Reloaded'",
"type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.fedoraproject.FirewallD1',arg1=''"
]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
while True:
line = process.stdout.readline().strip()
if not line:
break
if "signal" in line:
if "member=Reloaded" in line:
print("firewalld reload...")
load_iptables()
elif "member=NameOwnerChanged" in line:
print("firewalld restart...")
threading.Timer(3, load_iptables).start()
def main():
import time
if len(sys.argv) < 2:
print("commandstart|reload|stop|save")
sys.exit(1)
command = sys.argv[1]
if command == "start":
load_ipset()
load_iptables()
listener_thread = threading.Thread(target=dbus_listener)
listener_thread.daemon = True
listener_thread.start()
while True:
time.sleep(1)
elif command == "reload":
save_ipset()
save_iptables()
load_ipset()
load_iptables()
elif command == "stop":
save_ipset()
save_iptables()
elif command == "save":
save_ipset()
save_iptables()
elif command == "saveiptables":
save_iptables()
elif command == "saveipset":
save_ipset()
elif command == "loadiptables":
load_iptables()
elif command == "loadipset":
load_ipset()
elif command == "reloadiptables":
save_iptables()
load_iptables()
elif command == "reloadipset":
save_ipset()
load_ipset()
else:
sys.exit(1)
if __name__ == "__main__":
main()

26
script/GetOS.sh Normal file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
if grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then
OSNAME='CentOS'
elif grep -Eqi "Red Hat Enterprise Linux Server" /etc/issue || grep -Eq "Red Hat Enterprise Linux Server" /etc/*-release; then
OSNAME='RHEL'
elif grep -Eqi "Aliyun" /etc/issue || grep -Eq "Aliyun" /etc/*-release; then
OSNAME='Aliyun'
elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then
OSNAME='Fedora'
elif grep -Eqi "Amazon Linux AMI" /etc/issue || grep -Eq "Amazon Linux AMI" /etc/*-release; then
OSNAME='Amazon'
elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then
OSNAME='Debian'
elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then
OSNAME='Ubuntu'
elif grep -Eqi "Raspbian" /etc/issue || grep -Eq "Raspbian" /etc/*-release; then
OSNAME='Raspbian'
elif grep -Eqi "Deepin" /etc/issue || grep -Eq "Deepin" /etc/*-release; then
OSNAME='Deepin'
else
OSNAME='unknow'
fi
echo "$OSNAME" > /www/server/panel/data/osname.pl

Binary file not shown.

Binary file not shown.

943
script/auto_apply_ip_ssl.py Normal file
View File

@@ -0,0 +1,943 @@
import base64
import binascii
import hashlib
import json
import os
import fcntl
import re
import shutil
import socket
import subprocess
import sys
import time
import datetime
from pathlib import Path
import requests
APACHE_CONF_DIRS = [
"/www/server/panel/vhost/apache"
]
def is_ipv4(ip):
'''
@name 是否是IPV4地址
@author hwliang
@param ip<string> IP地址
@return True/False
'''
# 验证基本格式
if not re.match(r"^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$", ip):
return False
# 验证每个段是否在合理范围
try:
socket.inet_pton(socket.AF_INET, ip)
except AttributeError:
try:
socket.inet_aton(ip)
except socket.error:
return False
except socket.error:
return False
return True
def is_ipv6(ip):
'''
@name 是否为IPv6地址
@author hwliang
@param ip<string> 地址
@return True/False
'''
# 验证基本格式
if not re.match(r"^[\w:]+$", ip):
return False
# 验证IPv6地址
try:
socket.inet_pton(socket.AF_INET6, ip)
except socket.error:
return False
return True
def check_ip(ip):
return is_ipv4(ip) or is_ipv6(ip)
def find_apache_conf_files(keyword):
"""查找 Apache 主配置和 vhost 文件"""
files = set()
for base in APACHE_CONF_DIRS:
base_path = Path(base)
if not base_path.exists():
continue
for f in base_path.rglob("*.conf"):
with open(f, "r") as file:
content = file.read()
# 检查是否包含 ServerName 或 ServerAlias 指令
if keyword in content:
files.add(str(f))
return list(files)
def insert_location_into_vhost(file_path, keyword, verify_file):
LOCATION_BLOCK = [
" <Location /.well-known/acme-challenge/{}>\n".format(verify_file),
" Require all granted\n",
" Header set Content-Type \"text/plain\"\n",
" </Location>\n",
" Alias /.well-known/acme-challenge/{} /tmp/{}\n".format(verify_file, verify_file),
]
path = Path(file_path)
backup = path.with_suffix(path.suffix + ".bak")
shutil.copy(path, backup)
with open(path, "r") as f:
lines = f.readlines()
new_lines = []
in_vhost = False
hit_vhost = False
location_exists = False
for line in lines:
stripped = line.strip()
if stripped.lower().startswith("<virtualhost"):
in_vhost = True
hit_vhost = False
location_exists = False
if in_vhost:
low = stripped.lower()
if (low.startswith("servername") or low.startswith("serveralias")) and keyword in stripped:
hit_vhost = True
if "<location /.well-known/acme-challenge/>" in low:
location_exists = True
if stripped.lower() == "</virtualhost>":
if hit_vhost and not location_exists:
new_lines.extend(LOCATION_BLOCK)
in_vhost = False
new_lines.append(line)
with open(path, "w") as f:
f.writelines(new_lines)
return True
def find_nginx_files_by_servername(keyword):
"""通过 nginx -T 找到包含 server_name 的配置文件"""
result = subprocess.run(
["nginx", "-T"],
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
text=True,
check=True
)
files = set()
current_file = None
for line in result.stdout.splitlines():
if line.startswith("# configuration file"):
current_file = line.split()[-1].rstrip(":")
if "server_name" in line and keyword in line:
if current_file:
files.add(current_file)
return list(files)
def insert_location_into_server(file_path, keyword, verify_file, verify_content):
path = Path(file_path)
backup = path.with_suffix(path.suffix + ".bak")
shutil.copy(path, backup)
with open(path, "r") as f:
lines = f.readlines()
new_lines = []
brace_level = 0
in_server = False
hit_server = False
location_exists = False
LOCATION_BLOCK = [
" location = /.well-known/acme-challenge/{} {{\n".format(verify_file),
" default_type text/plain;\n",
" return 200 \"{}\";\n".format(verify_content),
" }\n"
]
for i, line in enumerate(lines):
stripped = line.strip()
# server 开始
if stripped.startswith("server"):
in_server = True
hit_server = False
location_exists = False
if in_server:
brace_level += line.count("{")
brace_level -= line.count("}")
if "server_name" in line and keyword in line:
hit_server = True
if "location /.well-known/acme-challenge/" in line:
location_exists = True
# server 结束
if brace_level == 0:
if hit_server and not location_exists:
new_lines.extend(LOCATION_BLOCK)
in_server = False
new_lines.append(line)
with open(path, "w") as f:
f.writelines(new_lines)
return True
class AutoApplyIPSSL:
# 请求到ACME接口
def __init__(self):
self._wait_time = 5
self._max_check_num = 15
self._url = 'https://acme-v02.api.letsencrypt.org/directory'
self._bits = 2048
self._conf_file_v2 = '/www/server/panel/config/letsencrypt_v2.json'
self._apis = None
self._replay_nonce = None
self._config = self.read_config()
# 取接口目录
def get_apis(self):
if not self._apis:
# 尝试从配置文件中获取
api_index = "Production"
if not 'apis' in self._config:
self._config['apis'] = {}
if api_index in self._config['apis']:
if 'expires' in self._config['apis'][api_index] and 'directory' in self._config['apis'][api_index]:
if time.time() < self._config['apis'][api_index]['expires']:
self._apis = self._config['apis'][api_index]['directory']
return self._apis
# 尝试从云端获取
res = requests.get(self._url)
if not res.status_code in [200, 201]:
result = res.json()
if "type" in result:
if result['type'] == 'urn:acme:error:serverInternal':
raise Exception('Service is closed for maintenance or internal error occurred, check <a href="https://letsencrypt.status.io/" target="_blank" class="btlink">https://letsencrypt.status.io/</a> .')
raise Exception(res.content)
s_body = res.json()
self._apis = {}
self._apis['newAccount'] = s_body['newAccount']
self._apis['newNonce'] = s_body['newNonce']
self._apis['newOrder'] = s_body['newOrder']
self._apis['revokeCert'] = s_body['revokeCert']
self._apis['keyChange'] = s_body['keyChange']
# 保存到配置文件
self._config['apis'][api_index] = {}
self._config['apis'][api_index]['directory'] = self._apis
self._config['apis'][api_index]['expires'] = time.time() + \
86400 # 24小时后过期
self.save_config()
return self._apis
def acme_request(self, url, payload):
headers = {}
payload = self.stringfy_items(payload)
if payload == "":
payload64 = payload
else:
payload64 = self.calculate_safe_base64(json.dumps(payload))
protected = self.get_acme_header(url)
protected64 = self.calculate_safe_base64(json.dumps(protected))
signature = self.sign_message(
message="{0}.{1}".format(protected64, payload64)) # bytes
signature64 = self.calculate_safe_base64(signature) # str
data = json.dumps(
{"protected": protected64, "payload": payload64,
"signature": signature64}
)
headers.update({"Content-Type": "application/jose+json"})
response = requests.post(url, data=data.encode("utf8"), headers=headers)
# 更新随机数
self.update_replay_nonce(response)
return response
# 更新随机数
def update_replay_nonce(self, res):
replay_nonce = res.headers.get('Replay-Nonce')
if replay_nonce:
self._replay_nonce = replay_nonce
def stringfy_items(self, payload):
if isinstance(payload, str):
return payload
for k, v in payload.items():
if isinstance(k, bytes):
k = k.decode("utf-8")
if isinstance(v, bytes):
v = v.decode("utf-8")
payload[k] = v
return payload
# 转为无填充的Base64
def calculate_safe_base64(self, un_encoded_data):
if sys.version_info[0] == 3:
if isinstance(un_encoded_data, str):
un_encoded_data = un_encoded_data.encode("utf8")
r = base64.urlsafe_b64encode(un_encoded_data).rstrip(b"=")
return r.decode("utf8")
# 获请ACME请求头
def get_acme_header(self, url):
header = {"alg": "RS256", "nonce": self.get_nonce(), "url": url}
if url in [self._apis['newAccount'], 'GET_THUMBPRINT']:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
private_key = serialization.load_pem_private_key(
self.get_account_key().encode(),
password=None,
backend=default_backend(),
)
public_key_public_numbers = private_key.public_key().public_numbers()
exponent = "{0:x}".format(public_key_public_numbers.e)
exponent = "0{0}".format(exponent) if len(
exponent) % 2 else exponent
modulus = "{0:x}".format(public_key_public_numbers.n)
jwk = {
"kty": "RSA",
"e": self.calculate_safe_base64(binascii.unhexlify(exponent)),
"n": self.calculate_safe_base64(binascii.unhexlify(modulus)),
}
header["jwk"] = jwk
else:
header["kid"] = self.get_kid()
return header
def get_nonce(self, force=False):
# 如果没有保存上一次的随机数或force=True时则重新获取新的随机数
if not self._replay_nonce or force:
response = requests.get(
self._apis['newNonce'],
)
self._replay_nonce = response.headers["Replay-Nonce"]
return self._replay_nonce
def analysis_private_key(self, key_pem, password=None):
"""
解析私钥
:param key_pem: 私钥内容
:param password: 私钥密码
:return: 私钥对象
"""
try:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
private_key = serialization.load_pem_private_key(
key_pem.encode(),
password=password,
backend=default_backend()
)
return private_key
except:
return None
def sign_message(self, message):
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
pk = self.analysis_private_key(self.get_account_key())
return pk.sign(message.encode("utf8"), padding.PKCS1v15(), hashes.SHA256())
# 获用户取密钥对
def get_account_key(self):
if not 'account' in self._config:
self._config['account'] = {}
k = "Production"
if not k in self._config['account']:
self._config['account'][k] = {}
if not 'key' in self._config['account'][k]:
self._config['account'][k]['key'] = self.create_key()
if type(self._config['account'][k]['key']) == bytes:
self._config['account'][k]['key'] = self._config['account'][k]['key'].decode()
self.save_config()
return self._config['account'][k]['key']
def create_key(self, key_type='RSA'):
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, ec, ed25519
if key_type == 'RSA':
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=self._bits
)
elif key_type == 'EC':
private_key = ec.generate_private_key(ec.SECP256R1())
elif key_type == 'ED25519':
private_key = ed25519.Ed25519PrivateKey.generate()
else:
raise ValueError(f"Unsupported key type: {key_type}")
private_key_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
return private_key_pem
def get_kid(self, force=False):
#如果配置文件中不存在kid或force = True时则重新注册新的acme帐户
if not 'account' in self._config:
self._config['account'] = {}
k = "Production"
if not k in self._config['account']:
self._config['account'][k] = {}
if not 'kid' in self._config['account'][k]:
self._config['account'][k]['kid'] = self.register()
self.save_config()
time.sleep(3)
self._config = self.read_config()
return self._config['account'][k]['kid']
# 读配置文件
def read_config(self):
if not os.path.exists(self._conf_file_v2):
self._config = {'orders': {}, 'account': {}, 'apis': {}, 'email': None}
self.save_config()
return self._config
with open(self._conf_file_v2, 'r') as f:
fcntl.flock(f, fcntl.LOCK_SH) # 加锁
tmp_config = f.read()
fcntl.flock(f, fcntl.LOCK_UN) # 解锁
f.close()
if not tmp_config:
return self._config
try:
self._config = json.loads(tmp_config)
except:
self.save_config()
return self._config
return self._config
# 写配置文件
def save_config(self):
fp = open(self._conf_file_v2, 'w+')
fcntl.flock(fp, fcntl.LOCK_EX) # 加锁
fp.write(json.dumps(self._config))
fcntl.flock(fp, fcntl.LOCK_UN) # 解锁
fp.close()
return True
# 注册acme帐户
def register(self, existing=False):
if not 'email' in self._config:
self._config['email'] = 'demo@yakpanel.com'
if existing:
payload = {"onlyReturnExisting": True}
elif self._config['email']:
payload = {
"termsOfServiceAgreed": True,
"contact": ["mailto:{0}".format(self._config['email'])],
}
else:
payload = {"termsOfServiceAgreed": True}
res = self.acme_request(url=self._apis['newAccount'], payload=payload)
if res.status_code not in [201, 200, 409]:
raise Exception("Failed to register ACME account: {}".format(res.json()))
kid = res.headers["Location"]
return kid
def create_csr(self, ips):
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
import ipaddress
# 生成私钥
pk = self.create_key()
private_key = serialization.load_pem_private_key(pk, password=None)
# IP证书不需要CN
csr_builder = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([]))
# 添加 subjectAltName 扩展
alt_names = [x509.IPAddress(ipaddress.ip_address(ip)) for ip in ips]
csr_builder = csr_builder.add_extension(
x509.SubjectAlternativeName(alt_names),
critical=False
)
# 签署 CSR
csr = csr_builder.sign(private_key, hashes.SHA256())
# 返回 CSR (ASN1 格式)
return csr.public_bytes(serialization.Encoding.DER), pk
def apply_ip_ssl(self, ips, email, webroot=None, mode=None, path=None):
print("Starting to apply for Let's Encrypt IP SSL certificate...")
print("Retrieving ACME API directory...")
self.get_apis()
self._config['email'] = email
print("Creating order...")
order_data = self.create_order(ips)
if not order_data:
raise Exception("Failed to create order!")
print("Order created successfully")
print("Performing domain verification...")
try:
self.get_and_set_authorizations(order_data, webroot, mode, ips)
except Exception as e:
raise Exception("Domain verification failed! {}".format(e))
# 完成订单
print("Creating CSR...")
csr, private_key = self.create_csr(ips)
print("Sending CSR and completing order...")
res = self.acme_request(order_data['finalize'], payload={
"csr": self.calculate_safe_base64(csr)
})
if res.status_code not in [200, 201]:
raise Exception("Failed to complete order! {}".format(res.json()))
# 获取证书
print("Retrieving certificate...")
cert_url = res.json().get('certificate')
if not cert_url:
raise Exception("Failed to retrieve certificate URL!")
cert_res = self.acme_request(cert_url, payload="")
if cert_res.status_code not in [200, 201]:
raise Exception("Failed to retrieve certificate! {}".format(cert_res.json()))
print("Certificate retrieved successfully!")
cert_pem = cert_res.content.decode()
# 保存证书和私钥
if not path:
path = "/www/server/panel/ssl"
if not os.path.exists(path):
os.makedirs(path)
cert_path = os.path.join(path, 'certificate.pem')
key_path = os.path.join(path, 'privateKey.pem')
with open(cert_path, 'w') as f:
f.write(cert_pem)
with open(key_path, 'w') as f:
f.write(private_key.decode())
return cert_path, key_path
def create_order(self, ips):
identifiers = []
for ip in ips:
identifiers.append({"type": "ip", "value": ip})
payload = {"identifiers": identifiers, "profile": "shortlived"}
print("Create order, domain name list:{}".format(','.join(ips)))
res = self.acme_request(self._apis['newOrder'], payload)
if not res.status_code in [201,200]: # 如果创建失败
print("Failed to create order, attempting to fix error...")
e_body = res.json()
if 'type' in e_body:
# 如果随机数失效
if e_body['type'].find('error:badNonce') != -1:
print("Nonce invalid, retrieving new nonce and retrying...")
self.get_nonce(force=True)
res = self.acme_request(self._apis['newOrder'], payload)
# 如果帐户失效
if e_body['detail'].find('KeyID header contained an invalid account URL') != -1:
print("Account invalid, re-registering account and retrying...")
k = "Production"
del(self._config['account'][k])
self.get_kid()
self.get_nonce(force=True)
res = self.acme_request(self._apis['newOrder'], payload)
if not res.status_code in [201,200]:
print(res.json())
# 2025/12/25 yakpanel
raise Exception(str(res.json()))
# return {}
return res.json()
# UTC时间转时间戳
def utc_to_time(self, utc_string):
try:
utc_string = utc_string.split('.')[0]
utc_date = datetime.datetime.strptime(
utc_string, "%Y-%m-%dT%H:%M:%S")
# 按北京时间返回
return int(time.mktime(utc_date.timetuple())) + (3600 * 8)
except:
return int(time.time() + 86400 * 7)
def get_keyauthorization(self, token):
acme_header_jwk_json = json.dumps(
self.get_acme_header("GET_THUMBPRINT")["jwk"], sort_keys=True, separators=(",", ":")
)
acme_thumbprint = self.calculate_safe_base64(
hashlib.sha256(acme_header_jwk_json.encode("utf8")).digest()
)
acme_keyauthorization = "{0}.{1}".format(token, acme_thumbprint)
base64_of_acme_keyauthorization = self.calculate_safe_base64(
hashlib.sha256(acme_keyauthorization.encode("utf8")).digest()
)
return acme_keyauthorization, base64_of_acme_keyauthorization
# 获取并设置验证信息
def get_and_set_authorizations(self, order_data, webroot=None, mode=None, ips=None):
import os
if 'authorizations' not in order_data:
raise Exception("Abnormal order data, missing authorization information!")
for auth_url in order_data['authorizations']:
res = self.acme_request(auth_url, payload="")
if not res.status_code in [200, 201]:
raise Exception("Failed to get authorization information! {}".format(res.json()))
s_body = res.json()
if 'status' in s_body:
if s_body['status'] in ['invalid']:
raise Exception("Invalid order, current order status is verification failed!")
if s_body['status'] in ['valid']: # 跳过无需验证的域名
continue
for challenge in s_body['challenges']:
if challenge['type'] == "http-01":
break
if challenge['type'] != "http-01":
raise Exception("http-01 verification method not found, cannot continue applying for certificate!")
# 检查是否需要验证
check_auth_data = self.check_auth_status(challenge['url'])
if check_auth_data.json()['status'] == 'invalid':
raise Exception('Domain verification failed, please try applying again!')
if check_auth_data.json()['status'] == 'valid':
continue
acme_keyauthorization, auth_value = self.get_keyauthorization(
challenge['token'])
print(challenge)
if mode:
if mode == 'standalone':
from http.server import HTTPServer, SimpleHTTPRequestHandler
import threading
import os
class ACMERequestHandler(SimpleHTTPRequestHandler):
def log_message(self, format, *args):
# 屏蔽默认的请求日志输出
return
def do_GET(self):
if self.path == '/.well-known/acme-challenge/{}'.format(challenge['token']):
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(acme_keyauthorization.encode())
else:
self.send_response(404)
self.end_headers()
server_address = ('', 80)
httpd = HTTPServer(server_address, ACMERequestHandler)
def start_server():
httpd.serve_forever()
server_thread = threading.Thread(target=start_server)
server_thread.daemon = True
server_thread.start()
# 2025/12/26 yakpanel
time.sleep(2) # 等待服务器启动
try:
# ========================= 校验服务 ==================================
server_started = False
challenge_local = f"http://127.0.0.1/.well-known/acme-challenge/{challenge['token']}"
for i in range(10): # 最大尝试时间5秒
try:
# 临时检查80 打印
with socket.create_connection(("127.0.0.1", 80), timeout=0.2):
print("Temporary server started on port 80.")
response = requests.get(challenge_local, timeout=0.5)
if response.status_code == 200 and response.text == acme_keyauthorization:
print("Temporary server started and responding correctly challenge token on port 80.")
server_started = True
break
else:
print("Temporary server response incorrect, retrying...")
time.sleep(0.5)
except Exception:
time.sleep(0.5)
if not server_started:
# raise Exception("Failed to start temporary HTTP server on port 80 in 5 seconds.")
print("Failed to start temporary HTTP server on port 80 in 5 seconds.")
# ========================= 通知ACME服务器进行验证 ===============================
self.acme_request(challenge['url'], payload={"keyAuthorization": "{0}".format(acme_keyauthorization)})
self.check_auth_status(challenge['url'], [
'valid', 'invalid'])
finally:
httpd.shutdown()
server_thread.join()
elif mode == 'nginx':
tmp_path = '/www/server/panel/vhost/nginx/tmp_apply_ip_ssl.conf'
files = find_nginx_files_by_servername(ips[0])
if not files:
print("No related Nginx configuration files found, attempting to create temporary configuration file...")
if not os.path.exists('/www/server/panel/vhost/nginx'):
raise Exception("No Nginx configuration files found, and Nginx configuration directory does not exist!")
# 如果没有找到相关配置文件,则创建一个临时配置文件
with open(tmp_path, 'w') as f:
f.write("""server
{{
listen 80;
server_name {0};
location /.well-known/acme-challenge/{1} {{
default_type text/plain;
return 200 "{2}";
}}
}}
""".format(ips[0], challenge['token'], acme_keyauthorization))
try:
for file in files:
print("Modifying Nginx configuration file: {}".format(file))
insert_location_into_server(file, ips[0], verify_file=challenge['token'], verify_content=acme_keyauthorization)
# 重新加载Nginx配置
subprocess.run(["nginx", "-t"], check=True)
subprocess.run(["nginx", "-s", "reload"], check=True)
# 通知ACME服务器进行验证
self.acme_request(challenge['url'],
payload={"keyAuthorization": "{0}".format(acme_keyauthorization)})
self.check_auth_status(challenge['url'], [
'valid', 'invalid'])
finally:
for file in files:
print("Restoring Nginx configuration file: {}".format(file))
# 恢复备份文件
backup_file = file + ".bak"
if os.path.exists(backup_file):
shutil.move(backup_file, file)
if not files:
print("Deleting temporary Nginx configuration file...")
# 删除临时配置文件
os.remove(tmp_path)
# 重新加载Nginx配置
subprocess.run(["nginx", "-t"], check=True)
subprocess.run(["nginx", "-s", "reload"], check=True)
elif mode == 'apache':
tmp_path = '/www/server/panel/vhost/apache/tmp_apply_ip_ssl.conf'
files = find_apache_conf_files(ips[0])
if not files:
print("No related Apache configuration files found, attempting to create temporary configuration file...")
if not os.path.exists('/www/server/panel/vhost/apache'):
raise Exception("No Apache configuration files found, and Apache configuration directory does not exist!")
# 如果没有找到相关配置文件,则创建一个临时配置文件
with open(tmp_path, 'w') as f:
f.write("""<VirtualHost *:80>
ServerName {0}
<Location /.well-known/acme-challenge/{1}>
Require all granted
Header set Content-Type "text/plain"
</Location>
Alias /.well-known/acme-challenge/{1} /tmp/{1}
</VirtualHost>
""".format(ips[0], challenge['token']))
try:
for file in files:
print("Modifying Apache configuration file: {}".format(file))
insert_location_into_vhost(file, ips[0], verify_file=challenge['token'])
# 写入验证文件
with open('/tmp/{}'.format(challenge['token']), 'w') as f:
f.write(acme_keyauthorization)
# 重新加载Apache配置
subprocess.run(["/etc/init.d/httpd", "reload"], check=True)
# 通知ACME服务器进行验证
self.acme_request(challenge['url'],
payload={"keyAuthorization": "{0}".format(acme_keyauthorization)})
self.check_auth_status(challenge['url'], [
'valid', 'invalid'])
finally:
for file in files:
print("Restoring Apache configuration file: {}".format(file))
# 恢复备份文件
backup_file = file + ".bak"
if os.path.exists(backup_file):
shutil.move(backup_file, file)
if not files:
print("Deleting temporary Apache configuration file...")
# 删除临时配置文件
os.remove(tmp_path)
# 重新加载Apache配置
subprocess.run(["systemctl", "restart", "httpd"], check=True)
else:
# 使用webroot方式验证
challenge_path = os.path.join(
webroot, '.well-known', 'acme-challenge')
if not os.path.exists(challenge_path):
os.makedirs(challenge_path)
file_path = os.path.join(challenge_path, challenge['token'])
with open(file_path, 'w') as f:
f.write(acme_keyauthorization)
try:
# 通知ACME服务器进行验证
self.acme_request(challenge['url'], payload={"keyAuthorization": "{0}".format(acme_keyauthorization)})
self.check_auth_status(challenge['url'], [
'valid', 'invalid'])
finally:
os.remove(file_path)
# 检查验证状态
def check_auth_status(self, url, desired_status=None):
desired_status = desired_status or ["pending", "valid", "invalid"]
number_of_checks = 0
authorization_status = "pending"
while True:
print("|- {} checking verification result...".format(number_of_checks + 1))
if desired_status == ['valid', 'invalid']:
time.sleep(self._wait_time)
check_authorization_status_response = self.acme_request(url, "")
a_auth = check_authorization_status_response.json()
if not isinstance(a_auth, dict):
continue
authorization_status = a_auth["status"]
number_of_checks += 1
if authorization_status in desired_status:
if authorization_status == "invalid":
try:
if 'error' in a_auth['challenges'][0]:
ret_title = a_auth['challenges'][0]['error']['detail']
elif 'error' in a_auth['challenges'][1]:
ret_title = a_auth['challenges'][1]['error']['detail']
elif 'error' in a_auth['challenges'][2]:
ret_title = a_auth['challenges'][2]['error']['detail']
else:
ret_title = str(a_auth)
except:
ret_title = str(a_auth)
raise StopIteration(
"{0} >>>> {1}".format(
ret_title,
json.dumps(a_auth)
)
)
break
if number_of_checks == self._max_check_num:
raise StopIteration(
"Error: Verification attempted {0} times. Maximum verification attempts: {1}. Verification interval: {2} seconds.".format(
number_of_checks,
self._max_check_num,
self._wait_time
)
)
print("|-Verification result: {}".format(authorization_status))
return check_authorization_status_response
if __name__ == '__main__':
import argparse
# 解析命令行参数
parser = argparse.ArgumentParser(description='Auto-apply IP SSL certificate script')
parser.add_argument('-ips', type=str, required=True, help='IP addresses to apply SSL certificate for', dest='ips')
parser.add_argument('-email', type=str, required=False, help='Email for SSL certificate application', dest='email')
parser.add_argument('-w', type=str, help='Website root directory', dest='webroot')
parser.add_argument('--standalone', help='Apply certificate using standalone mode', dest='standalone', action='store_true')
parser.add_argument('--nginx', help='Apply certificate using nginx mode', dest='nginx', action='store_true')
parser.add_argument('--apache', help='Apply certificate using apache mode', dest='apache', action='store_true')
parser.add_argument('-path', type=str, help='Certificate save path', dest='path')
args = parser.parse_args()
if not args.standalone and not args.webroot and not args.nginx and not args.apache:
print("No verification mode detected, will attempt to auto-select verification mode!")
# 自动选择验证模式
# 判断80端口是否被占用
use_80 = False
result = subprocess.run(
["lsof", "-i:80"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
if result.stdout:
result = subprocess.run(
["netstat", "-lntup", "|", "grep", "80"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
if result.stdout:
use_80 = True
if use_80:
print("It is detected that port 80 is occupied, try to use Nginx or Apache mode to verify...")
# 检查是否安装Nginx
if os.path.exists('/www/server/nginx/sbin/nginx'):
args.nginx = True
print("Selected Nginx mode for verification...")
elif os.path.exists('/www/server/apache/bin/httpd'):
args.apache = True
print("Selected Apache mode for verification...")
else:
print("[ERROR] Nginx or Apache installation not detected, cannot use Nginx or Apache mode for verification! Please release port 80 and try again.")
exit(1)
else:
args.standalone = True
print("Port 80 not occupied, selected standalone mode for verification...")
if not args.email:
# 使用默认邮箱
email = "demo@yakpanel.com"
else:
email = args.email
ips = args.ips.split(',')
# 先只支持单个IP申请
if len(ips) > 1 and not args.standalone:
print("[ERROR] Multiple IP SSL certificate application not supported in non-standalone mode!")
exit(1)
# 先只支持IPv4
if not is_ipv4(ips[0]):
print("[ERROR] Only IPv4 addresses are supported for SSL certificate application at this time!")
exit(1)
auto_ssl = AutoApplyIPSSL()
mode = None
if args.standalone:
mode = 'standalone'
elif args.nginx:
mode = 'nginx'
elif args.apache:
mode = 'apache'
try:
cert_path, key_path = auto_ssl.apply_ip_ssl(ips, email, webroot=args.webroot, mode=mode, path=args.path)
except Exception as e:
# 2025/12/25 yakpanel
print(f"[ERROR] Certificate application failed! Error details: {e}", file=sys.stderr)
exit(1)
exit(0)

195
script/backup Normal file
View File

@@ -0,0 +1,195 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#备份网站
Backup_Site()
{
#准备必要信息
startTime=`date +%s`
myDate=`date +"%Y%m%d_%H%M%S"`
zipName=${1}_${myDate}.zip
backupDir=${backup_path}/site
fileName=$backupDir/$zipName
tmp=`$Sql_Exec -e "SELECT id,path FROM bt_sites WHERE name='$1'"`;
pid=`echo $tmp|awk '{print $3}'`
sitePath=`echo $tmp|awk '{print $4}'`
if [ "$pid" == '' ];then
endDate=`date +"%Y-%m-%d %H:%M:%S"`
log="网站[$1]不存在!"
echo "★[$endDate] $log"
echo '----------------------------------------------------------------------------'
exit
fi
if [ ! -d $backupDir ];then
mkdir -p $backupDir
fi
#取目录名称
oldIFS=$IFS
IFS=/
nameArr=($sitePath)
pathName=${nameArr[@]: -1};
IFS=$oldIFS
#压缩
cd $sitePath
cd ..
zip -r $fileName $pathName > /dev/null
if [ ! -f $fileName ];then
endDate=`date +"%Y-%m-%d %H:%M:%S"`
log="网站[$1]备份失败!"
echo "★[$endDate] $log"
echo '----------------------------------------------------------------------------'
exit
fi
#记录日志
endDate=`date +"%Y-%m-%d %H:%M:%S"`
endTime=`date +%s`
((outTime=($endTime-$startTime)))
log="网站[$1]备份成功,用时[$outTime]秒"
$Sql_Exec -e "INSERT INTO bt_backup(type,name,pid,filename,addtime) VALUES(0,'$zipName',$pid,'$fileName','$endDate')"
$Sql_Exec -e "INSERT INTO bt_logs(type,log,addtime) VALUES('Cron','$log','$endDate')"
echo "★[$endDate] $log"
echo "|---保留最新的[$2]份备份"
echo "|---文件名:$fileName"
#清理多余备份
tmp=`$Sql_Exec -e "SELECT COUNT(*) FROM bt_backup WHERE type=0 AND pid=$pid"`;
count=`echo $tmp|awk '{print $2}'`
((sum=($count-$2)))
if [ $sum -gt 0 ];then
str=`$Sql_Exec -e "SELECT filename FROM bt_backup WHERE type=0 AND pid=$pid ORDER BY id ASC LIMIT $sum"`
str=`echo $str|sed "s#filename##"|sed "s# ##"`
oldIFS=$IFS
IFS=' '
arr=($str)
for s in ${arr[@]}
do
if [ -f $s ];then
rm -f $s
fi
echo "|---已清理过期备份文件:$s"
$Sql_Exec -e "DELETE FROM bt_backup WHERE filename='$s'" > /dev/null
done
IFS=$oldIFS
fi
echo '----------------------------------------------------------------------------'
}
#备份数据库
Backup_Database()
{
#准备必要信息
startTime=`date +%s`
mysqlRoot=`echo $select |grep mysql_root|awk '{print $3}'`
myDate=`date +"%Y%m%d_%H%M%S"`
sqlName=${1}_${myDate}.sql
zipName=${1}_${myDate}.zip
backupDir=${backup_path}/database
tmp=`$Sql_Exec -e "SELECT id FROM bt_databases WHERE name='$1'"`;
pid=`echo $tmp|awk '{print $2}'`
if [ "$pid" == '' ];then
endDate=`date +"%Y-%m-%d %H:%M:%S"`
log="数据库[$1]不存在!"
echo "★[$endDate] $log"
echo '----------------------------------------------------------------------------'
exit
fi
if [ ! -d $backupDir ];then
mkdir -p $backupDir
fi
#sed -i "s###"
isWrite=`cat /etc/my.cnf|grep 'user=root'`
if [ "${isWrite}" == '' ];then
echo -e "\n[mysqldump]\nuser=root\npassword=$mysqlRoot" >> /etc/my.cnf
else
sed -i "s#password=.*#password=$mysqlRoot#" /etc/my.cnf
fi
#导出数据库
cd $backupDir
/www/server/mysql/bin/mysqldump -uroot -p$mysqlRoot -R $1 > $sqlName
sed -i "/\[mysqldump\]/d" /etc/my.cnf
sed -i "/user=root/d" /etc/my.cnf
sed -i "/password=.*/d" /etc/my.cnf
sed -i "s/quick/\[mysqldump\]\nquick/" /etc/my.cnf
if [ ! -f $sqlName ];then
endDate=`date +"%Y-%m-%d %H:%M:%S"`
log="数据库[$1]备份失败!"
echo "★[$endDate] $log"
echo '----------------------------------------------------------------------------'
exit
fi
#压缩
zip -r $zipName $sqlName > /dev/null
rm -f $sqlName
#记录日志
endDate=`date +"%Y-%m-%d %H:%M:%S"`
endTime=`date +%s`
((outTime=($endTime-$startTime)))
fileName=$backupDir/$zipName
log="数据库[$1]备份成功,用时[$outTime]秒"
$Sql_Exec -e "INSERT INTO bt_backup(type,name,pid,filename,addtime) VALUES(1,'$zipName',$pid,'$fileName','$endDate')" > /dev/null
$Sql_Exec -e "INSERT INTO bt_logs(type,log,addtime) VALUES('Cron','$log','$endDate')" > /dev/null
echo "★[$endDate] $log"
echo "|---保留最新的[$2]份备份"
echo "|---文件名:$fileName"
#清理多余备份
tmp=`$Sql_Exec -e "SELECT COUNT(*) FROM bt_backup WHERE type=1 AND pid=$pid"`;
count=`echo $tmp|awk '{print $2}'`
((sum=($count-$2)))
if [ $sum -gt 0 ];then
str=`$Sql_Exec -e "SELECT filename FROM bt_backup WHERE type=1 AND pid=$pid ORDER BY id ASC LIMIT $sum"`
str=`echo $str|sed "s#filename##"|sed "s# ##"`
oldIFS=$IFS
IFS=' '
arr=($str)
for s in ${arr[@]}
do
if [ -f $s ];then
rm -f $s
fi
echo "|---已清理过期备份文件:$s"
$Sql_Exec -e "DELETE FROM bt_backup WHERE filename='$s'" > /dev/null
done
IFS=$oldIFS
fi
echo '----------------------------------------------------------------------------'
}
#准备基础信息
action=$1
name=$2
num=$3
dbPwd=`cat /www/wwwroot/default/conf/sql.config.php |grep 'DB_PWD'|awk '{print $3}'|sed "s#'##g"|sed "s#,##g"`
dbName='bt_default'
Sql_Exec="/www/server/mysql/bin/mysql -u$dbName -p$dbPwd --default-character-set=utf8 -D $dbName"
select=`$Sql_Exec -e "SELECT mysql_root,backup_path FROM bt_config WHERE id=1"`
backup_path=`echo $select |grep backup_path|awk '{print $4}'`
case "${action}" in
'site')
Backup_Site $name $num
;;
'database')
Backup_Database $name $num
;;
esac

66
script/backup.py Normal file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/python
#coding: utf-8
#-----------------------------
# YakPanel
# 网站备份工具
#-----------------------------
import sys,os
os.chdir('/www/server/panel')
if "./" not in sys.path: sys.path.insert(0, "./")
if "class/" not in sys.path: sys.path.insert(0, "class/")
if "class_v2/" not in sys.path: sys.path.insert(0, "class_v2/")
if sys.version_info[0] == 2:
reload(sys) # noqa
sys.setdefaultencoding('utf-8') # noqa
import public,db,time
from public.hook_import import hook_import
hook_import()
import class_v2.panel_backup_v2 as panelBackup
# import panelBackup as panelBackup
class backupTools(panelBackup.backup):
def backupSite(self, name, count, echo_id=None):
self.backup_site(name, save=count, echo_id=echo_id)
def backupDatabase(self, name, count, echo_id=None):
self.backup_database(name, save=count, echo_id=echo_id)
#备份指定目录
def backupPath(self, path, count, echo_id=None):
self.backup_path(path, save=count, echo_id=echo_id)
def backupSiteAll(self, save, echo_id=None):
self.backup_site_all(save, echo_id=echo_id)
def backupDatabaseAll(self, save, echo_id=None):
self.backup_database_all(save, echo_id=echo_id)
if __name__ == "__main__":
backup = backupTools()
type = sys.argv[1]
echo_id = None
if len(sys.argv) > 4: echo_id = sys.argv[4]
if type == 'site':
if sys.argv[2] == 'ALL':
backup.backupSiteAll(sys.argv[3], echo_id)
elif ',' in sys.argv[2]:
for i in sys.argv[2].split(','):
backup.backupSite(i, sys.argv[3], echo_id)
else:
backup = backupTools(cron_info = {'echo':sys.argv[4]})
backup.backupSite(sys.argv[2], sys.argv[3], echo_id)
elif type == 'path':
backup = backupTools(cron_info = {'echo':sys.argv[4]})
backup.backupPath(sys.argv[2], sys.argv[3], echo_id)
elif type == 'database':
if sys.argv[2] == 'ALL':
backup.backupDatabaseAll(sys.argv[3], echo_id)
elif ',' in sys.argv[2]:
for i in sys.argv[2].split(','):
backup.backupDatabase(i, sys.argv[3], echo_id)
else:
backup = backupTools(cron_info = {'echo':sys.argv[4]})
backup.backupDatabase(sys.argv[2], sys.argv[3], echo_id)

View File

@@ -0,0 +1,39 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2017 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: hezhihong <hezhihong@yakpanel.com>
# -------------------------------------------------------------------
# ------------------------------
# 防爆破检测脚本
#------------------------------
import os,sys
panel_path = '/www/server/panel'
if not os.name in ['nt']:
os.chdir(panel_path)
if not 'class/' in sys.path:
sys.path.insert(0, 'class/')
if not 'class_v2/' in sys.path:
sys.path.insert(0, 'class_v2/')
sys.path.insert(0, '.')
import public
# import breaking_through
# breakingObject = breaking_through.main()
# 调用处理方法
# result = run_object(pdata)
import public.PluginLoader as plugin_loader
mod_file = '{}/class_v2/breaking_through.py'.format(public.get_panel_path())
plugin_class = plugin_loader.get_module(mod_file)
class_string='main'
plugin_object = getattr(plugin_class,class_string)()
def_name='cron_method'
getattr(plugin_object,def_name)()
# public.print_log(result)

BIN
script/bt-ipfilter Normal file

Binary file not shown.

102
script/btpyprojectenv.sh Normal file
View File

@@ -0,0 +1,102 @@
_BT_PYTHON_NAME_MAP_FILE="/www/server/panel/data/python_project_name2env.txt"
ACTIVATE_NAME="${1}"
if [ -z "${ACTIVATE_NAME}" ]; then
echo "Usage: \" source py-project-env <project_name> \" to activate virtual environment"
echo "Usage: \" bt_env_deactivate \" command to exit virtual environment and return to previous environment"
echo "(Only supported on Linux servers)"
return 1
fi
_bt_map_safe_get() {
awk -F: -v key="$1" '$1 == key {print $2; exit}' "${_BT_PYTHON_NAME_MAP_FILE}" 2>/dev/null
}
if [ -z "${_BT_PROJECT_ENV}" ]; then
_BT_PROJECT_ENV="$(_bt_map_safe_get ${ACTIVATE_NAME})"
else
ACTIVATE_NAME=$(basename "${_BT_PROJECT_ENV}")
fi
if [ ! -d "${_BT_PROJECT_ENV}" ]; then
echo "Virtual environment for project: ${ACTIVATE_NAME} does not exist"
return 1
fi
# This file must be used with "source bin/activate" *from bash*
# You cannot run it directly
bt_env_deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" ] || [ -n "${ZSH_VERSION:-}" ] ; then
hash -r 2> /dev/null
fi
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
unset VIRTUAL_ENV_PROMPT
if [ ! "${1:-}" = "nondestructive" ] ; then
# Self destruct!
unset -f bt_env_deactivate
fi
}
# unset irrelevant variables
bt_env_deactivate nondestructive
_OLD_VIRTUAL_PATH="$PATH"
if [ -d "${_BT_PROJECT_ENV}/bin" ] ; then
VIRTUAL_ENV="${_BT_PROJECT_ENV}"
PATH="$VIRTUAL_ENV/bin:$PATH"
else
VIRTUAL_ENV="${_BT_PROJECT_ENV}"
PATH="$VIRTUAL_ENV:$PATH"
fi
# use the path as-is
export VIRTUAL_ENV
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
PS1="(${ACTIVATE_NAME}) ${PS1:-}"
export PS1
VIRTUAL_ENV_PROMPT="(${ACTIVATE_NAME}) "
export VIRTUAL_ENV_PROMPT
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" ] || [ -n "${ZSH_VERSION:-}" ] ; then
hash -r 2> /dev/null
fi

40
script/check_msg.py Normal file
View File

@@ -0,0 +1,40 @@
#coding: utf-8
import sys,os,time
os.chdir('/www/server/panel/')
sys.path.insert(0,"class/")
import public
import http_requests
http_requests.DEFAULT_TYPE = 'src'
os.environ['BT_TASK'] = '1'
def main():
import panelMessage
import re
msgObj = panelMessage.panelMessage()
data = msgObj.get_messages()
for x in data:
if x['level'] in ['danger', 'error'] and not x['send'] and x['retry_num'] < 5:
msg = '服务器IP【{}】: {}'.format(
public.GetLocalIp(), re.sub(r'?<a\s*.+</a>', '', x['msg']))
is_send = False
ret = public.return_is_send_info()
for key in ret:
if ret[key]:
ret = public.send_body_words(key, 'YakPanel 消息提醒', msg)
if ret:
is_send = True
pdata = {}
if is_send:
pdata['send'] = 1
pdata['retry_num'] = 0
else:
pdata['send'] = 0
pdata['retry_num'] = x['retry_num'] + 1
msgObj.set_send_status(x['id'], pdata)
time.sleep(5)
if __name__ == "__main__":
main()

82
script/createSsl.py Normal file
View File

@@ -0,0 +1,82 @@
#自签证书脚本 hezhihong
import sys,os
os.chdir('/www/server/panel/')
sys.path.insert(0,"class/")
sys.path.insert(0,"class_v2/")
import public,json
class CreateSSLMain:
cert_file="/www/server/vhost_virtual/data/cert/vhost.crt"
key_file="/www/server/vhost_virtual/data/cert/vhost.key"
def get_ipaddress(self):
'''
@name 获取本机IP地址
@author hwliang<2020-11-24>
@return list
'''
ipa_tmp = public.ExecShell("ip a |grep inet|grep -v inet6|grep -v 127.0.0.1|awk '{print $2}'|sed 's#/[0-9]*##g'")[0].strip()
iplist = ipa_tmp.split('\n')
return iplist
def get_host_all(self):
local_ip = ['127.0.0.1','::1','localhost']
ip_list = []
bind_ip = self.get_ipaddress()
for ip in bind_ip:
ip = ip.strip()
if ip in local_ip: continue
if ip in ip_list: continue
ip_list.append(ip)
net_ip = public.httpGet("https://ifconfig.me/ip")
if net_ip:
net_ip = net_ip.strip()
if not net_ip in ip_list:
ip_list.append(net_ip)
if len(ip_list) > 1:
ip_list = [ip_list[-1],ip_list[0]]
return ip_list
#自签证书
def CreateSSL(self):
if os.path.exists(self.cert_file) and os.path.exists(self.key_file): return True
import base64
userInfo = public.get_user_info()
if not userInfo:
userInfo['uid'] = 0
userInfo['access_key'] = 'B' * 32
if 'access_key' not in userInfo or not userInfo['access_key']:
userInfo['access_key'] = 'B' * 32
domains = self.get_host_all()
pdata = {
"action":"get_domain_cert",
"company":"yakpanel.com",
"domain":','.join(domains),
"uid":userInfo['uid'],
"access_key":userInfo['access_key'],
"panel":1
}
cert_api = 'https://api.yakpanel.com/yakpanel_cert'
result = json.loads(public.httpPost(cert_api,{'data': json.dumps(pdata)}))
if 'status' in result:
if result['status']:
public.writeFile(self.cert_file,result['cert'])
public.writeFile(self.key_file,result['key'])
_cert_dir = '/www/server/vhost_virtual/data/cert'
for _rpfx in (f'{_cert_dir}/yakpanel_root.pfx', f'{_cert_dir}/baota_root.pfx'):
if os.path.exists(_rpfx):
os.remove(_rpfx)
public.writeFile(f'{_cert_dir}/yakpanel_root.pfx', base64.b64decode(result['pfx']), 'wb+')
public.writeFile('/www/server/vhost_virtual/data/cert/root_password.pl',result['password'])
print('1')
return True
print('0')
return False
if __name__ == '__main__':
ssl = CreateSSLMain()
ssl.CreateSSL()

31
script/cron_scaning.py Normal file
View File

@@ -0,0 +1,31 @@
import os,sys
panel_path = '/www/server/panel'
if not os.name in ['nt']:
os.chdir(panel_path)
if not 'class/' in sys.path:
sys.path.insert(0, 'class/')
if not 'class_v2/' in sys.path:
sys.path.insert(0, 'class_v2/')
sys.path.insert(0, '.')
import public
from mod.base.push_mod import system
class main:
def run(self):
msg_list = []
from panel_site_v2 import panelSite
site_obj = panelSite()
res = site_obj.get_Scan(None)
if int(res['loophole_num']):
msg_list.append('Scan the website {} and find {} vulnerabilities'.format(res['site_num'], res['loophole_num']))
else:
msg_list.append('Scan the website [{}], status is [Security]'.format(res['site_num']))
return {"msg_list": msg_list}
if __name__ == '__main__':
main = main()
msg = main.run()
system.push_by_task_keyword("vulnerability_scanning", "vulnerability_scanning", push_data=msg)

View File

@@ -0,0 +1,42 @@
#coding: utf-8
#-------------------------------------------------------------------
# yakpanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel (https://www.yakpanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@yakpanel.com>
#-------------------------------------------------------------------
#------------------------------
# 任务编排调用脚本
#------------------------------
import sys,os
os.chdir('/www/server/panel')
sys.path.insert(0,'class/')
sys.path.insert(0,'class_v2/')
# import PluginLoader
import public
args = public.dict_obj()
if len(sys.argv) < 2:
print('ERROR: Task ID not found.')
sys.exit()
args.trigger_id = int(sys.argv[1])
args.model_index = 'crontab_v2'
# res = PluginLoader.module_run('trigger','test_trigger',args)
# if not res['status']:
# print(res['msg'])
# sys.exit()
import public.PluginLoader as plugin_loader
mod_file = '{}/class_v2/crontabModelV2/triggerModel.py'.format(public.get_panel_path())
plugin_class = plugin_loader.get_module(mod_file)
class_string='main'
plugin_object = getattr(plugin_class,class_string)()
def_name='test_trigger'
res=getattr(plugin_object,def_name)(args)
if res['status'] != 0 and 'message' in res:
print(res['message'])
sys.exit()

95
script/dk_log_split.py Normal file
View File

@@ -0,0 +1,95 @@
#!/usr/bin/python
# coding: utf-8
# -----------------------------
# docker container log cutting script
# -----------------------------
import sys
import os
import time
import datetime
os.chdir("/www/server/panel")
sys.path.append('class/')
import public
class DkLogSpilt:
task_list = []
def __init__(self):
if not public.M('sqlite_master').db('docker_log_split').where('type=? AND name=?', ('table', 'docker_log_split')).count():
self.task_list = []
else:
self.task_list = public.M('docker_log_split').select()
def run(self):
if not self.task_list:
print('No docker log cutting task')
for task in self.task_list:
try:
if task['split_type'] == 'day':
self.day_split(task)
elif task['split_type'] == 'size':
self.size_split(task)
except:
print('{} Failed to cut log!'.format(task['name']))
def day_split(self, task):
now_time = int(time.time())
exec_time = int(self.get_timestamp_of_hour_minute(task['split_hour'], task['split_minute']))
if now_time <= exec_time <= now_time + 300:
print("{} container starts log cutting".format(task['name']))
split_path = '/var/lib/docker/containers/history_logs/{}/'.format(task['pid'])
if not os.path.exists(split_path):
os.makedirs(split_path)
os.rename(task['log_path'], split_path + task['pid'] + "-json.log" + '_' + str(int(time.time())))
public.writeFile(task['log_path'], '')
print("{} log has been cut to:{}".format(task['name'],split_path + task['pid'] + "-json.log" + '_' + str(int(time.time()))))
self.check_save(task)
else:
print('{}container log has not reached the cutting time'.format(task['name']))
def size_split(self, task):
if not os.path.exists(task['log_path']):
print('Log file does not exist')
return
if os.path.getsize(task['log_path']) >= task['split_size']:
print("{} container starts log cutting".format(task['name']))
split_path = '/var/lib/docker/containers/history_logs/{}/'.format(task['pid'])
if not os.path.exists(split_path):
os.makedirs(split_path)
os.rename(task['log_path'], split_path + task['pid'] + "-json.log" + '_' + str(int(time.time())))
public.writeFile(task['log_path'], '')
print("{} log has been cut to:{}".format(task['name'],split_path + task['pid'] + "-json.log" + '_' + str(int(time.time()))))
self.check_save(task)
else:
print('{} container log has not reached cutting size'.format(task['name']))
def check_save(self, task):
split_path = '/var/lib/docker/containers/history_logs/{}/'.format(task['pid'])
file_count = len(os.listdir(split_path))
if file_count > task['save']:
file_list = os.listdir(split_path)
file_list.sort()
for i in range(file_count - task['save']):
os.remove(split_path + file_list[i])
print('Delete log files:{}'.format(split_path + file_list[i]))
print('The latest {} logs have been retained'.format(task['save']))
def get_timestamp_of_hour_minute(self, hour, minute):
"""获取当天指定时刻的时间戳。
Args:
hour: 小时。
minute: 分钟。
Returns:
时间戳。
"""
current_time = datetime.datetime.now()
timestamp = current_time.replace(hour=hour, minute=minute, second=0, microsecond=0)
return int(timestamp.timestamp())
if __name__ == '__main__':
dk = DkLogSpilt()
dk.run()

254
script/free_total_update.py Normal file
View File

@@ -0,0 +1,254 @@
# encoding utf-8
import subprocess
import requests
import os
import logging
import tempfile
class UpdateSiteTotal:
# 类级别的常量定义,集中管理固定配置
DEFAULT_USER_AGENT = "Yak-Panel/10.0"
INSTALL_SCRIPT_TIMEOUT = 600 # 安装脚本超时时间(秒)
VERSION_CHECK_TIMEOUT = 10 # 版本检查超时时间(秒)
SCRIPT_DOWNLOAD_TIMEOUT = 15 # 脚本下载超时时间(秒)
site_total_path = '/www/server/site_total'
download_url = 'https://node.yakpanel.com/site_total/'
version_url = download_url + 'version.txt'
install_sh = download_url + 'install.sh'
site_total_bin = os.path.join(site_total_path, 'site_total')
def __init__(self):
"""初始化更新工具
"""
# 初始化日志配置
self._init_logger()
def _init_logger(self):
"""初始化日志配置(封装为独立方法,便于维护)"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# ------------------------------
# 版本相关方法
# ------------------------------
def get_current_version(self):
"""获取当前安装的版本号"""
if not self.is_installed():
self._log_warning(f"{self.site_total_bin} is not detected, so the current version cannot be obtained")
return None
try:
# 执行版本命令
result = subprocess.run(
[self.site_total_bin, 'version'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=True,
timeout=self.VERSION_CHECK_TIMEOUT
)
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, Exception) as e:
return self._handle_version_cmd_error(e)
# 解析版本号(调用辅助方法)
return self._parse_version_output(result)
def _parse_version_output(self, result):
"""解析版本命令的输出结果,提取版本号"""
# 合并stdout和stderr避免版本信息输出到错误流
output_lines = result.stdout.splitlines() + result.stderr.splitlines()
for line in output_lines:
if "Version:" in line:
# 提取版本号并格式化确保x.y格式
raw_version = line.split(":")[-1].strip()
return self._format_version(raw_version)
self._log_warning(f"The version number cannot be parsed from the output: {output_lines}")
return None
def _format_version(self, raw_version):
"""将原始版本号格式化为x.y的字符串格式"""
try:
float_version = float(raw_version)
return f"{float_version:.1f}"
except ValueError:
self._log_error(f"The version number format is abnormal. It's the original value: {raw_version}")
return None
def _handle_version_cmd_error(self, error):
"""处理版本命令执行中的异常"""
if isinstance(error, subprocess.CalledProcessError):
self._log_error(f"The version command failed to execute: {error.stderr.strip()}")
elif isinstance(error, subprocess.TimeoutExpired):
self._log_error("The version command has timed out")
else:
self._log_error(f"An error occurred when obtaining the version number: {str(error)}")
return None
def get_latest_version(self):
"""获取最新版本号(返回字符串格式)"""
try:
response = requests.get(
self.version_url,
timeout=self.VERSION_CHECK_TIMEOUT,
headers={"User-Agent": self.DEFAULT_USER_AGENT}
)
response.raise_for_status()
latest_version = response.text.strip()
if not latest_version:
self._log_warning("The latest version number obtained is empty")
return None
return latest_version
except requests.RequestException as e:
self._log_error(f"Failed to get the latest version: {str(e)}")
return None
def check_update_available(self):
"""检查是否有可用更新基于float格式版本号比较"""
current_version_str = self.get_current_version()
latest_version_str = self.get_latest_version()
# 检查版本号获取结果
if not current_version_str:
return False, "The current version cannot be obtained"
if not latest_version_str:
return False, "The latest version cannot be obtained"
# 版本号比较
try:
current_version = float(current_version_str)
latest_version = float(latest_version_str)
except ValueError as e:
error_msg = f"Version number format error (it should be x.y): {str(e)}"
self._log_error(f"{error_msg}Current: {current_version_str}, Latest: {latest_version_str}")
return False, error_msg
if latest_version > current_version:
return True, f"Current version: {current_version_str}, The latest version: {latest_version_str}"
else:
return False, f"Current version: {current_version_str}, The latest version: {latest_version_str}"
# ------------------------------
# 安装/更新相关方法
# ------------------------------
def is_installed(self):
"""检查site_total是否已安装且可执行"""
return os.path.exists(self.site_total_bin) and os.access(self.site_total_bin, os.X_OK)
def install_or_update(self):
"""执行安装或更新操作"""
# 下载安装脚本
install_script = self._download_install_script()
if not install_script:
return False, "The download and installation script failed"
# 执行安装脚本
return self._execute_install_script(install_script)
def _download_install_script(self):
"""下载安装脚本"""
try:
response = requests.get(
self.install_sh,
timeout=self.SCRIPT_DOWNLOAD_TIMEOUT,
headers={"User-Agent": self.DEFAULT_USER_AGENT}
)
response.raise_for_status()
return response.text
except requests.RequestException as e:
self._log_error(f"The download and installation script failed: {str(e)}")
return None
def _execute_install_script(self, script_content):
"""执行安装脚本"""
temp_script = None
try:
# 创建临时脚本文件
temp_script = self._create_temp_script(script_content)
# 执行脚本
result = subprocess.run(
["bash", temp_script],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=self.INSTALL_SCRIPT_TIMEOUT
)
# 检查执行结果
if result.returncode != 0:
error_msg = (f"The installation script failed to execute (return code: {result.returncode})\n"
f"Error output: {result.stderr.strip()[:500]}")
self._log_error(error_msg)
return False, error_msg
if not self.is_installed():
return False, "The installation is complete but the executable file was not found"
return True, "Installation/update successful"
except subprocess.TimeoutExpired:
return False, "The installation script has timed out"
except Exception as e:
return False, f"An error occurred during the installation process: {str(e)}"
finally:
# 清理临时文件
self._cleanup_temp_script(temp_script)
def _create_temp_script(self, content):
"""创建临时脚本文件(辅助方法,封装文件操作)"""
with tempfile.NamedTemporaryFile(
mode='w',
suffix='.sh',
delete=False,
dir='/tmp'
) as f:
f.write(content)
return f.name
def _cleanup_temp_script(self, script_path):
"""清理临时脚本文件(辅助方法,确保资源释放)"""
if script_path and os.path.exists(script_path):
try:
os.remove(script_path)
except OSError as e:
self._log_warning(f"Temporary files cannot be deleted {script_path}: {str(e)}")
# ------------------------------
# 主逻辑与工具方法
# ------------------------------
def update_if_needed(self):
"""有更新时执行更新,未安装时执行安装(主逻辑入口)"""
if not self.is_installed():
self._log_info("Installation not detected. Start the installation")
return self.install_or_update()
has_update, msg = self.check_update_available()
if not has_update:
return False, f"No update required: {msg}"
self._log_info(f"Update detected: {msg}Start implementing the update")
return self.install_or_update()
# 日志工具方法(封装日志调用,便于统一管理)
def _log_info(self, msg):
logging.info(msg)
def _log_warning(self, msg):
logging.warning(msg)
def _log_error(self, msg):
logging.error(msg)
if __name__ == "__main__":
updater = UpdateSiteTotal()
success, message = updater.update_if_needed()
logging.info(f"Operation result: {'Success' if success else 'Failure'} - {message}")

26
script/ftp.sh Normal file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
install_tmp='/tmp/bt_install.pl'
download_Url=https://www.yakpanel.com
Install_Ftp()
{
echo 'Installing script files...' > $install_tmp
wget -O /www/server/panel/script/backup_ftp.py $download_Url/install/lib/script/backup_ftp.py -T 5
echo 'The installation is complete' > $install_tmp
}
Uninstall_Ftp()
{
rm -f /www/server/panel/script/backup_ftp.py
echo 'Uninstall complete' > $install_tmp
}
action=$1
if [ "${1}" == 'install' ];then
Install_Ftp
else
Uninstall_Ftp
fi

25
script/ftplogs_cut.py Normal file
View File

@@ -0,0 +1,25 @@
import os, sys
os.chdir('/www/server/panel')
sys.path.append('class/')
import public
ftp_backup_path = public.get_backup_path() + '/pure-ftpd/'
ftp_log_file = '/var/log/pure-ftpd.log'
if not os.path.exists(ftp_backup_path):
public.ExecShell('mkdir -p {}'.format(ftp_backup_path))
from datetime import date, timedelta
yesterday = (date.today() + timedelta(days=-1)).strftime("%Y-%m-%d")
ftp_file = ftp_backup_path + yesterday + '_pure-ftpd.log'
conf = ''
if os.path.isfile(ftp_file):
old_conf = public.readFile(ftp_file)
conf += old_conf
conf += '\n'
new_conf = public.readFile(ftp_log_file)
if new_conf: conf += new_conf
public.writeFile(ftp_file, conf)
public.writeFile(ftp_log_file, '')
print('|pure-ftpd log cut completed')

159
script/init_firewall.sh Normal file
View File

@@ -0,0 +1,159 @@
#!/bin/bash
create_chain() {
# 创建iptables链
# params表名 链名
local table=$1
local chain=$2
if ! iptables -t "$table" -n -L "$chain" > /dev/null 2>&1; then
iptables -t "$table" -N "$chain"
echo "Created chain $chain in table $table"
else
echo "Chain $chain already exists in table $table"
fi
}
insert_input_output_rules() {
# 在指定的表的链中插入子链
# params"表名:目标链名:需要插入的链"
local rules=("$@")
for rule in "${rules[@]}"; do
IFS=':' read -r table chain target <<< "$rule"
if ! iptables -t "$table" -C "$chain" -j "$target" > /dev/null 2>&1; then
iptables -t "$table" -I "$chain" 1 -j "$target"
echo "Inserted $target to $chain in table $table"
else
echo "$target already in $chain in table $table"
fi
done
}
add_jump_rules() {
# 在指定的表的链中添加跳转规则
# params表名 目标链名 需要跳转的链
local table=$1
local target_chain=$2
shift 2
local chains=("$@")
for chain in "${chains[@]}"; do
if ! iptables -t "$table" -C "$target_chain" -j "$chain" > /dev/null 2>&1; then
iptables -t "$table" -A "$target_chain" -j "$chain"
echo "Added $chain to $target_chain in table $table"
else
echo "$chain already in $target_chain in table $table"
fi
done
}
create_ipset() {
local ipset_name=$1
if ! ipset list "$ipset_name" > /dev/null 2>&1; then
ipset create "$ipset_name" hash:net maxelem 100000 timeout 0
echo "Created ipset $ipset_name"
else
echo "ipset $ipset_name already exists"
fi
}
add_ipset_rules() {
local rules=("$@")
for rule in "${rules[@]}"; do
IFS=':' read -r chain action direction ipset_name <<< "$rule"
if ! iptables -C "$chain" -m set --match-set "$ipset_name" "$direction" -j "$action" > /dev/null 2>&1; then
iptables -I "$chain" 1 -m set --match-set "$ipset_name" "$direction" -j "$action"
echo "Added $action rule for $ipset_name ($direction) in $chain"
else
echo "$action rule for $ipset_name ($direction) already in $chain"
fi
done
}
# 函数创建systemd服务
create_systemd_service() {
local exec_path="/www/server/panel/pyenv/bin/python3 /www/server/panel/script/BT-FirewallServices.py"
local service_file="/etc/systemd/system/BT-FirewallServices.service"
if [ ! -f "$service_file" ]; then
/www/server/panel/pyenv/bin/python3 -c "import os,sys; os.chdir('/www/server/panel/'); sys.path.insert(0, 'class/'); sys.path.insert(0, '/www/server/panel/'); import public; public.stop_syssafe();"
cat << EOF > "$service_file"
[Unit]
Description=Firewall and System Event Listener Service
After=network.target
[Service]
ExecStart=$exec_path start
ExecReload=$exec_path reload
ExecStop=$exec_path stop
User=root
Type=simple
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable BT-FirewallServices.service
${exec_path} save
systemctl start BT-FirewallServices.service
echo "Systemd service created and started"
/www/server/panel/pyenv/bin/python3 -c "import os,sys; os.chdir('/www/server/panel/'); sys.path.insert(0, 'class/'); sys.path.insert(0, '/www/server/panel/'); import public; public.start_syssafe();"
else
echo "Systemd service already exists"
fi
}
main() {
# 所有需要创建接管的子链
local chains=(
"filter:IN_BT"
"filter:IN_BT_log"
"filter:IN_BT_user_ip"
"filter:IN_BT_ip"
"filter:IN_BT_user_port"
"filter:OUT_BT"
"filter:OUT_BT_user_ip"
"filter:OUT_BT_user_port"
"filter:IN_BT_Country"
"nat:FORWARD_BT"
)
for chain in "${chains[@]}"; do
IFS=':' read -r table chain_name <<< "$chain"
create_chain "$table" "$chain_name"
done
# 插入接管的子链
local rules=(
"filter:INPUT:IN_BT"
"filter:IN_BT:IN_BT_log"
"filter:IN_BT:IN_BT_user_ip"
"filter:IN_BT:IN_BT_ip"
"filter:IN_BT:IN_BT_user_port"
"filter:IN_BT_ip:IN_BT_Country"
"filter:OUTPUT:OUT_BT"
"filter:OUT_BT:OUT_BT_user_ip"
"filter:OUT_BT:OUT_BT_user_port"
"nat:PREROUTING:FORWARD_BT"
)
insert_input_output_rules "${rules[@]}"
# ipset集合
local ipsets=(
"in_bt_user_accept_ipset"
"in_bt_user_drop_ipset"
"out_bt_user_accept_ipset"
"out_bt_user_drop_ipset"
)
for ipset_name in "${ipsets[@]}"; do
create_ipset "$ipset_name"
done
local ipset_rules=(
"IN_BT_user_ip:ACCEPT:src:in_bt_user_accept_ipset"
"IN_BT_user_ip:DROP:src:in_bt_user_drop_ipset"
"OUT_BT_user_ip:ACCEPT:dst:out_bt_user_accept_ipset"
"OUT_BT_user_ip:DROP:dst:out_bt_user_drop_ipset"
)
add_ipset_rules "${ipset_rules[@]}"
create_systemd_service
systemctl reload BT-FirewallServices
echo "yakpanel firewall init finish..."
}
main

235
script/install.sh Normal file
View File

@@ -0,0 +1,235 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "
+----------------------------------------------------------------------
| Bt-WebPanel 3.0 FOR CentOS beta
+----------------------------------------------------------------------
| Copyright (c) 2015-2017 YakPanel(https://www.yakpanel.com) All rights reserved.
+----------------------------------------------------------------------
| Python2.6/2.7 successful the http://SERVER_IP:8888 is WebPanel
+----------------------------------------------------------------------
"
download_Url='https://www.yakpanel.com'
setup_patn=/www
while [ "$go" != 'y' ] && [ "$go" != 'n' ]
do
read -p "Now do you want to install Yak-Panel to the $setup_patn directory?(y/n): " go;
done
if [ "$go" == 'n' ];then
exit;
fi
yum -y install ntp
\cp -a -r /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
echo 'Synchronizing system time..'
ntpdate 0.asia.pool.ntp.org
hwclock -w
startTime=`date +%s`
rm -f /var/run/yum.pid
paces="wget python-devel python-imaging zip unzip openssl openssl-devel gcc libxml2 libxml2-dev libxslt* zlib zlib-devel libjpeg-devel libpng-devel libwebp libwebp-devel freetype freetype-devel lsof pcre pcre-devel vixie-cron crontabs"
yum -y install $paces
if [ ! -f '/usr/bin/mysql_config' ];then
yum install mysql-devel -y
fi
tmp=`python -V 2>&1|awk '{print $2}'`
pVersion=${tmp:0:3}
if [ ! -f "/usr/lib/python${pVersion}/site-packages/setuptools-33.1.1-py${pVersion}.egg" ];then
wget $download_Url/install/src/setuptools-33.1.1.zip -T 10
unzip setuptools-33.1.1.zip
rm -f setuptools-33.1.1.zip
cd setuptools-33.1.1
python setup.py install
cd ..
rm -rf setuptools-33.1.1
fi
if [ ! -f "/usr/lib64/python${pVersion}/site-packages/Pillow-3.2.0-py${pVersion}-linux-x86_64.egg" ] && [ ! -f "/usr/lib/python${pVersion}/site-packages/Pillow-3.2.0-py${pVersion}-linux-x86_64.egg" ];then
wget $download_Url/install/src/Pillow-3.2.0.zip -T 10
unzip Pillow-3.2.0.zip
rm -f Pillow-3.2.0.zip
cd Pillow-3.2.0
python setup.py install
cd ..
rm -rf Pillow-3.2.0
fi
if [ ! -d "/usr/lib/python${pVersion}/site-packages/psutil-5.1.3-py${pVersion}-linux-x86_64.egg" ] && [ ! -d "/usr/lib64/python${pVersion}/site-packages/psutil-5.1.3-py${pVersion}-linux-x86_64.egg" ];then
wget $download_Url/install/src/psutil-5.1.3.tar.gz -T 10
tar xvf psutil-5.1.3.tar.gz
rm -f psutil-5.1.3.tar.gz
cd psutil-5.1.3
python setup.py install
cd ..
rm -rf psutil-5.1.3
fi
if [ ! -f "/usr/lib64/python${pVersion}/site-packages/MySQL_python-1.2.5-py${pVersion}-linux-x86_64.egg" ] && [ ! -f "/usr/lib/python${pVersion}/site-packages/MySQL_python-1.2.5-py${pVersion}-linux-x86_64.egg" ];then
wget $download_Url/install/src/MySQL-python-1.2.5.zip -T 10
unzip MySQL-python-1.2.5.zip
rm -f MySQL-python-1.2.5.zip
cd MySQL-python-1.2.5
python setup.py install
cd ..
rm -rf MySQL-python-1.2.5
fi
if [ ! -f "/usr/lib/python${pVersion}/site-packages/chardet-2.3.0-py${pVersion}.egg" ];then
wget $download_Url/install/src/chardet-2.3.0.tar.gz -T 10
tar xvf chardet-2.3.0.tar.gz
rm -f chardet-2.3.0.tar.gz
cd chardet-2.3.0
python setup.py install
cd ..
rm -rf chardet-2.3.0
fi
if [ ! -f "/usr/lib/python${pVersion}/site-packages/web.py-0.38-py${pVersion}.egg-info" ];then
wget $download_Url/install/src/web.py-0.38.tar.gz -T 10
tar xvf web.py-0.38.tar.gz
rm -f web.py-0.38.tar.gz
cd web.py-0.38
python setup.py install
cd ..
rm -rf web.py-0.38
fi
mkdir -p $setup_patn/server/panel/logs
wget https://dl.eff.org/certbot-auto --no-check-certificate -O $setup_patn/server/panel/certbot-auto
chmod +x $setup_patn/server/panel/certbot-auto
isCron=`cat /var/spool/cron/root|grep certbot.log`
if [ "${isCron}" == "" ];then
echo "30 2 * * * $setup_patn/server/panel/certbot-auto renew >> $setup_patn/server/panel/logs/certbot.log" >> /var/spool/cron/root
chown 600 /var/spool/cron/root
fi
if [ -f '/etc/init.d/bt' ];then
service bt stop
fi
mkdir -p /www/server
mkdir -p /www/wwwroot
mkdir -p /www/wwwlogs
mkdir -p /www/backup/database
mkdir -p /www/backup/site
wget -O panel.zip $download_Url/install/src/panel.zip -T 10
wget -O /etc/init.d/bt $download_Url/install/src/bt.init -T 10
if [ -f "$setup_patn/server/panel/data/default.db" ];then
if [ -d "/$setup_patn/server/panel/old_data" ];then
rm -rf /$setup_patn/server/panel/old_data
fi
mv $setup_patn/server/panel/data /$setup_patn/server/panel/old_data
fi
unzip -o panel.zip -d $setup_patn/server/ > /dev/null
if [ -d "$setup_patn/server/panel/old_data" ];then
if [ -d "/$setup_patn/server/panel/data" ];then
rm -rf /$setup_patn/server/panel/data
fi
mv /$setup_patn/server/panel/old_data $setup_patn/server/panel/data
fi
rm -f panel.zip
rm -f $setup_patn/server/panel/class/*.pyc
rm -f $setup_patn/server/panel/*.pyc
python -m compileall $setup_patn/server/panel
rm -f $setup_patn/server/panel/class/*.py
rm -f $setup_patn/server/panel/*.py
chmod +x /etc/init.d/bt
chkconfig --add bt
chkconfig --level 2345 bt on
echo '8888' > $setup_patn/server/panel/data/port.pl
chmod -R 600 $setup_patn/server/panel
chmod +x $setup_patn/server/panel/certbot-auto
chmod -R +x $setup_patn/server/panel/script
service bt start
password=`cat /dev/urandom | head -n 16 | md5sum | head -c 8`
cd $setup_patn/server/panel/
python tools.pyc panel $password
cd ~
echo "$password" > $setup_patn/server/panel/default.pl
chmod 600 $setup_patn/server/panel/default.pl
if [ -f "/etc/init.d/iptables" ];then
iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 20 -j ACCEPT
iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 21 -j ACCEPT
iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 8888 -j ACCEPT
iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 30000:40000 -j ACCEPT
service iptables save
iptables_status=`service iptables status | grep 'not running'`
if [ "${iptables_status}" == '' ];then
service iptables restart
fi
fi
if [ "${isVersion}" == '' ];then
if [ ! -f "/etc/init.d/iptables" ];then
yum install firewalld -y
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --permanent --zone=public --add-port=20/tcp
firewall-cmd --permanent --zone=public --add-port=21/tcp
firewall-cmd --permanent --zone=public --add-port=22/tcp
firewall-cmd --permanent --zone=public --add-port=80/tcp
firewall-cmd --permanent --zone=public --add-port=8888/tcp
firewall-cmd --permanent --zone=public --add-port=30000-40000/tcp
firewall-cmd --reload
fi
fi
yum -y install epel-release
country=`curl -sS --connect-timeout 10 -m 60 http://ip.vpser.net/country`
if [ "${country}" = "CN" ]; then
mkdir ~/.pip
cat > ~/.pip/pip.conf <<EOF
[global]
index-url = https://pypi.doubanio.com/simple/
[install]
trusted-host=pypi.doubanio.com
EOF
fi
nohup $setup_patn/server/panel/certbot-auto -n > /tmp/certbot-auto.log 2>&1 &
address=""
n=0
while [ "$address" == '' ]
do
address=`curl -s http://city.ip138.com/ip2city.asp|grep -Eo '([0-9]+\.){3}[0-9]+'`
let n++
sleep 0.1
if [ $n -gt 5 ];then
address="SERVER_IP"
fi
done
curl http://www.yakpanel.com/Api/SetupCount?type=Linux
echo "====================================="
echo -e "\033[32mThe install successful!\033[0m"
echo -e "====================================="
echo -e "Yak-Panel: http://$address:8888"
echo -e "username: admin"
echo -e "password: $password"
echo -e "====================================="
endTime=`date +%s`
((outTime=($endTime-$startTime)/60))
echo -e "Time consuming:\033[32m $outTime \033[0mMinute!"
rm -f install.sh

View File

@@ -0,0 +1,20 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
mypip="/www/server/panel/pyenv/bin/pip3"
pycountry_path="/www/server/panel/script/pycountry-24.6.1-py3-none-any.whl"
change_pip_package_list=$( $mypip list | grep -E "pycountry" )
pycountry_v=$(echo "$change_pip_package_list" | grep pycountry)
if [ "$pycountry_v" = "" ];then
echo "Update pycountry"
$mypip install $pycountry_path
rm -f $pycountry_path
pycountry_v_2=$($mypip list |grep pycountry)
if [ "$pycountry_v_2" = "" ];then
$mypip install pycountry
fi
fi

View File

@@ -0,0 +1,20 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
mypip="/www/server/panel/pyenv/bin/pip3"
pyroute2_path="/www/server/panel/script/pyroute2-0.7.12-py3-none-any.whl"
change_pip_package_list=$( $mypip list | grep -E "pyroute2" )
pyroute2_v=$(echo "$change_pip_package_list" | grep pyroute2)
if [ "$pyroute2_v" = "" ];then
echo "Update pyroute2"
$mypip install $pyroute2_path
rm -f $pyroute2_path
pyroute2_v_2=$($mypip list |grep pyroute2)
if [ "$pyroute2_v_2" = "" ];then
$mypip install pyroute2
fi
fi

55
script/install_python.sh Normal file
View File

@@ -0,0 +1,55 @@
#!/bin/bash
pyversion=${1}
py_path=/www/server/pyporject_evn/versions
py_cache=/www/server/pyporject_evn/versions/cached
cpuCore=$(cat /proc/cpuinfo |grep "physical id"|sort |uniq|wc -l)
mkdir -p ${py_path}
download_Url='https://node.yakpanel.com'
install_python() {
\cp ${py_cache}/Python-${pyversion}.tar.xz /tmp/Python-${pyversion}.tar.xz
cd /tmp/ && xz -d /tmp/Python-${pyversion}.tar.xz && tar -xvf /tmp/Python-${pyversion}.tar
cd /tmp/Python-${pyversion} || exit
if [ ${pyversion:0:1} -ge 2 ]; then
openssl111check=$(openssl version | grep 1.1.1)
if [ -z "${openssl111check}" ]; then
Install_Openssl111
WITH_SSL="--with-openssl=/usr/local/openssl111"
else
WITH_SSL=""
fi
cd /tmp/Python-${pyversion} || exit
./configure --prefix=${py_path}/${pyversion} ${WITH_SSL} -with-openssl-rpath=auto
make -j${cpuCore}
make install
rm -rf /tmp/Python-*
else
./configure --prefix=${py_path}/${pyversion}
make -j${cpuCore}
make install
rm -rf /tmp/Python-*
fi
}
Install_Openssl111() {
opensslCheck=$(/usr/local/openssl111/bin/openssl version | grep 1.1.1)
if [ -z "${opensslCheck}" ]; then
opensslVersion="1.1.1o"
cd /tmp/
wget ${download_Url}/src/openssl-${opensslVersion}.tar.gz
tar -zxf openssl-${opensslVersion}.tar.gz
rm -f openssl-${opensslVersion}.tar.gz
cd openssl-${opensslVersion} || exit
./config --prefix=/usr/local/openssl111 zlib-dynamic
make -j${cpuCore}
make install
echo "/usr/local/openssl111/lib" >>/etc/ld.so.conf.d/openssl111.conf
ldconfig
ldconfig /lib64
cd ..
rm -rf openssl-${opensslVersion}
fi
}
install_python

View File

@@ -0,0 +1,63 @@
import datetime
import sys
import os
os.chdir('/www/server/panel/')
sys.path.insert(0, "class/")
import public
# 检查是否提供了日志文件路径作为命令行参数
if len(sys.argv) != 2:
print("Usage: python log_task_analyzer.py <log_file_path>")
sys.exit(1)
# 从命令行参数中获取日志文件路径
log_file = sys.argv[1]
# 记录任务执行状态的文件路径
task_log = "/tmp/task_log.log"
# 反向读取日志文件
try:
with open(log_file, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()[::-1]
except FileNotFoundError:
print("Log file {} not found.".format(log_file))
sys.exit(1)
# 初始化变量
found_successful = False
found_error_before_next_successful = False
# 要查找的错误模式
error_patterns = ["错误", "失败", "command not found", "Unknown error", "不存在", "请先到软件商店安装日志清理工具", "failed to run command", "No such file or directory", "not supported or disabled in libcurl"]
# 迭代检查日志内容
for line in lines:
if "Successful" in line:
if found_successful:
# 如果找到第二个 "Successful",停止搜索
break
else:
# 找到第一个 "Successful"
found_successful = True
elif any(error_pattern in line for error_pattern in error_patterns) and found_successful:
# 如果在找到 "Successful" 后发现任何错误模式,则标记错误
found_error_before_next_successful = True
break
# 根据检查结果记录日志
with open(task_log, 'a') as f:
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 提取 echo 值作为日志文件名的前缀
echo_value = log_file.split('/')[-1].split('.')[0]
crontab_data_list = public.M('crontab').where('echo=?', (echo_value,)).select()
if found_successful and found_error_before_next_successful:
if crontab_data_list:
public.M('crontab').where('echo=?', (echo_value,)).setField('result', 0)
# f.write("{0} - 最后一次定时任务执行发现错误,执行失败\n".format(current_time))
else:
if crontab_data_list:
public.M('crontab').where('echo=?', (echo_value,)).setField('result', 1)
# f.write("{0} - 最后一次定时任务执行成功\n".format(current_time))

253
script/logsBackup Normal file
View File

@@ -0,0 +1,253 @@
#!/usr/bin/python
# coding: utf-8
# -----------------------------
# 宝塔Linux面板网站日志切割脚本
# -----------------------------
import glob
import os
import re
import shutil
import sys
import time
os.chdir("/www/server/panel")
sys.path.append('class/')
sys.stdout.reconfigure(encoding="utf-8")
import public
print('==================================================================')
print('★[' + time.strftime("%Y/%m/%d %H:%M:%S") + '] Cutting log')
print('==================================================================')
print('|--Currently keeping the latest copies of [' + sys.argv[2] + ']')
logsPath = '/www/wwwlogs/'
is_gzip = not os.path.exists('/www/server/panel/data/log_not_gzip.pl')
is_nginx = False
if os.path.exists('/www/server/nginx/logs/nginx.pid'): is_nginx = True
px = '.log'
if not is_nginx: px = '-access_log'
def build_errlog(sitename):
if is_nginx:
log = sitename + '.error.log'
elif os.path.exists('/usr/local/lsws/bin/lswsctrl'):
log = sitename + '_ols.error_log'
else:
log = sitename + '-error_log'
return log
def clean_backlog(logname,num):
logs=sorted(glob.glob(logname+"_*"))
count=len(logs)
num=count - num
for i in range(count):
if i>num: break;
os.remove(logs[i])
print('|---The extra log ['+logs[i]+'] has been deleted!')
def split_logs(oldFileName,num,site_name):
global logsPath
errlog_name = build_errlog(site_name)
old_errlog = logsPath + errlog_name
if not os.path.exists(oldFileName):
print('|---'+oldFileName+'file does not exist!')
return
clean_backlog(oldFileName,num)
clean_backlog(old_errlog,num)
newFileName=oldFileName+'_'+time.strftime("%Y-%m-%d_%H%M%S")+'.log'
shutil.move(oldFileName,newFileName)
new_errlog = old_errlog+'_'+time.strftime("%Y-%m-%d_%H%M%S")+'.log'
shutil.move(old_errlog, new_errlog)
if not os.path.exists('/www/server/panel/data/log_not_gzip.pl'):
os.system("gzip %s" % newFileName)
os.system("gzip %s" % new_errlog)
print('|---The log has been cut to:' + newFileName + '.gz')
print('|---The log has been cut to:' + new_errlog + '.gz')
else:
print('|---The log has been cut to:'+newFileName+'.log')
print('|---The log has been cut to:' + new_errlog + '.log')
def split_log(siteName, num, log_cut_path):
global logsPath, is_gzip
res = public.M('sites').where('name=?', (siteName,)).select()[0]['project_type'].lower()
if res == 'php':
res = ''
else:
res = res + '_'
# print(res)
directory = '/etc/init.d/'
files = os.listdir(directory)
file_list = list(files)
print('|-Processing website: {}'.format(siteName))
if 'nginx' in file_list:
config_path = '/www/server/panel/vhost/nginx/{}.conf'.format(res + siteName)
config = public.readFile(config_path)
if not config:
print('|-Processing website: logs for {} site not detected'.format(siteName))
return
tmp, _ = nginx_get_log_path(config, is_error_log=False)
if tmp is not None:
logsPath = tmp
elif 'httpd' in file_list:
config_path = '/www/server/panel/vhost/apache/{}.conf'.format(res + siteName)
config = public.readFile(config_path)
if not config:
print('|-Processing website: logs for [{}] site not detected'.format(siteName))
return
tmp, _ = apache_get_log_path(config, is_error_log=False)
if tmp is not None:
logsPath = tmp
# print('|-正在处理网站: {}'.format(siteName))
# print(logsPath)
log_path = os.path.join(log_cut_path, 'history_backups', siteName)
log_path = log_path.replace('history_backups/history_backups/', 'history_backups/')
if not os.path.exists(log_path): os.makedirs(log_path, 384)
logs = sorted(glob.glob(log_path + '/' + siteName + "_access_*"))
old_logs = sorted(glob.glob(logsPath + '/' + siteName + "*log_*.log"))
count = len(logs)
remove_num = count - num
# print('|-当前日志数量: {}, 删除:{}'.format(count,remove_num))
old_list = old_logs[:remove_num+1]
for i in old_list:
if os.path.exists(i):
os.remove(old_logs[i])
print('|---The extra log [' + i + '] has been deleted!')
remove_num = remove_num - len(old_list)
for i in logs[:remove_num+1]:
if os.path.exists(i):
os.remove(i)
print('|---The extra log [' + i + '] has been deleted!')
err_file = i.replace('access', 'error')
if os.path.exists(err_file):
os.remove(err_file)
print('|---The extra log [' + err_file + '] has been deleted!')
his_date = time.strftime("%Y-%m-%d_%H%M%S")
ngx_access_log = os.path.join(logsPath, siteName + '.log')
ngx_error_log = os.path.join(logsPath, siteName + '.error.log')
if os.path.exists(ngx_access_log):
history_log_file = log_path + '/' + siteName + '_access_' + his_date + '.log'
shutil.move(ngx_access_log, history_log_file)
if is_gzip:
os.system('gzip {}'.format(history_log_file))
print('|---Cut logs to:' + history_log_file + '.gz')
else:
print('|---Cut logs to:' + history_log_file + '.gz')
if os.path.exists(ngx_error_log):
history_log_file = log_path + '/' + siteName + '_error_' + his_date + '.log'
shutil.move(ngx_error_log, history_log_file)
if is_gzip:
os.system('gzip {}'.format(history_log_file))
print('|---Cut logs to:' + history_log_file + '.gz')
else:
print('|---Cut logs to:' + history_log_file + '.gz')
httpd_access_log = os.path.join(logsPath, siteName + '-access_log')
httpd_error_log = os.path.join(logsPath, siteName + '-error_log')
if os.path.exists(httpd_access_log):
history_log_file = log_path + '/' + siteName + '_apache_access_' + his_date + '.log'
if not os.path.exists(history_log_file):
shutil.move(httpd_access_log, history_log_file)
if is_gzip:
os.system('gzip {}'.format(history_log_file))
print('|---Cut logs to:' + history_log_file + '.gz')
else:
print('|---Cut logs to:' + history_log_file + '.gz')
if os.path.exists(httpd_error_log):
history_log_file = log_path + '/' + siteName + '_apache_error_' + his_date + '.log'
if not os.path.exists(history_log_file):
shutil.move(httpd_error_log, history_log_file)
if is_gzip:
os.system('gzip {}'.format(history_log_file))
print('|---Cut logs to:' + history_log_file + '.gz')
else:
print('|---Cut logs to:' + history_log_file + '.gz')
ols_access_log = os.path.join(logsPath, siteName + '_ols.access_log')
ols_error_log = os.path.join(logsPath, siteName + '_ols.error_log')
if os.path.exists(ols_access_log):
history_log_file = log_path + '/' + siteName + '_ols_access_' + his_date + '.log'
if not os.path.exists(history_log_file):
shutil.move(ols_access_log, history_log_file)
if is_gzip:
os.system('gzip {}'.format(history_log_file))
print('|---Cut logs to:' + history_log_file + '.gz')
else:
print('|---Cut logs to:' + history_log_file + '.gz')
if os.path.exists(ols_error_log):
history_log_file = log_path + '/' + siteName + '_ols_error_' + his_date + '.log'
if not os.path.exists(history_log_file):
shutil.move(ols_error_log, history_log_file)
if is_gzip:
os.system('gzip {}'.format(history_log_file))
print('|---Cut logs to:' + history_log_file + '.gz')
else:
print('|---Cut logs to:' + history_log_file + '.gz')
def split_all(save,log_cut_path):
sites = public.M('sites').field('name').select()
for site in sites:
split_log(site['name'], save,log_cut_path)
def nginx_get_log_path(nginx_config: str, is_error_log: bool = False):
if is_error_log:
re_data = re.findall(r"error_log +(/(\S+/?)+) ?(.*?);", nginx_config)
else:
re_data = re.findall(r"access_log +(/(\S+/?)+) ?(.*?);", nginx_config)
if re_data is None:
return None, None
for i in re_data:
file_path = i[0].strip(";")
if file_path != "/dev/null":
return os.path.dirname(file_path), os.path.basename(file_path)
return None, None
def apache_get_log_path(apache_config: str, is_error_log: bool = False):
if is_error_log:
re_data = re.findall(r'''ErrorLog +['"]?(/(\S+/?)+)['"]? ?(.*?)\n''', apache_config)
else:
re_data = re.findall(r'''CustomLog +['"]?(/(\S+/?)+)['"]? ?(.*?)\n''', apache_config)
if re_data is None:
return None
for i in re_data:
file_path = i[0].strip('"').strip("'")
if file_path != "/dev/null":
return os.path.dirname(file_path), os.path.basename(file_path)
return None, None
if __name__ == '__main__':
num = int(sys.argv[2])
if len(sys.argv) > 3:
log_cut_path = sys.argv[3]
else:
log_cut_path = '/www/wwwlogs/'
if sys.argv[1].find('ALL') == 0:
split_all(num, log_cut_path)
else:
siteName = sys.argv[1]
try:
public.M('sites').where('name=?', (siteName,)).select()[0]['project_type'].lower()
split_log(sys.argv[1], num, log_cut_path)
except:
siteName = sys.argv[1]
if siteName[-4:] == '.log':
siteName = siteName[:-4]
elif siteName[-11:] == '-access_log':
siteName = siteName.replace("-access_log",'')
else:
siteName = siteName.replace("_ols.access_log", '')
oldFileName = logsPath+sys.argv[1]
split_logs(oldFileName,num,siteName)
public.serviceReload()

92
script/logsBackup.py Normal file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/python
#coding: utf-8
#-----------------------------
#YakPanel
# 网站日志切割脚本
#-----------------------------
import sys
import os
import shutil
import time
import glob
os.chdir("/www/server/panel")
sys.path.append('class/')
import public
print ('==================================================================')
print( '★['+time.strftime("%Y/%m/%d %H:%M:%S")+']Cutting log')
print ('==================================================================')
print ('|--Currently retaining the latest ['+sys.argv[2]+'] copies')
logsPath = '/www/wwwlogs/'
is_nginx = False
if os.path.exists('/www/server/nginx/logs/nginx.pid'): is_nginx = True
px = '.log'
if not is_nginx: px = '-access_log'
def build_errlog(sitename):
if is_nginx:
log = sitename + '.error.log'
elif os.path.exists('/usr/local/lsws/bin/lswsctl'):
log = sitename + '-error_log'
else:
log = sitename + '_ols.error_log'
return log
def clean_backlog(logname,num):
logs=sorted(glob.glob(logname+"_*"))
count=len(logs)
num=count - num
for i in range(count):
if i>num: break;
os.remove(logs[i])
print('|---The extra log ['+logs[i]+'] has been deleted!')
def split_logs(oldFileName,num,site_name):
global logsPath
errlog_name = build_errlog(site_name)
old_errlog = logsPath + errlog_name
if not os.path.exists(oldFileName):
print('|---'+oldFileName+'file does not exist!')
return
clean_backlog(oldFileName,num)
clean_backlog(old_errlog,num)
newFileName=oldFileName+'_'+time.strftime("%Y-%m-%d_%H%M%S")+'.log'
shutil.move(oldFileName,newFileName)
new_errlog = build_errlog(site_name)+'_'+time.strftime("%Y-%m-%d_%H%M%S")+'.log'
shutil.move(old_errlog, newFileName)
if not os.path.exists('/www/server/panel/data/log_not_gzip.pl'):
os.system("gzip %s" % newFileName)
os.system("gzip %s" % new_errlog)
print('|---The log has been cut to:' + newFileName + '.gz')
else:
print('|---The log has been cut to:'+newFileName+'.log')
def split_all(save):
sites = public.M('sites').field('name').select()
for site in sites:
oldFileName = logsPath + site['name'] + px
split_logs(oldFileName,save,site['name'])
if __name__ == '__main__':
num = int(sys.argv[2])
if sys.argv[1].find('ALL') == 0:
split_all(num)
else:
siteName = sys.argv[1]
if siteName[-4:] == '.log':
siteName = siteName[:-4]
elif siteName[-11:] == '-access_log':
siteName = siteName.replace("-access_log",'')
else:
siteName = siteName.replace("_ols.access_log", '')
oldFileName = logsPath+sys.argv[1]
split_logs(oldFileName,num,siteName)
if is_nginx:
os.system("kill -USR1 `cat /www/server/nginx/logs/nginx.pid`")
elif os.path.exists('/usr/local/lsws/bin/lswsctl'):
os.system('/usr/local/lsws/bin/lswsctl restart')
else:
os.system('/etc/init.d/httpd reload')

10
script/mail_task.py Normal file
View File

@@ -0,0 +1,10 @@
#coding: utf-8
import os,sys
sys.path.insert(0,"/www/server/panel/class/")
try:
import send_to_user
msg=send_to_user.send_to_user()
msg.main()
except:
pass

View File

@@ -0,0 +1,234 @@
import os
import shutil
import sys
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
import public
# ols 修改多服务配置文件
def ols_update_config(status, is_restart=True):
"""
端口关系:
8188:80
8190:443
"""
listen_dir = os.path.join(public.get_panel_path(), 'vhost', 'openlitespeed')
listen_main = os.path.join(listen_dir, 'listen', '80.conf') # 主监听
listen_ssl = os.path.join(listen_dir, 'listen', '443.conf')
phpmyadmin = [
os.path.join(listen_dir, 'listen', '887.conf'),
os.path.join(listen_dir, 'listen', '888.conf'),
os.path.join(listen_dir, 'phpmyadmin.conf'),
os.path.join(listen_dir, 'detail', 'phpmyadmin.conf')
]
pattern = '*:80'
pattern_ANY = '[ANY]:80'
pattern_ssl = '*:443'
pattern_ssl_ANY = '[ANY]:443'
if status == 'enable':
if os.path.exists(listen_main):
content = public.readFile(listen_main)
content = content.replace(pattern, '*:8188')
content = content.replace(pattern_ANY, '[ANY]:8188')
public.writeFile(listen_main, content)
if os.path.exists(listen_ssl):
content = public.readFile(listen_ssl)
content = content.replace(pattern_ssl, '*:8190')
content = content.replace(pattern_ssl_ANY, '[ANY]:8190')
public.writeFile(listen_ssl, content)
# 取消监听phpmyadmin
for path in phpmyadmin:
if os.path.exists(path):
shutil.move(path, path + '.bar')
elif status == 'disable':
pattern = '*:8188'
pattern_ANY = '[ANY]:8188'
pattern_ssl = '*:8190'
pattern_ssl_ANY = '[ANY]:8190'
# 恢复服务
if os.path.exists(listen_main):
content = public.readFile(listen_main)
content = content.replace(pattern, '*:80')
content = content.replace(pattern_ANY, '[ANY]:80')
public.writeFile(listen_main, content)
if os.path.exists(listen_ssl):
content = public.readFile(listen_ssl)
content = content.replace(pattern_ssl, '*:443')
content = content.replace(pattern_ssl_ANY, '[ANY]:443')
public.writeFile(listen_ssl, content)
for path in phpmyadmin:
if os.path.exists(path + '.bar'):
shutil.move(path + '.bar', path)
# 处理用户添加的端口恢复
listen_custom_dir = os.path.join(listen_dir, 'listen')
if os.path.exists(listen_custom_dir):
for filename in os.listdir(listen_custom_dir):
file = filename.split('.')[0]
if file not in ['80', '443', '887', '888']:
content = public.readFile(os.path.join(listen_custom_dir, filename))
content = content.replace(pattern, '*:' + file)
content = content.replace(pattern_ANY, '*:' + file)
public.writeFile(os.path.join(listen_custom_dir, filename), content)
return True, "The ols configuration modification was successful"
# apache 修改多服务配置文件
def apache_update_config(status, is_restart=True) -> tuple[bool, str]:
"""
端口关系:
8288:80
8289:888
8290:443
"""
main_config = '/www/server/apache/conf/httpd.conf' # 主配置文件
httpd_vhosts = '/www/server/apache/conf/extra/httpd-vhosts.conf'
httpd_ssl = '/www/server/apache/conf/extra/httpd-ssl.conf'
phpadmin = os.path.join(public.get_panel_path(), 'vhost', 'apache', 'phpmyadmin.conf')
apache_adminer = f"/www/server/panel/vhost/apache/0.adminer.conf"
ols_adminer = f"/www/server/panel/vhost/openlitespeed/0.adminer.conf"
bar_list = [phpadmin, ols_adminer, apache_adminer]
port_80 = '80'
new_port_80 = '8288'
port_888 = '888'
new_port_888 = '8289'
port_443 = '443'
new_port_443 = '8290'
if status == 'disable':
port_80 = '8288'
new_port_80 = '80'
port_888 = '8289'
new_port_888 = '888'
port_443 = '8290'
new_port_443 = '443'
# 恢复配置文件
for bar in bar_list:
if os.path.exists(bar + '.bar'):
shutil.move(bar + '.bar', bar)
else:
# 使配置文件无效
for bar in bar_list:
if os.path.exists(bar):
shutil.move(bar, bar + '.bar')
# 修改虚拟主机端口配置,匹配所有网站配置文件
site_path_list = get_apache_site_conf()
for path in site_path_list:
if os.path.exists(path):
content = public.readFile(path)
content = content.replace(f'*:{port_80}', f'*:{new_port_80}')
content = content.replace(f'*:{port_443}', f'*:{new_port_443}')
content = content.replace(f'[::]:{port_80}', f'[::]:{new_port_80}')
content = content.replace(f'[::]:{port_443}', f'[::]:{new_port_443}')
public.writeFile(path, content)
# 处理node
site_name = public.M('sites').where('project_type = ?', 'Node').field('name').select()
for name in site_name:
check_node_project(name, status)
if os.path.exists(main_config):
content = public.readFile(main_config)
content = content.replace(f'Listen {port_80}', f'Listen {new_port_80}')
content = content.replace(f'Listen {port_443}', f'Listen {new_port_443}')
content = content.replace(f'ServerName 0.0.0.0:{port_80}', f'ServerName 0.0.0.0:{new_port_80}')
public.writeFile(main_config, content)
if os.path.exists(httpd_vhosts):
content = public.readFile(httpd_vhosts)
content = content.replace(f'Listen {port_888}', f'Listen {new_port_888}')
content = content.replace(f'*:{port_888}', f'*:{new_port_888}')
content = content.replace(f'*:{port_80}', f'*:{new_port_80}')
content = content.replace(f'[::]:{port_888}', f'[::]:{new_port_888}')
content = content.replace(f'[::]:{port_80}', f'[::]:{new_port_80}')
public.writeFile(httpd_vhosts, content)
if os.path.exists(httpd_ssl):
content = public.readFile(httpd_ssl)
content = content.replace(f'{port_443}', f'{new_port_443}')
public.writeFile(httpd_ssl, content)
return True, ' '
# 检测node项目多服务下默认走nginx
def check_node_project(site_name, is_ = 'enable'):
conf = os.path.join(public.get_panel_path(),'vhost', 'apache', f'node_{site_name}.conf')
# 使多服务下apache文件不生效
if is_ == 'enable':
if os.path.exists(conf):
shutil.move(conf, conf + '.barduo')
else:
if os.path.exists(conf + '.barduo'):
shutil.move(conf + '.barduo', conf )
return True
# 获取apache所有站点配置文件
def get_apache_site_conf():
pata =os.path.join(public.get_panel_path() , 'vhost' ,'apache')
if not os.path.exists(pata):
return []
conf_files = []
for entry in os.listdir(pata):
full_path = os.path.join(pata, entry)
if os.path.isfile(full_path) and entry.endswith('.conf'):
conf_files.append(full_path)
return conf_files
def multi_service_check_repair():
try:
# 尝试重新修改配置
ols_update_config('enable')
apache_update_config('enable')
# 重载服务
public.kill_process_strictly('litespeed', True) # 去残留
setup_path = public.get_setup_path()
services = [
(
f"{setup_path}/nginx/sbin/nginx",
"/etc/init.d/nginx reload",
"pkill -9 nginx && sleep 1 && /etc/init.d/nginx start"
),
(
f"{setup_path}/apache/bin/apachectl",
"/etc/init.d/httpd reload",
None
),
(
"/usr/local/lsws/bin/lswsctrl",
"/usr/local/lsws/bin/lswsctrl restart",
None
)
]
for path, cmd, err_cmd in services:
if os.path.exists(path):
result = public.ExecShell(cmd)
if "nginx" in path and result[1].find("nginx.pid") != -1:
public.ExecShell(err_cmd)
public.print_log("The modification of multiple service ports has been completed")
except Exception as e:
public.print_log(str(e))
if __name__ == '__main__':
multi_service_check_repair()

9
script/mysql_quota.py Normal file
View File

@@ -0,0 +1,9 @@
#!/www/server/panel/pyenv/bin/python
#coding: utf-8
import os,sys
os.chdir("/www/server/panel")
sys.path.insert(0,"class/")
sys.path.insert(0,"/www/server/panel/")
from projectModel.quotaModel import main
p = main()
p.mysql_quota_check()

View File

@@ -0,0 +1,58 @@
# coding: utf-8
import sys
import os
import traceback
import argparse
os.chdir('/www/server/panel/')
sys.path.insert(0, "/www/server/panel/class/")
sys.path.insert(0, "/www/server/panel/")
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
import public
parser = argparse.ArgumentParser()
parser.add_argument("--task_type", type=str, required=True, help="Task type")
parser.add_argument("--task_id", type=int, required=True, help="Task ID")
parser.add_argument("--exclude_nodes", type=str, help="Exclude nodes")
args = parser.parse_args()
task_type, task_id = args.task_type, args.task_id
ex_ids = []
if args.exclude_nodes:
ex_ids = [int(i) for i in args.exclude_nodes.split(",")]
pid_file = "{}/logs/executor_log/{}_{}_0.pid".format(public.get_panel_path(), task_type, task_id)
with open(pid_file, "w") as f:
f.write(str(os.getpid()))
def cmd_task(main_id: int, log_id: int = 0):
from mod.project.node.task_flow.command_task import CMDTask
_task = CMDTask(main_id, log_id, print)
_task.start()
def file_task(main_id: int, exclude_nodes: list, log_id: int = 0):
from mod.project.node.task_flow.file_task import SelfFiletransferTask
_task = SelfFiletransferTask(main_id, exclude_nodes, log_id)
_task.start()
def flow_task(main_id: int):
from mod.project.node.task_flow.flow import FlowTask
_task = FlowTask(main_id)
_task.start()
try:
if task_type == "command":
cmd_task(task_id)
elif task_type == "file":
file_task(task_id, ex_ids)
elif task_type == "flow":
flow_task(task_id)
except:
traceback.print_exc()
with open('/tmp/node_flow_task.log', 'w') as f:
f.write(traceback.format_exc())
os.remove(pid_file)

View File

@@ -0,0 +1,17 @@
# coding: utf-8
import sys
import os
import traceback
os.chdir('/www/server/panel/')
sys.path.insert(0, "/www/server/panel/class/")
sys.path.insert(0, "/www/server/panel/")
try:
from mod.project.node.filetransfer import run_file_transfer_task
run_file_transfer_task(int(sys.argv[1]))
except:
traceback.print_exc()
with open('/tmp/node_file_transfer.pl', 'w') as f:
f.write(traceback.format_exc())

20
script/node_monitor.py Normal file
View File

@@ -0,0 +1,20 @@
# coding: utf-8
import sys
import os
import traceback
import time
os.chdir('/www/server/panel/')
sys.path.insert(0, "/www/server/panel/class/")
sys.path.insert(0, "/www/server/panel/")
try:
from mod.project.node.nodeutil import monitor_all_node_status
monitor_all_node_status()
except Exception as e:
traceback.print_exc()
with open('/tmp/node_monitor.pl', 'w') as f:
f.write(str(int(time.time())))
f.write("{}".format(traceback.format_exc()))

35
script/nodejs-service.py Normal file
View File

@@ -0,0 +1,35 @@
#!/www/server/panel/pyenv/bin/python
#coding: utf-8
import os,sys
os.chdir("/www/server/panel")
sys.path.insert(0,"class/")
from projectModel.nodejsModel import main
import public
p = main()
if len(sys.argv) < 3:
print("Usage: nodejs-service [project_name] [start|stop|restart]")
sys.exit()
get = public.dict_obj()
get.project_name = sys.argv[1].strip()
action = sys.argv[2].strip()
if action not in ['start','stop','restart','status']:
print("Usage: nodejs-service [project_name] [start|stop|restart]")
sys.exit()
if action == 'start':
res = p.start_project(get)
elif action == 'stop':
res = p.stop_project(get)
elif action == 'restart':
res = p.restart_project(get)
elif action == 'status':
res = p.get(get)
if res['status']:
print("\033[1;32mSUCCESS: " + res['data'] + "\033[0m")
else:
print("\033[1;31mERROR: " + res['error_msg'] + "\033[0m")

View File

@@ -0,0 +1,43 @@
#!/bin/bash
configure_logging() {
sudo tee /etc/rsyslog.d/ip-daily-log.conf <<EOF
:msg, contains, "DAILY-IP: " -/var/log/IP-DAILY-LOG.log
& stop
EOF
sudo tee /etc/logrotate.d/ip-daily-log <<EOF
/var/log/IP-DAILY-LOG.log {
daily
rotate 3 # 仅保留3天历史
missingok
nocompress # 无需压缩
notifempty
sharedscripts
postrotate
systemctl reload rsyslog >/dev/null 2>&1
endscript
}
EOF
sudo systemctl restart rsyslog
}
sudo iptables -C IN_BT_log -j IN_BT_log_DAILY > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "rules 'iptables -I IN_BT_log -j IN_BT_log_DAILY' Exiting..."
exit 0
else
echo "add rule..."
sudo iptables -N IN_BT_log_DAILY
sudo iptables -I IN_BT_log -j IN_BT_log_DAILY
sudo iptables -A IN_BT_log_DAILY -m recent --name DAILY_IPS --rcheck --seconds 86400 -j RETURN
sudo iptables -A IN_BT_log_DAILY -m recent --name DAILY_IPS --set -j LOG --log-prefix "DAILY-IP: " --log-level 4
ipset create in_bt_malicious_ipset hash:ip maxelem 1000000 timeout 0;
iptables -A IN_BT_ip -m set --match-set in_bt_malicious_ipset src -j DROP
systemctl reload BT-FirewallServices
configure_logging
echo "add rule finish..."
fi

14
script/panel_ssl_task.py Normal file
View File

@@ -0,0 +1,14 @@
#coding: utf-8
import os,sys,time
os.chdir('/www/server/panel/')
sys.path.insert(0,"class/")
import setPanelLets,public
from json import loads
lets_info = public.readFile("/www/server/panel/ssl/lets.info")
if lets_info:
lets_info = loads(lets_info)
res = setPanelLets.setPanelLets().check_cert_update(lets_info['domain'])
if res['status'] and res['msg'] == '1':
strTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
public.writeFile("/tmp/panelSSL.pl","{} Panel certificate updated successfully\n".format(strTime),"a+")
public.writeFile('/www/server/panel/data/reload.pl',"1")

114
script/polkit_upgrade.py Normal file
View File

@@ -0,0 +1,114 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | YakPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@yakpanel.com>
# +-------------------------------------------------------------------
#--------------------------------
# 修复polkit提权漏洞(CVE-2021-4034)
#--------------------------------
import os,sys
os.chdir("/www/server/panel")
sys.path.insert(0,'class/')
import public
upgrade_log_file = '/www/server/panel/logs/upgrade_polkit.log'
log_msg = "A polkit (CVE-2021-4034) privilege escalation vulnerability has been detected in the system and has been fixed for you!"
def write_log(msg):
global upgrade_log_file
public.writeFile(upgrade_log_file,"[{}] - {}".format(public.format_date(),msg),'a+')
def is_yum():
if os.path.exists('/usr/bin/yum'):
return True
return False
def is_dnf():
if os.path.exists('/usr/bin/dnf'):
return True
return False
def is_apt():
if os.path.exists('/usr/bin/apt'):
return True
return False
def upgrade_by_yum():
global upgrade_log_file,log_msg
res = public.ExecShell("rpm -q polkit")[0]
if res.startswith('polkit-'):
os.system("yum -y update polkit &> {}".format(upgrade_log_file))
res2 = public.ExecShell("rpm -q polkit")[0]
if res == res2:
write_log("Repair failed, please execute the command manually: yum -y update polkit")
return False
public.WriteLog('Vulnerability Repair',log_msg)
return True
return False
def upgrade_by_dnf():
global upgrade_log_file,log_msg
res = public.ExecShell("rpm -q polkit")[0]
if res.startswith('polkit-'):
os.system("dnf -y update polkit &> {}".format(upgrade_log_file))
res2 = public.ExecShell("rpm -q polkit")[0]
if res == res2:
write_log("Repair failed, please execute the command manually: dnf -y update polkit")
return False
public.WriteLog('Vulnerability Repair',log_msg)
return True
return False
def upgrade_by_apt():
global upgrade_log_file,log_msg
res = public.ExecShell("dpkg -l policykit-1|grep policykit-1|awk '{print $3}'")[0]
if res.startswith('0.105'):
os.system("apt-get -y install policykit-1 &> {}".format(upgrade_log_file))
res2 = public.ExecShell("dpkg -l policykit-1|grep policykit-1|awk '{print $3}'")[0]
if res == res2:
write_log("Repair failed, please execute the command manually: apt-get -y install policykit-1")
return False
public.WriteLog('Vulnerability Repair',log_msg)
return True
return False
def check():
tip_file = '/www/server/panel/data/upgrade_polkit.pl'
if os.path.exists(tip_file):
return
write_log("Fixing the privilege escalation vulnerability of polkit (CVE-2021-4034)...")
if is_yum():
upgrade_by_yum()
elif is_dnf():
upgrade_by_dnf()
elif is_apt():
upgrade_by_apt()
else:
return
public.writeFile(tip_file,'True')
if __name__ == "__main__":
tip_file = '/www/server/panel/data/upgrade_polkit_run.pl'
if os.path.exists(tip_file):
print("The program is running, exit!")
sys.exit(1)
public.writeFile(tip_file,'True')
try:
check()
except:
pass
finally:
if os.path.exists(tip_file): os.remove(tip_file)

View File

@@ -0,0 +1,325 @@
#coding: utf-8
#-------------------------------------------------------------------
# YakPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang<hwl@yakpanel.com>
#-------------------------------------------------------------------
import sys
import time
import os
import struct
import base64
base64.b64encode
os.chdir('/www/server/panel')
if 'class/' in sys.path: sys.path.insert(0,"class/")
import copy
# try:
# import pcap
# except ImportError:
# # 标记只安装一次
# tip_file = '/www/server/panel/install/tip.json'
# if not os.path.exists(tip_file):
# if os.path.exists('/usr/bin/apt'):
# os.system("apt install libpcap-dev -y")
# elif os.path.exists('/usr/bin/dnf'):
# red_file = '/etc/redhat-release'
# if os.path.exists(red_file):
# f = open(red_file,'r')
# red_body = f.read()
# f.close()
# if red_body.find('CentOS Linux release 8.') != -1:
# rpm_file = '/root/libpcap-1.9.1.rpm'
# down_url = "wget -O {} https://node.yakpanel.com/src/libpcap-devel-1.9.1-5.el8.x86_64.rpm --no-check-certificate -T 10".format(rpm_file)
# if os.path.exists(rpm_file):
# os.system(down_url)
# os.system("rpm -ivh {}".format(rpm_file))
# if os.path.exists(rpm_file): os.remove(rpm_file)
# else:
# os.system("dnf install libpcap-devel -y")
# else:
# os.system("dnf install libpcap-devel -y")
# elif os.path.exists('/usr/bin/yum'):
# os.system("yum install libpcap-devel -y")
# # os.system("btpip install pypcap")
# # 写入标记文件
# os.system("echo True > {}".format(tip_file))
# # try:
# # import pcap
# # except ImportError:
# # print("3 pypcap module install failed. 3")
# # sys.exit()
class process_network_total:
__pid_file = 'logs/process_network_total.pid'
__inode_list = {}
__net_process_list = {}
__net_process_size = {}
__last_stat = 0
__last_write_time = 0
__end_time = 0
# def start(self,timeout = 0):
# '''
# @name 启动进程网络监控
# @author hwliang<2021-09-13>
# @param timeout<int> 结束时间(秒)0表示持久运行默认为0
# @return void
# '''
# stime = time.time()
# self.__end_time = timeout + stime
# self.__last_stat = stime
# try:
# p = pcap.pcap() # 监听所有网卡
# p.setfilter('tcp') # 只监听TCP数据包
# for p_time,p_data in p:
# self.handle_packet(p_data)
# # 过期停止监听
# if timeout > 0:
# if p_time > self.__end_time:
# self.rm_pid_file()
# break
# except:
# self.rm_pid_file()
def handle_packet(self, pcap_data):
'''
@name 处理pcap数据包
@author hwliang<2021-09-12>
@param pcap_data<bytes> pcap数据包
@return void
'''
# 获取IP协议头
ip_header = pcap_data[14:34]
# 解析src/dst地址
src_ip = ip_header[12:16]
dst_ip = ip_header[16:20]
# 解析sport/dport端口
src_port = pcap_data[34:36]
dst_port = pcap_data[36:38]
src = src_ip + b':' + src_port
dst = dst_ip + b':' + dst_port
# 计算数据包长度
pack_size = len(pcap_data)
# 统计进程流量
self.total_net_process(dst,src,pack_size)
def total_net_process(self,dst,src,pack_size):
'''
@name 统计进程流量
@author hwliang<2021-09-13>
@param dst<bytes> 目标地址
@param src<bytes> 源地址
@param pack_size<int> 数据包长度
@return void
'''
self.get_tcp_stat()
direction = None
mtime = time.time()
if dst in self.__net_process_list:
pid = self.__net_process_list[dst]
direction = 'down'
elif src in self.__net_process_list:
pid = self.__net_process_list[src]
direction = 'up'
else:
if mtime - self.__last_stat > 3:
self.__last_stat = mtime
self.get_tcp_stat(True)
if dst in self.__net_process_list:
pid = self.__net_process_list[dst]
direction = 'down'
elif src in self.__net_process_list:
pid = self.__net_process_list[src]
direction = 'up'
if not direction: return False
if not pid: return False
if not pid in self.__net_process_size:
self.__net_process_size[pid] = {}
self.__net_process_size[pid]['down'] = 0
self.__net_process_size[pid]['up'] = 0
self.__net_process_size[pid]['up_package'] = 0
self.__net_process_size[pid]['down_package'] = 0
self.__net_process_size[pid][direction] += pack_size
self.__net_process_size[pid][direction + '_package'] += 1
# 写入到文件
if mtime - self.__last_write_time > 1:
self.__last_write_time = mtime
self.write_net_process()
def write_net_process(self):
'''
@name 写入进程流量
@author hwliang<2021-09-13>
@return void
'''
w_file = '/dev/shm/bt_net_process'
process_size = copy.deepcopy(self.__net_process_size)
net_process = []
for pid in process_size.keys():
net_process.append(str(pid) + " " + str(process_size[pid]['down']) + " " + str(process_size[pid]['up']) + " " + str(process_size[pid]['down_package']) + " " + str(process_size[pid]['up_package']))
f = open(w_file,'w+',encoding='utf-8')
f.write('\n'.join(net_process))
f.close()
def hex_to_ip(self, hex_ip):
'''
@name 将16进制的IP地址转换为字符串IP地址
@author hwliang<2021-09-13>
@param hex_ip<string> 16进制的IP地址:16进程端口
@return tuple(ip<str>,port<int>) IP地址,端口
'''
hex_ip,hex_port = hex_ip.split(':')
ip = '.'.join([str(int(hex_ip[i:i+2], 16)) for i in range(0, len(hex_ip), 2)][::-1])
port = int(hex_port, 16)
return ip,port
def get_tcp_stat(self,force = False):
'''
@name 获取当前TCP连接状态表
@author hwliang<2021-09-13>
@param force<bool> 是否强制刷新
@return dict
'''
if not force and self.__net_process_list: return self.__net_process_list
self.__net_process_list = {}
tcp_stat_file = '/proc/net/tcp'
tcp_stat = open(tcp_stat_file, 'rb')
tcp_stat_list = tcp_stat.read().decode('utf-8').split('\n')
tcp_stat.close()
tcp_stat_list = tcp_stat_list[1:]
if force: self.get_process_inodes(force)
for i in tcp_stat_list:
tcp_tmp = i.split()
if len(tcp_tmp) < 10: continue
inode = tcp_tmp[9]
if inode == '0': continue
local_ip,local_port = self.hex_to_ip(tcp_tmp[1])
if local_ip == '127.0.0.1': continue
remote_ip,remote_port = self.hex_to_ip(tcp_tmp[2])
if local_ip == remote_ip: continue
if remote_ip == '0.0.0.0': continue
pid = self.inode_to_pid(inode,force)
if not pid: continue
key = self.get_ip_pack(local_ip) + b':' + self.get_port_pack(local_port)
self.__net_process_list[key] = pid
return self.__net_process_list
def get_port_pack(self,port):
'''
@name 将端口转换为字节流
@author hwliang<2021-09-13>
@param port<int> 端口
@return bytes
'''
return struct.pack('H',int(port))[::-1]
def get_ip_pack(self,ip):
'''
@name 将IP地址转换为字节流
@author hwliang<2021-09-13>
@param ip<str> IP地址
@return bytes
'''
ip_arr = ip.split('.')
ip_pack = b''
for i in ip_arr:
ip_pack += struct.pack('B',int(i))
return ip_pack
def inode_to_pid(self,inode,force = False):
'''
@name 将inode转换为进程ID
@author hwliang<2021-09-13>
@param inode<string> inode
@param force<bool> 是否强制刷新
@return int
'''
inode_list = self.get_process_inodes()
if inode in inode_list:
return inode_list[inode]
return None
def get_process_inodes(self,force = False):
'''
@name 获取进程inode列表
@author hwliang<2021-09-13>
@param force<bool> 是否强制刷新
@return dict
'''
if not force and self.__inode_list: return self.__inode_list
proc_path = '/proc'
inode_list = {}
for pid in os.listdir(proc_path):
try:
if not pid.isdigit(): continue
inode_path = proc_path + '/' + pid + '/fd'
for fd in os.listdir(inode_path):
try:
fd_file = inode_path + '/' + fd
fd_link = os.readlink(fd_file)
if fd_link.startswith('socket:['):
inode = fd_link[8:-1]
inode_list[inode] = pid
except:
continue
except:
continue
self.__inode_list = inode_list
return inode_list
def get_process_name(self,pid):
'''
@name 获取进程名称
@author hwliang<2021-09-13>
@param pid<str> 进程ID
@return str
'''
pid_path = '/proc/' + pid + '/comm'
if not os.path.exists(pid_path): return ''
pid_file = open(pid_path, 'rb')
pid_name = pid_file.read().decode('utf-8').strip()
pid_file.close()
return pid_name
def write_pid(self):
'''
@name 写入进程ID到PID文件
@author hwliang<2021-09-13>
@return void
'''
self_pid = os.getpid()
pid_file = open(self.__pid_file,'w')
pid_file.write(str(self_pid))
pid_file.close()
def rm_pid_file(self):
'''
@name 删除进程pid文件
@author hwliang<2021-09-13>
@return void
'''
if os.path.exists(self.__pid_file):
os.remove(self.__pid_file)
if __name__ == '__main__':
if len(sys.argv) > 1:
timeout = int(sys.argv[-1])
else:
timeout = 0
p = process_network_total()
p.write_pid()
# p.start(timeout)

82
script/project_daemon.py Normal file
View File

@@ -0,0 +1,82 @@
import json
import os
import sys
panel_path = '/www/server/panel'
os.chdir(panel_path)
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
if "/www/server/panel" not in sys.path:
sys.path.insert(0, "/www/server/panel")
import public
def PythonDaemons():
"""
@name Python 项目守护进程
@author baozi@yakpanel.com
@time 2023-10-21
"""
if public.M('sites').where('project_type=?', ('Python',)).count() >= 1:
project_info = public.M('sites').where('project_type=?', ('Python',)).select()
for i in project_info:
try:
from projectModelV2 import pythonModel
# sites 表中的 project_config
i['project_config'] = json.loads(i['project_config'])
# auto_run, 自启,守护
if 'auto_run' in i['project_config'] and i['project_config']['auto_run'] in ["1", 1, True, "true"]:
python_obj = pythonModel.main()
# 项目的运行状态是否为运行中
if python_obj.get_project_run_state(project_name=i['name']):
continue
# 项目是否被用户停止了, 则不再自动启动
if python_obj.is_stop_by_user(i["id"]):
continue
get = public.dict_obj()
get.name = i['name']
# StartProject
python_obj.StartProject(get)
public.WriteLog('Project Daemon', 'Python project [{}] auto start!'.format(i['name']))
except Exception:
import traceback
print("Python Daemon Error: {}".format(traceback.format_exc()))
continue
def NodeDaemons():
if public.M('sites').where('project_type=?', ('Node',)).count() >= 1:
project_info = public.M('sites').where('project_type=?', ('Node',)).select()
from projectModelV2 import nodejsModel
for i in project_info:
try:
i['project_config'] = json.loads(i['project_config'])
if 'is_power_on' in i['project_config'] and i['project_config']['is_power_on'] in ["1", 1, True, "true"]: # node 默认为is_power_on
node_obj = nodejsModel.main()
get = public.dict_obj()
get.project_name = i['name']
if node_obj._get_project_run_state(get):
continue
# StartProject
node_obj.start_project(get)
public.WriteLog('Project Daemon', 'Node.js project [{}] auto start!'.format(i['name']))
except Exception:
import traceback
print("Node.js Daemon Error: {}".format(traceback.format_exc()))
continue
def GoDaemons():
pass
def main():
# go, java, python, nodejs...
GoDaemons()
NodeDaemons()
PythonDaemons()
if __name__ == '__main__':
main()

43
script/push_msg.py Normal file
View File

@@ -0,0 +1,43 @@
# coding: utf-8
import json
import sys, os, time
os.chdir('/www/server/panel/')
sys.path.insert(0, "class/")
sys.path.insert(0, "/www/server/panel/")
import public
import http_requests
http_requests.DEFAULT_TYPE = 'src'
os.environ['BT_TASK'] = '1'
def main():
try:
import panelPush
push = panelPush.panelPush()
push.start()
# msg bind check
flag = False
sender = os.path.join(public.get_panel_path(), "data/mod_push_data/sender.json")
if os.path.exists(sender):
sender_info = public.readFile(sender)
try:
sender_info = json.loads(sender_info)
except:
pass
if sender_info and isinstance(sender_info, list):
for send in sender_info:
if send.get("data") != {}:
flag = True # has bind alarm
break
if flag:
from mod.base.push_mod import PushSystem
PushSystem().run()
except Exception as e:
pass
os.system("echo no,{},{} > /tmp/push.pl".format(time.time(), e))
if __name__ == "__main__":
main()

40
script/py_project_env.py Normal file
View File

@@ -0,0 +1,40 @@
#!/www/sererv/panel/penv/bin/python3
# coding: utf-8
# -----------------------------
# yakpanel Python项目准备脚本
# -----------------------------
import json
import sys
import os
os.chdir("/www/server/panel")
if "/www/server/panel" not in sys.path:
sys.path.insert(0, "/www/server/panel")
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
if "/www/server/panel/class_v2" not in sys.path:
sys.path.insert(0, "/www/server/panel/class_v2")
import public
from projectModelV2.pythonModel import main as pythonMod
def main(pj_id: int):
project_info = public.M('sites').where('id=? ', (pj_id,)).find()
if not isinstance(project_info, dict):
print("project not found!")
values = json.loads(project_info["project_config"])
pythonMod().simple_prep_env(values)
if __name__ == '__main__':
if len(sys.argv) > 1:
project_id = sys.argv[1]
try:
project_id = int(project_id)
except:
exit(1)
else:
main(project_id)

Binary file not shown.

Binary file not shown.

100
script/rememory.sh Normal file
View File

@@ -0,0 +1,100 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#+------------------------------------
#+ 宝塔释放内存脚本
#+------------------------------------
endDate=`date +"%Y-%m-%d %H:%M:%S"`
log="Free up memory!"
echo "★[$endDate] $log"
echo '----------------------------------------------------------------------------'
if [ -f "/etc/init.d/php-fpm-52" ];then
/etc/init.d/php-fpm-52 reload
fi
if [ -f "/etc/init.d/php-fpm-53" ];then
/etc/init.d/php-fpm-53 reload
fi
if [ -f "/etc/init.d/php-fpm-54" ];then
/etc/init.d/php-fpm-54 reload
fi
if [ -f "/etc/init.d/php-fpm-55" ];then
/etc/init.d/php-fpm-55 reload
fi
if [ -f "/etc/init.d/php-fpm-56" ];then
/etc/init.d/php-fpm-56 reload
fi
if [ -f "/etc/init.d/php-fpm-70" ];then
/etc/init.d/php-fpm-70 reload
fi
if [ -f "/etc/init.d/php-fpm-71" ];then
/etc/init.d/php-fpm-71 reload
fi
if [ -f "/etc/init.d/php-fpm-72" ];then
/etc/init.d/php-fpm-72 reload
fi
if [ -f "/etc/init.d/php-fpm-73" ];then
/etc/init.d/php-fpm-73 reload
fi
if [ -f "/etc/init.d/php-fpm-74" ];then
/etc/init.d/php-fpm-74 reload
fi
if [ -f "/etc/init.d/php-fpm-80" ];then
/etc/init.d/php-fpm-80 reload
fi
if [ -f "/etc/init.d/php-fpm-81" ];then
/etc/init.d/php-fpm-81 reload
fi
if [ -f "/etc/init.d/php-fpm-82" ];then
/etc/init.d/php-fpm-82 reload
fi
if [ -f "/etc/init.d/php-fpm-83" ];then
/etc/init.d/php-fpm-83 reload
fi
if [ -f "/etc/init.d/php-fpm-84" ];then
/etc/init.d/php-fpm-84 reload
fi
if [ -f "/etc/init.d/php-fpm-85" ];then
/etc/init.d/php-fpm-85 reload
fi
if [ -f "/etc/init.d/mysqld" ];then
/etc/init.d/mysqld reload
fi
if [ -f "/etc/init.d/nginx" ];then
/etc/init.d/nginx reload
fi
if [ -f "/etc/init.d/httpd" ];then
/etc/init.d/httpd graceful
fi
if [ -f "/etc/init.d/pure-ftpd" ];then
pkill -9 pure-ftpd
sleep 0.3
/etc/init.d/pure-ftpd start 2>/dev/null
fi
sync
sleep 2
sync
echo 3 > /proc/sys/vm/drop_caches
echo '----------------------------------------------------------------------------'

63
script/restart_project.py Normal file
View File

@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
# -----------------------------
# Website Project Restart Script
# -----------------------------
# author: YakPanel
import os
import sys
from importlib import import_module
from typing import Optional, Any
if "/www/server/panel" not in sys.path:
sys.path.insert(0, '/www/server/panel')
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, '/www/server/panel/class')
if "/www/server/panel/class_v2" not in sys.path:
sys.path.insert(0, '/www/server/panel/class_v2')
os.chdir('/www/server/panel')
import public
def get_action_model_obj(model_name: str) -> Optional[Any]:
try:
if model_name in "java" and os.path.exists("/www/server/panel/mod/project/java/projectMod.py"):
model = import_module("mod.project.java.projectMod")
else:
model = import_module("projectModelV2." + model_name + "Model")
except:
return None
if not hasattr(model, "main"):
return None
main_class = getattr(model, "main")
if not callable(main_class):
return None
return main_class()
def restart_project_based_on_model(model_name: str, project_name: str):
try:
print(public.lang(f"Starting to restart {model_name} project [{project_name}]..."))
model_obj = get_action_model_obj(model_name)
if not model_obj:
print(public.lang(f"Failed to load operation class for model {model_name}."))
return
res = model_obj.restart_project(public.to_dict_obj({
"project_name": project_name
}))
if res['status'] != 0:
print(public.lang(f"Failed to restart project [{project_name}]. {res.get('msg', '')}"))
return
print(public.lang("Project [{}] Restarted Successfully!").format(project_name))
except Exception as e:
print(public.get_error_info())
print("Error: " + str(e))
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python restart_project.py <model_name> <project_name>")
restart_project_based_on_model(sys.argv[1], sys.argv[2])

816
script/restart_services.py Normal file
View File

@@ -0,0 +1,816 @@
# coding: utf-8
import json
import os
import socket
import subprocess
import sys
import time
from functools import wraps, partial
from typing import Optional, Dict, Callable
import fcntl
os.chdir("/www/server/panel")
sys.path.insert(0, "class/")
sys.path.insert(0, "class_v2/")
SETUP_PATH = "/www/server"
DATA_PATH = os.path.join(SETUP_PATH, "panel/data")
PLUGINS_PATH = os.path.join(SETUP_PATH, "panel/plugin")
DAEMON_SERVICE = os.path.join(DATA_PATH, "daemon_service.pl")
DAEMON_SERVICE_LOCK = os.path.join(DATA_PATH, "daemon_service_lock.pl")
DAEMON_RESTART_RECORD = os.path.join(DATA_PATH, "daemon_restart_record.pl")
MANUAL_FLAG = os.path.join(SETUP_PATH, "panel/data/mod_push_data", "manual_flag.pl")
def read_file(filename: str):
fp = None
try:
fp = open(filename, "rb")
f_body_bytes: bytes = fp.read()
f_body = f_body_bytes.decode("utf-8", errors='ignore')
fp.close()
return f_body
except Exception:
return False
finally:
if fp and not fp.closed:
fp.close()
def run_command(cmd, timeout=5) -> str:
try:
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=timeout,
check=False
)
output = result.stdout.strip()
return output if output else ""
except subprocess.TimeoutExpired as t:
write_logs(f"Command execution timed out: {cmd}, error: {t}")
return ""
except Exception as e:
write_logs(f"Command execution failed: {cmd}, error: {e}")
return ""
def service_shop_name(services_name: str) -> str:
# 对应商店名字
shop_name = {
"postfix": "mail_sys",
"pgsql": "pgsql_manager",
"pure-ftpd": "pureftpd",
"php-fpm-52": "php-5.2",
"php-fpm-53": "php-5.3",
"php-fpm-54": "php-5.4",
"php-fpm-55": "php-5.5",
"php-fpm-56": "php-5.6",
"php-fpm-70": "php-7.0",
"php-fpm-71": "php-7.1",
"php-fpm-72": "php-7.2",
"php-fpm-73": "php-7.3",
"php-fpm-74": "php-7.4",
"php-fpm-80": "php-8.0",
"php-fpm-81": "php-8.1",
"php-fpm-82": "php-8.2",
"php-fpm-83": "php-8.3",
"php-fpm-84": "php-8.4",
"php-fpm-85": "php-8.5",
}
return shop_name.get(services_name, services_name)
def pretty_title(services_name: str) -> str:
title = {
"apache": "Apache",
"nginx": "Nginx",
"openlitespeed": "OpenLiteSpeed",
"redis": "Redis",
"mysql": "MySQL/MariaDB",
"mongodb": "MongoDB",
"pgsql": "PostgreSQL",
"pure-ftpd": "Pure-FTPd",
"memcached": "Memcached",
"ssh": "SSH",
"postfix": "Postfix",
"pdns": "PowerDNS",
# PHP-FPM
"php-fpm-52": "PHP 5.2 FPM",
"php-fpm-53": "PHP 5.3 FPM",
"php-fpm-54": "PHP 5.4 FPM",
"php-fpm-55": "PHP 5.5 FPM",
"php-fpm-56": "PHP 5.6 FPM",
"php-fpm-70": "PHP 7.0 FPM",
"php-fpm-71": "PHP 7.1 FPM",
"php-fpm-72": "PHP 7.2 FPM",
"php-fpm-73": "PHP 7.3 FPM",
"php-fpm-74": "PHP 7.4 FPM",
"php-fpm-80": "PHP 8.0 FPM",
"php-fpm-81": "PHP 8.1 FPM",
"php-fpm-82": "PHP 8.2 FPM",
"php-fpm-83": "PHP 8.3 FPM",
"php-fpm-84": "PHP 8.4 FPM",
"php-fpm-85": "PHP 8.5 FPM",
# plugins
"btwaf": "YakPanel WAF",
"fail2ban": "Fail2Ban",
}
return title.get(services_name, services_name)
# =======================================================
def ssh_ver() -> str:
version_info = run_command(["ssh", "-V"])
if version_info and "OpenSSH" in version_info:
return version_info.split(",")[0]
return ""
def postfix_ver() -> str:
# mail_version = x.x.x
output = run_command(["/usr/sbin/postconf", "mail_version"])
if output and "=" in output:
return output.split("=")[-1].strip()
return ""
def pgsql_pid() -> str:
pid_str = run_command(["pgrep", "-o", "postgres"])
if pid_str and pid_str.isdigit():
return pid_str
return ""
def pgsql_ver() -> str:
# psql (PostgreSQL) 18.0
output = run_command([f"{SETUP_PATH}/pgsql/bin/psql", "--version"])
if output:
from re import search
match = search(r"(\d+\.\d+(\.\d+)?)", output)
if match:
return match.group(1)
org_file = f"{SETUP_PATH}/pgsql/data/PG_VERSION",
if os.path.exists(str(org_file)):
try:
with open(str(org_file), "r") as f:
version = f.read().strip()
return version
except:
return ""
return ""
def pdns_pid() -> str:
pid_str = run_command(["pgrep", "-x", "pdns_server"])
if pid_str and pid_str.isdigit():
return pid_str
return ""
def pdns_ver() -> str:
# PowerDNS Authoritative Server x.x.x
output = run_command(["pdns_server", "--version"])
if not output:
return ""
from re import search
match = search(r"(\d+\.\d+\.\d+)", output)
if match:
return match.group(1)
if "PowerDNS" in output:
return output.split()[-1]
return ""
def waf_pid() -> str:
pid_str = run_command(["pgrep", "-x", "BT-WAF"])
if pid_str and pid_str.isdigit():
return pid_str
return ""
def pluign_ver(plugin_name: str) -> str:
info = f"{PLUGINS_PATH}/{plugin_name}/info.json"
if not os.path.exists(info):
return ""
try:
with open(info, "r") as f:
json_data = json.load(f)
ret = json_data.get("versions", "")
return ret
except:
pass
return ""
SERVICES_MAP = {
# ========================= core base =============================
"panel": (
"Yak-Panel", f"{SETUP_PATH}/panel/logs/panel.pid", f"{SETUP_PATH}/panel/init.sh",
f"{SETUP_PATH}/nginx/version.pl",
),
"apache": (
"httpd", f"{SETUP_PATH}/apache/logs/httpd.pid", "/etc/init.d/httpd",
f"{SETUP_PATH}/apache/version.pl",
),
"nginx": (
"nginx", f"{SETUP_PATH}/nginx/logs/nginx.pid", "/etc/init.d/nginx",
f"{SETUP_PATH}/nginx/version.pl",
),
"openlitespeed": (
"litespeed", "/tmp/lshttpd/lshttpd.pid", "/usr/local/lsws/bin/lswsctrl",
"/usr/local/lsws/VERSION",
),
"redis": (
"redis-server", f"{SETUP_PATH}/redis/redis.pid", "/etc/init.d/redis",
f"{SETUP_PATH}/redis/version.pl",
),
"mysql": (
"mysqld", "/tmp/mysql.sock", "/etc/init.d/mysqld",
f"{SETUP_PATH}/mysql/version.pl",
),
"mongodb": (
"mongod", f"{SETUP_PATH}/mongodb/log/configsvr.pid", "/etc/init.d/mongodb",
f"{SETUP_PATH}/mongodb/version.pl",
),
"pgsql": (
"postgres", pgsql_pid, "/etc/init.d/pgsql",
pgsql_ver,
),
"pure-ftpd": (
"pure-ftpd", "/var/run/pure-ftpd.pid", "/etc/init.d/pure-ftpd",
f"{SETUP_PATH}/pure-ftpd/version.pl",
),
"memcached": (
"memcached", "/var/run/memcached.pid", "/etc/init.d/memcached",
"/usr/local/memcached/version_check.pl",
),
"ssh": (
"sshd", "/var/run/sshd.pid", "/etc/init.d/ssh",
ssh_ver,
),
"postfix": (
"master", "/var/spool/postfix/pid/master.pid", "/etc/init.d/postfix",
postfix_ver,
),
"pdns": (
# "pdns_server", pdns_pid, "/usr/sbin/pdns_server",
"pdns_server", pdns_pid, "/www/server/panel/class_v2/ssl_dnsV2/aadns.pl",
pdns_ver,
),
# ======================== PHP-FPM ============================
"php-fpm-52": ("php-fpm", f"{SETUP_PATH}/php/52/var/run/php-fpm.pid", "/etc/init.d/php-fpm-52",
f"{SETUP_PATH}/php/52/version.pl"),
"php-fpm-53": ("php-fpm", f"{SETUP_PATH}/php/53/var/run/php-fpm.pid", "/etc/init.d/php-fpm-53",
f"{SETUP_PATH}/php/53/version.pl"),
"php-fpm-54": ("php-fpm", f"{SETUP_PATH}/php/54/var/run/php-fpm.pid", "/etc/init.d/php-fpm-54",
f"{SETUP_PATH}/php/54/version.pl"),
"php-fpm-55": ("php-fpm", f"{SETUP_PATH}/php/55/var/run/php-fpm.pid", "/etc/init.d/php-fpm-55",
f"{SETUP_PATH}/php/55/version.pl"),
"php-fpm-56": ("php-fpm", f"{SETUP_PATH}/php/56/var/run/php-fpm.pid", "/etc/init.d/php-fpm-56",
f"{SETUP_PATH}/php/56/version.pl"),
"php-fpm-70": ("php-fpm", f"{SETUP_PATH}/php/70/var/run/php-fpm.pid", "/etc/init.d/php-fpm-70",
f"{SETUP_PATH}/php/70/version.pl"),
"php-fpm-71": ("php-fpm", f"{SETUP_PATH}/php/71/var/run/php-fpm.pid", "/etc/init.d/php-fpm-71",
f"{SETUP_PATH}/php/71/version.pl"),
"php-fpm-72": ("php-fpm", f"{SETUP_PATH}/php/72/var/run/php-fpm.pid", "/etc/init.d/php-fpm-72",
f"{SETUP_PATH}/php/72/version.pl"),
"php-fpm-73": ("php-fpm", f"{SETUP_PATH}/php/73/var/run/php-fpm.pid", "/etc/init.d/php-fpm-73",
f"{SETUP_PATH}/php/73/version.pl"),
"php-fpm-74": ("php-fpm", f"{SETUP_PATH}/php/74/var/run/php-fpm.pid", "/etc/init.d/php-fpm-74",
f"{SETUP_PATH}/php/74/version.pl"),
"php-fpm-80": ("php-fpm", f"{SETUP_PATH}/php/80/var/run/php-fpm.pid", "/etc/init.d/php-fpm-80",
f"{SETUP_PATH}/php/80/version.pl"),
"php-fpm-81": ("php-fpm", f"{SETUP_PATH}/php/81/var/run/php-fpm.pid", "/etc/init.d/php-fpm-81",
f"{SETUP_PATH}/php/81/version.pl"),
"php-fpm-82": ("php-fpm", f"{SETUP_PATH}/php/82/var/run/php-fpm.pid", "/etc/init.d/php-fpm-82",
f"{SETUP_PATH}/php/82/version.pl"),
"php-fpm-83": ("php-fpm", f"{SETUP_PATH}/php/83/var/run/php-fpm.pid", "/etc/init.d/php-fpm-83",
f"{SETUP_PATH}/php/83/version.pl"),
"php-fpm-84": ("php-fpm", f"{SETUP_PATH}/php/84/var/run/php-fpm.pid", "/etc/init.d/php-fpm-84",
f"{SETUP_PATH}/php/84/version_check.pl"),
"php-fpm-85": ("php-fpm", f"{SETUP_PATH}/php/85/var/run/php-fpm.pid", "/etc/init.d/php-fpm-85",
f"{SETUP_PATH}/php/85/version_check.pl"),
# ======================== plugins ============================
# nginx : btwaf
# apache: btwaf_httpd
"btwaf": (
# "BT-WAF", f"{PLUGINS_PATH}/btwaf/BT-WAF.pid", f"/etc/init.d/btwaf",
"BT-WAF", waf_pid, f"/etc/init.d/btwaf",
partial(pluign_ver, "btwaf"),
),
"fail2ban": (
"fail2ban-server", f"{PLUGINS_PATH}/fail2ban/fail2ban.pid", "/etc/init.d/fail2ban",
partial(pluign_ver, "fail2ban"),
),
}
# 日志
def write_logs(msg: str, logger_name: str = "Service Daemon"):
try:
from public import M
t = time.strftime('%Y-%m-%d %X', time.localtime())
M("logs").add("uid,username,type,log,addtime", (1, "system", logger_name, msg, t))
except:
pass
# 手动干预
def manual_flag(server_name: str = None, open_: str = None) -> Optional[dict]:
if not server_name: # only read
return DaemonManager.manual_safe_read()
# 人为干预
if open_ in ["start", "restart"]: # 激活服务检查
return DaemonManager.active_daemon(server_name)
elif open_ == "stop": # 跳过服务检查
return DaemonManager.skip_daemon(server_name)
return DaemonManager.manual_safe_read()
# 管理服务助手
class ServicesHelper:
def __init__(self, nick_name: str = None):
self.nick_name = nick_name
self._serviced = None
self._pid_source = None
self._bash = None
self._ver_source = None
self._pid_cache = None
self._ver_cache = None
self._install_cache = None
self._info_inited = False
def __check_pid_process(self, pid: int) -> bool:
try:
# 是否活跃
with open(f"/proc/{pid}/stat", "r") as f:
if f.read().split()[2] == "Z":
return False # 僵尸进程
# 进程是否名字匹配
with open(f"/proc/{pid}/comm", "r") as f:
proc_name = f.read().strip()
# 特殊处理 mysql
if self.nick_name != "mysql":
return proc_name == self._serviced or proc_name == self.nick_name
else:
return proc_name in ["mysqld", "mariadbd"]
except (FileNotFoundError, IndexError):
return False
except Exception:
return False
def _init_info(self) -> None:
if self._info_inited:
return
if not self.nick_name or not isinstance(self.nick_name, str):
self._info_inited = True
return
map_info = SERVICES_MAP.get(self.nick_name)
if map_info:
self._serviced, self._pid_source, self._bash, self._ver_source = map_info
self._info_inited = True
@property
def pid(self):
"""返回pid文件路径或pid值"""
if self._pid_cache is None:
self._init_info()
if isinstance(self._pid_source, Callable):
try:
pid_val = self._pid_source()
self._pid_cache = int(pid_val) if pid_val and pid_val.isdigit() else pid_val
except Exception:
self._pid_cache = self._pid_source
else:
self._pid_cache = self._pid_source
return self._pid_cache
@property
def version(self) -> str:
"""返回服务版本号"""
if self._ver_cache is None:
self._init_info()
if isinstance(self._ver_source, Callable):
try:
self._ver_cache = self._ver_source()
except:
self._ver_cache = ""
elif isinstance(self._ver_source, str) and os.path.exists(self._ver_source):
try:
with open(self._ver_source, "r") as f:
self._ver_cache = f.read().strip()
except:
self._ver_cache = ""
else:
self._ver_cache = ""
return str(self._ver_cache).strip()
@property
def is_install(self) -> bool:
"""判断是否安装"""
if self._install_cache is not None:
return self._install_cache
self._init_info()
self._install_cache = False
# waf特殊处理
if self.nick_name == "btwaf":
if os.path.exists(f"{PLUGINS_PATH}/btwaf"):
self._install_cache = True
return self._install_cache
# postfix特殊处理
if self.nick_name == "postfix":
if os.path.exists(f"{PLUGINS_PATH}/mail_sys"):
self._install_cache = True
return self._install_cache
if any([
self._serviced and os.path.exists(f"/etc/init.d/{self._serviced}"),
self.nick_name and os.path.exists(f"/etc/init.d/{self.nick_name}"),
self._bash and os.path.exists(self._bash),
]):
self._install_cache = True
return self._install_cache
@property
def shop_name(self) -> str:
return service_shop_name(self.nick_name)
@property
def pretty_title(self) -> str:
return pretty_title(self.nick_name)
@property
def is_running(self) -> bool:
"""
判断进程是否存活
pid: 进程pid文件 str 或 int
serviced: 进程服务名称
nick_name: 进程别名
"""
if not self.is_install:
return False
if not self.pid:
return False
if isinstance(self.pid, int):
return self.__check_pid_process(self.pid)
if isinstance(self.pid, str):
if not os.path.exists(self.pid):
return False
if self.pid.endswith(".pid"):
try:
with open(self.pid, "r") as f:
temp_pid = int(f.read().strip())
return self.__check_pid_process(temp_pid)
except (ValueError, FileNotFoundError):
return False
except Exception:
return False
elif self.pid.endswith(".sock"):
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.settimeout(0.1)
s.connect(self.pid)
return True
except (socket.timeout, ConnectionRefusedError, FileNotFoundError):
return False
except Exception:
return False
# not str and not int
return False
def script(self, act: str, logger_name: str = "Service Daemon") -> None:
if not self.is_install or act not in ["start", "stop", "restart", "status"]:
return
try:
# "try to {act} [{self.nick_name}]..."
self._init_info()
bash_path = self._bash if self._bash else f"/etc/init.d/{self._serviced}"
# if isinstance(self.pid, str) and (
# self.pid.endswith(".sock") or self.pid.endswith(".pid")
# ):
# try:
# os.remove(self.pid)
# except:
# pass
if self.nick_name == "panel":
if not os.path.exists(f"{SETUP_PATH}/panel/init.sh"):
from public import get_url
os.system(f"curl -k {get_url()}/install/update_7.x_en.sh|bash &")
cmd = ["bash", bash_path, act]
elif self.nick_name == "pdns":
cmd = ["systemctl", act, "pdns"]
else:
cmd = [bash_path, act]
subprocess.Popen(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True
)
write_logs(f"Service [ {self.nick_name} ] {act}", logger_name)
except Exception as e:
print(str(e))
write_logs(f"Failed to {act} {self.nick_name}, error: {e}", logger_name)
# 守护服务管理
class DaemonManager:
_ensured = False
@classmethod
def __ensure(cls):
if cls._ensured:
return
if not os.path.exists(DAEMON_SERVICE_LOCK):
with open(DAEMON_SERVICE_LOCK, "w") as _:
pass
if not os.path.exists(DAEMON_RESTART_RECORD):
with open(DAEMON_RESTART_RECORD, "w") as fm:
fm.write(json.dumps({}))
os.makedirs(os.path.dirname(MANUAL_FLAG), exist_ok=True)
if not os.path.exists(MANUAL_FLAG):
with open(MANUAL_FLAG, "w") as fm:
fm.write(json.dumps({}))
if not os.path.exists(DAEMON_SERVICE):
with open(DAEMON_SERVICE, "w") as fm:
fm.write(json.dumps([]))
cls._ensured = True
@staticmethod
def read_lock(func):
@wraps(func)
def wrapper(*args, **kwargs):
DaemonManager.__ensure()
with open(DAEMON_SERVICE_LOCK, "r") as lock_file:
fcntl.flock(lock_file, fcntl.LOCK_SH)
try:
return func(*args, **kwargs)
finally:
fcntl.flock(lock_file, fcntl.LOCK_UN)
return wrapper
@staticmethod
def write_lock(func):
@wraps(func)
def wrapper(*args, **kwargs):
DaemonManager.__ensure()
with open(DAEMON_SERVICE_LOCK, "r+") as lock_file:
fcntl.flock(lock_file, fcntl.LOCK_EX)
try:
return func(*args, **kwargs)
finally:
fcntl.flock(lock_file, fcntl.LOCK_UN)
return wrapper
@staticmethod
@write_lock
def operate_daemon(service_name: str, flag: int = 0) -> list:
"""
flag: 0 add, 1 del
"""
with open(DAEMON_SERVICE, "r+") as f:
try:
service = json.load(f)
except json.JSONDecodeError:
service = []
if flag == 0:
service.append(service_name)
elif flag == 1:
service = [x for x in service if x != service_name]
service = list(set(service))
f.seek(0)
# noinspection PyTypeChecker
json.dump(service, f)
f.truncate()
return service
@staticmethod
@write_lock
def operate_manual_flag(service_name: str, flag: int = 0) -> dict:
"""
flag: 0 normal, 1 manual closed
"""
with open(MANUAL_FLAG, "r+") as f:
try:
service = json.load(f)
except json.JSONDecodeError:
service = {}
service[service_name] = flag
f.seek(0)
# noinspection PyTypeChecker
json.dump(service, f)
f.truncate()
return service
@staticmethod
def remove_daemon(service_name: str) -> list:
"""移除守护进程服务"""
return DaemonManager.operate_daemon(service_name, 1)
@staticmethod
def add_daemon(service_name: str) -> list:
"""添加守护进程服务"""
return DaemonManager.operate_daemon(service_name, 0)
@staticmethod
def skip_daemon(service_name: str) -> dict:
"""跳过服务检查"""
return DaemonManager.operate_manual_flag(service_name, 1)
@staticmethod
def active_daemon(service_name: str) -> dict:
"""激活服务检查"""
return DaemonManager.operate_manual_flag(service_name, 0)
@staticmethod
@read_lock
def safe_read():
"""服务守护进程服务列表"""
try:
res = read_file(DAEMON_SERVICE)
return json.loads(res) if res else []
except:
return []
@staticmethod
@read_lock
def manual_safe_read():
"""手动干预服务字典, 0: 需要干预, 1: 被手动关闭的"""
try:
manual = read_file(MANUAL_FLAG)
return json.loads(manual) if manual else {}
except:
return {}
@staticmethod
def update_restart_record(service_name: str, max_count: int) -> bool:
try:
with open(DAEMON_RESTART_RECORD, "r+") as f:
fcntl.flock(f, fcntl.LOCK_EX)
try:
record = json.load(f)
except json.JSONDecodeError:
record = {}
count = record.get(service_name, 0)
if count >= max_count:
return True
record[service_name] = count + 1
f.seek(0)
json.dump(record, f)
f.truncate()
return False
except Exception:
return True
@staticmethod
def safe_read_restart_record() -> Dict[str, int]:
try:
with open(DAEMON_RESTART_RECORD, "r") as f:
fcntl.flock(f, fcntl.LOCK_SH)
record = json.load(f)
except Exception:
record = {}
return record
# 服务守护
class RestartServices:
COUNT = 30
@staticmethod
def __keep_flag_right(manual_info: dict) -> None:
try:
with open(MANUAL_FLAG, "r+") as f:
try:
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
f.seek(0)
# noinspection PyTypeChecker
json.dump(manual_info, f)
f.truncate()
except:
print("Error writing manual flag file")
finally:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
except Exception as e:
print("Error keep_flag_right:", e)
def _overhead(self, nick_name) -> bool:
return DaemonManager.update_restart_record(
nick_name, self.COUNT
)
@DaemonManager.read_lock
def main(self):
manaul = read_file(MANUAL_FLAG)
services = read_file(DAEMON_SERVICE)
try:
manual_info = json.loads(manaul) if manaul else {}
check_list = json.loads(services) if services else []
check_list = ["panel"] + check_list # panel 强制守护
except Exception:
manual_info = {}
check_list = ["panel"]
record = DaemonManager.safe_read_restart_record()
for service in [
x for x in list(set(check_list)) if record.get(x, 0) < self.COUNT
]:
obj = ServicesHelper(service)
if not obj.is_install:
continue
if not obj.is_running:
if int(manual_info.get(obj.nick_name, 0)) == 1:
# service closed maually, skip
continue
# if obj.nick_name != "panel":
# write_logs(f"Service [ {obj.nick_name} ] is Not Running, Try to start it...")
if not self._overhead(obj.nick_name):
obj.script("restart")
if obj.is_running and manual_info.get(obj.nick_name) == 1:
# service is running, fix the wrong flag
manual_info[obj.nick_name] = 0
# under lock file read lock
self.__keep_flag_right(manual_info)
def first_time_installed(data: dict) -> None:
"""
首次安装服务启动守护进程服务
"""
# todo 虽然支持, 但是守护目前不干预, 前端没开放
exculde = [
"pgsql", "fail2ban", "btwaf", "ssh", "pdns", "php-fpm", "memcached"
]
if not data:
return
try:
for service in SERVICES_MAP.keys(): # support service
if service in exculde:
continue
if "php-fpm" in service:
continue
pl_name = f"{DATA_PATH}/first_installed_flag_{service}.pl"
if data.get(service): # panel installed
setup = data[service].get("setup", False)
if setup is False and os.path.exists(pl_name):
os.remove(pl_name)
elif setup is True and not os.path.exists(pl_name):
DaemonManager.add_daemon(service)
with open(pl_name, "w") as f:
f.write("1")
else:
pass
except:
pass
if __name__ == "__main__":
if len(sys.argv) == 2:
service_name = sys.argv[1]
if service_name in SERVICES_MAP:
helper = ServicesHelper(service_name)
print(f"Attempting to restart service: {service_name}")
helper.script("restart")
print(f"'{service_name}' restart.")
write_logs(f"Service [ {service_name} ] restart ...")
else:
print(f"Error: Service '{service_name}' not found.")
print("Available services are:")
for key in SERVICES_MAP.keys():
print(f" - {key}")
elif len(sys.argv) == 1:
pass
else:
print("Usage: python restart_services.py [service_name]")

171
script/run_log_split.py Normal file
View File

@@ -0,0 +1,171 @@
#!/usr/bin/python
# coding: utf-8
# -----------------------------
# Website run log split script
# -----------------------------
import sys
import os
import time
import glob
os.chdir("/www/server/panel")
if '/www/server/panel' not in sys.path:
sys.path.insert(0,'/www/server/panel')
if '/www/server/panel/class' not in sys.path:
sys.path.insert(0,'class/')
if '/www/server/panel/class_v2' not in sys.path:
sys.path.insert(0,'class_v2/')
import public, json
try:
from projectModelV2.pythonModel import main as pythonMod
from projectModelV2.nodejsModel import main as nodejsMod
mods = {
"python": pythonMod(),
"node": nodejsMod(),
}
except Exception as e:
print(str(e))
print("****** project split log task error ******")
print('==================================================================')
print('★[' + time.strftime("%Y/%m/%d %H:%M:%S") + '] split log task start ★')
print('==================================================================')
class LogSplit:
__slots__ = ("stype", "log_size", "limit", "_time", "compress", "exclude_sites")
@classmethod
def build_log_split(cls, name):
logsplit = cls()
path = '{}/data/run_log_split.conf'.format(public.get_panel_path())
data = {}
if os.path.exists(path):
try:
data = json.loads(public.readFile(path))
except:
public.ExecShell("rm -f {}".format(path))
return "file not found"
_clean(data)
public.writeFile(path, json.dumps(data))
target = data.get(name)
if not target :
return "file not found"
else:
for i in cls.__slots__:
if i in target:
setattr(logsplit, i, target[i])
logsplit._show()
return logsplit
def __init__(self, split_type: str = "day", limit: int = 180, log_size: int = 1024, compress: bool = False) -> None:
self.stype = split_type
self.log_size = log_size
self.limit = limit
self._time = time.strftime("%Y-%m-%d_%H%M%S")
self.compress = compress
self.exclude_sites = []
def _show(self):
if self.stype == "day":
print('|---Split method: Split 1 copy per day')
else:
print('|---Split method: Split by file size, split when file exceeds {}'.format(public.to_size(self.log_size)))
print('|---Currently keeping the latest [{}] copies'.format(self.limit))
def _to_zip(self, file_path):
os.system('gzip {}'.format(file_path))
def _del_surplus_log(self, history_log_path, log_prefix):
if not os.path.exists(history_log_path):
os.makedirs(history_log_path, mode=0o755)
logs = sorted(glob.glob(history_log_path + '/' + log_prefix + "*_log.*"))
count = len(logs)
if count >= self.limit:
for i in logs[:count - self.limit + 1]:
if os.path.exists(i):
os.remove(i)
print('|---Surplus log [' + i + '] has been deleted!')
def __call__(self, pjanme: str, sfile: str, log_prefix: str):
base_path, filename = sfile.rsplit("/", 1)
history_log_path = '{}/{}-history_logs'.format(base_path, pjanme)
if self.stype == 'size' and os.path.getsize(sfile) < self.log_size:
print('|---File size has not exceeded [{}], skipping!'.format(public.to_size(self.log_size)))
return
self._del_surplus_log(history_log_path, log_prefix)
if os.path.exists(sfile):
history_log_file = history_log_path + '/' + log_prefix + '_' + self._time + '_log.log'
if not os.path.exists(history_log_file):
with open(history_log_file, 'wb') as hf, open(sfile, 'r+b') as lf:
while True:
chunk_data = lf.read(1024*100)
if not chunk_data:
break
hf.write(chunk_data)
lf.seek(0)
lf.truncate()
if self.compress:
self._to_zip(history_log_file)
print('|---Log has been split to: ' + history_log_file + (".gz" if self.compress else ""))
else:
print('|---Target log file {} for project {} is missing, please note'.format(sfile, pjanme))
def main(name):
logsplit = LogSplit.build_log_split(name)
if logsplit=="file not found":
print(
"****** Detected panel project log split task configuration is empty,"
" please reset project log split task ******"
)
return
if not logsplit:
print("****** Panel project log split task configuration file is missing ******")
return
project = public.M('sites').where("project_type <> ? and name = ?", ("PHP", name)).find()
project['project_config'] = json.loads(project['project_config'])
for_split_func = getattr(mods.get(project["project_type"].lower()), "for_split")
if callable(for_split_func):
print('|---Starting to operate on {} project [{}] logs'.format(project["project_type"], project["name"]))
try:
for_split_func(logsplit, project)
print('|---Completed log split task for {} project [{}]'.format(project["project_type"], project["name"]))
except:
import traceback
print(traceback.format_exc())
print('|---Log split task error for {} project [{}]'.format(project["project_type"], project["name"]))
else:
print("****** Panel project log split task error ******")
print('================= All log split tasks completed ==================')
def _clean(data):
res = public.M('crontab').field('name').select()
del_config = []
for i in data.keys():
for j in res:
if j["name"].find(i) != -1 and j["name"].find("log split"):
break
else:
del_config.append(i)
for i in del_config:
del data[i]
if __name__ == '__main__':
if len(sys.argv) == 2:
name = sys.argv[1].strip()
main(name)
else:
print("****** Panel project log split task configuration parameter error ******")

60
script/run_script.py Normal file
View File

@@ -0,0 +1,60 @@
#coding: utf-8
#-------------------------------------------------------------------
# YakPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@yakpanel.com>
#-------------------------------------------------------------------
#------------------------------
# 项目开机自启调用脚本
#------------------------------
import os,sys
panel_path = '/www/server/panel'
os.chdir(panel_path)
if not 'class/' in sys.path: sys.path.insert(0,'class/')
import public,time,psutil
def project_model_auto_run():
'''
@name 项目模型自启调用
@author hwliang<2021-08-09>
@return bool
'''
project_model_path = '{}/projectModel'.format(public.get_class_path())
if not os.path.exists(project_model_path): return False
for mod_name in os.listdir(project_model_path):
if mod_name in ['base.py','__init__.py']: continue
mod_file = "{}/{}".format(project_model_path,mod_name)
if not os.path.exists(mod_file): continue
if not os.path.isfile(mod_file): continue
tmp_mod = public.get_script_object(mod_file)
if not hasattr(tmp_mod,'main'): continue
run_object = getattr(tmp_mod.main(),'auto_run',None)
if run_object: run_object()
def start():
run_tips = '/dev/shm/bt_auto_run.pl'
boot_time = psutil.boot_time()
stime = time.time()
if os.path.exists(run_tips):
last_time = int(public.readFile(run_tips))
if boot_time < last_time: return False
if stime - 3600 > boot_time: return False
# --------------------- 调用自启动程序 ---------------------
project_model_auto_run()
# --------------------- 结束调用 ---------------------
public.writeFile(run_tips,str(int(stime)))
if __name__ == '__main__':
start()

26
script/scan_log.py Normal file
View File

@@ -0,0 +1,26 @@
#coding: utf-8
import os,sys,time
os.chdir('/www/server/panel/')
sys.path.insert(0,"class/")
import public
import log_analysis
la = log_analysis.log_analysis()
site_infos = public.M('sites').field('name').select()
if not site_infos:
exit()
get = public.to_dict_obj({})
for i in site_infos:
if public.get_webserver() == 'nginx':
log_file = '{}.log'
elif public.get_webserver() == 'apache':
log_file = '{}-access_log'
else:
log_file = '{}_ols.access_log'
log_file = log_file.format(i['name'])
get.path = "/www/wwwlogs/{}".format(log_file)
get.action = "log_analysis"
print('==================================================================')
print('|-Analyzing [{}] website logs...'.format(i['name']))
la.log_analysis(get)
print('|-Analysis of website logs completed')
print('==================================================================')

45
script/second_task.py Normal file
View File

@@ -0,0 +1,45 @@
import time
import os, sys
panelPath = '/www/server/panel/'
os.chdir(panelPath)
needed_paths=['/www/server/panel', '/www/server/panel/class','/www/server/panel/class_v2']
for path in needed_paths:
if path not in sys.path:
sys.path.insert(0,path)
import public
def task(echo):
execstr = public.GetConfigValue('setup_path') + '/cron/' + echo
public.ExecShell('chmod +x ' + execstr)
public.ExecShell('nohup ' + execstr + ' start >> ' + execstr + '.log 2>&1 &')
def run_task(echo, interval):
timestamp_file = '{}/data/{}.timestamp'.format(panelPath,echo)
start_time = time.time()
while time.time() - start_time <= 60:
try:
with open(timestamp_file, 'r') as file:
last_executed = float(file.read().strip())
except (FileNotFoundError, ValueError):
last_executed = 0
current_time = time.time()
if current_time - last_executed >= interval:
# print("任务开始执行时间: {}".format(time.strftime('%H:%M:%S')))
task(echo)
with open(timestamp_file, 'w') as file:
file.write(str(time.time()))
# 计算需要等待的时间,避免过频繁的检查
time_to_wait = interval - (current_time - last_executed)
time.sleep(max(0, time_to_wait))
if __name__ == '__main__':
if len(sys.argv) != 3:
sys.exit(1)
interval = int(sys.argv[1])
echo = sys.argv[2]
run_task(echo, interval)

18
script/site_task.py Normal file
View File

@@ -0,0 +1,18 @@
#coding: utf-8
import os,sys,time
os.chdir('/www/server/panel/')
sys.path.insert(0,"class/")
import public
oldEdate = public.readFile('data/edate.pl')
if not oldEdate: oldEdate = '0000-00-00'
mEdate = time.strftime('%Y-%m-%d',time.localtime())
edateSites = public.M('sites').where('edate>? AND edate<? AND (status=? OR status=?)',('0000-00-00',mEdate,1,u'正在运行')).field('id,name').select()
import panelSite
siteObject = panelSite.panelSite()
for site in edateSites:
get = public.dict_obj()
get.id = site['id']
get.name = site['name']
siteObject.SiteStop(get)
oldEdate = mEdate
public.writeFile('data/edate.pl',mEdate)

89
script/sync_time.py Normal file
View File

@@ -0,0 +1,89 @@
import os
import sys
import time
import traceback
try:
import requests
except:
os.system("btpip install requests")
import requests
try:
import ntplib
except:
os.system("btpip install ntplib")
import ntplib
from datetime import datetime
try:
import pytz
except:
os.system("btpip install pytz")
import pytz
def sync_server_time(server, zone):
try:
print("Getting time from {}...".format(server))
client = ntplib.NTPClient()
response = client.request(server, version=3)
timestamp = response.tx_time
tz = pytz.timezone(zone)
time_zone = datetime.fromtimestamp(timestamp, tz)
local_time = datetime.now()
offset = timestamp - time.time()
print("Local time:", local_time)
print("Server time:", time_zone)
print("Time offset:", offset, "seconds")
import os
print("Syncing time...")
os.system('date -s "{}"'.format(time_zone))
return True
except Exception as e:
print("Failed to retrieve time from {}!".format(server))
# print(traceback.format_exc())
return False
server_list = ['cn.pool.ntp.org', '0.pool.ntp.org', '2.pool.ntp.org']
if __name__ == '__main__':
area = sys.argv[1].split('/')[0]
zone = sys.argv[1].split('/')[1]
print("Current time zone setting:{}".format(sys.argv[1]))
if not zone:
exit()
os.system('rm -f /etc/localtime')
os.system("ln -s '/usr/share/zoneinfo/" + area + "/" + zone + "' '/etc/localtime'")
flag = 0
for server in server_list:
if sync_server_time(server, sys.argv[1]):
flag = 1
print("|-Synchronized time successfully!")
break
if flag == 0:
try:
port = '7800'
port_pl = '/www/server/panel/data/port.pl'
if os.path.exists(port_pl):
with open(port_pl) as _pf:
port = (_pf.read() or '').strip() or '7800'
local_url = 'http://127.0.0.1:{}/api/index/get_time'.format(port)
print("Getting time from {}...".format(local_url))
r = requests.get(local_url, timeout=8)
timestamp = int(r.text)
tz = pytz.timezone(sys.argv[1])
time_zone = datetime.fromtimestamp(timestamp, tz)
local_time = datetime.now()
offset = timestamp - time.time()
print("Local time:", local_time)
print("Server time:", time_zone)
print("Time offset:", offset, "")
print("Syncing time...")
os.system(f"date -s '{time_zone}'")
flag = 1
print("|-Synchronized time successfully!")
except:
print(traceback.format_exc())
if flag == 0:
print("|-Synchronization time error!")

378
script/upgrade_firewall.py Normal file
View File

@@ -0,0 +1,378 @@
# coding: utf-8
# -------------------------------------------------------------------
# yakpanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 yakpanel(http://www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
import os
import sys
os.chdir('/www/server/panel/')
sys.path.insert(0, "class/")
sys.path.insert(0, "class_v2/")
sys.path.insert(0, "/www/server/panel/")
import public
__isFirewalld = False
__isUfw = False
if os.path.exists('/usr/sbin/firewalld') and os.path.exists('/usr/bin/yum'):
__isFirewalld = True
if os.path.exists('/usr/sbin/ufw') and os.path.exists('/usr/bin/apt-get'):
__isUfw = True
def check_ipset_exist(ipset_name):
cmd = "ipset list {}|grep Name".format(ipset_name)
res, err = public.ExecShell(cmd)
if err != "":
return False
return True
def firewalld_process_zone_file(zone_path, rule_dict, zone_name):
"""
处理zone文件public.xml或trusted.xml删除匹配的规则。
参数:
zone_path (str): zone文件路径
rule_dict (dict): 规则字典
zone_name (str): Zone名称'public''trusted'
"""
if not os.path.exists(zone_path):
return
import xml.etree.ElementTree as ET
tree = ET.parse(zone_path)
root = tree.getroot()
# 处理<rule>标签
for rule in root.findall('rule'):
source = rule.find('source')
if source is not None:
ip = source.get('address')
action = None
if rule.find('accept') is not None:
action = 'ACCEPT'
elif rule.find('drop') is not None:
action = 'DROP'
if (ip in rule_dict and
rule_dict[ip]['Chain'].upper() == 'INPUT' and
rule_dict[ip]['Zone'] == zone_name and
rule_dict[ip]['Strategy'].upper() == action.upper()):
root.remove(rule)
if zone_name == 'trusted':
for source in root.findall('source'):
ip = source.get('address')
if (ip in rule_dict and
rule_dict[ip]['Chain'].upper() == 'INPUT' and
rule_dict[ip]['Zone'] == 'trusted' and
rule_dict[ip]['Strategy'].upper() == 'ACCEPT'):
root.remove(source)
tree.write(zone_path)
def firewalld_process_direct_file(direct_path, rule_dict):
"""
处理direct.xml文件删除匹配的OUTPUT规则。
参数:
direct_path (str): direct.xml文件路径
rule_dict (dict): 规则字典
"""
if not os.path.exists(direct_path):
return
import xml.etree.ElementTree as ET
tree = ET.parse(direct_path)
root = tree.getroot()
for rule in root.findall('rule'):
if rule.get('chain') == 'OUTPUT':
rule_text = rule.text.strip()
if rule_text.startswith('-d '):
parts = rule_text.split()
ip = parts[1]
action = parts[-1]
if (ip in rule_dict and
rule_dict[ip]['Chain'].upper() == 'OUTPUT' and
rule_dict[ip]['Strategy'].upper() == action.upper()):
root.remove(rule)
tree.write(direct_path)
def firewalld_batch_remove_ip_rule(rule_dict):
direct_path = '/etc/firewalld/direct.xml'
public_path = '/etc/firewalld/zones/public.xml'
trusted_path = '/etc/firewalld/zones/trusted.xml'
import shutil
if os.path.exists(public_path):
shutil.copyfile(public_path, public_path + '.bak')
firewalld_process_zone_file(public_path, rule_dict, 'public')
if os.path.exists(trusted_path):
shutil.copyfile(trusted_path, trusted_path + '.bak')
firewalld_process_zone_file(trusted_path, rule_dict, 'trusted')
if os.path.exists(direct_path):
shutil.copyfile(direct_path, direct_path + '.bak')
firewalld_process_direct_file(direct_path, rule_dict)
def ufw_batch_remove_ip_rule(rule_dict):
"""
UFW批量删除规则
"""
ufw_config = '/etc/ufw/user.rules'
import shutil
shutil.copy(ufw_config, ufw_config + ".bak" + public.format_date())
lines = public.readFile(ufw_config)
lines = lines.splitlines()
new_lines = []
i = 0
while i < len(lines):
line = lines[i]
if line.startswith('### tuple ###'):
parts = line.split()
action = parts[3] # deny 或 allow
direction = parts[-1] # in 或 out
ip = parts[-2] # IP 地址
if ip == '0.0.0.0/0':
new_lines.extend(lines[i:i + 3]) # 保留这三行
i += 3
continue
chain = 'INPUT' if direction == 'in' else 'OUTPUT' if direction == 'out' else None
strategy = 'DROP' if action == 'deny' else 'ACCEPT' if action == 'allow' else None
if chain and strategy and all([
ip in rule_dict,
chain.upper() == rule_dict[ip]['Chain'].upper(),
strategy.upper() == rule_dict[ip]['Strategy'].upper(),
]):
# 如果匹配,跳过这三行(即删除)
i += 3
else:
# 如果不匹配,保留这三行
new_lines.extend(lines[i:i + 3])
i += 3
else:
# 如果不是规则的开始行,直接保留
new_lines.append(lines[i])
i += 1
with open(ufw_config, 'w') as file:
file.writelines(line if line.endswith('\n') else line + '\n' for line in new_lines)
# 9.5.0-9.6.0
def upgrade_iprule(commodel):
list_address = commodel.firewall.list_address()
if len(list_address) == 0:
return
rule_dict = {}
print("-* IP rule data being migrated...")
for item in list_address:
print("Migration IP rules:{}".format(item))
commodel.iptables.set_chain_rich_ip(item, 'add', item['Chain'])
rule_dict[item['Address']] = {
"Address": item['Address'],
"Family": item['Family'],
"Strategy": item['Strategy'],
"Zone": item.get('Zone'),
"Chain": item['Chain']
}
if __isFirewalld:
firewalld_batch_remove_ip_rule(rule_dict)
elif __isUfw:
ufw_batch_remove_ip_rule(rule_dict)
else:
return
commodel.firewall.reload()
public.ExecShell("systemctl reload BT-FirewallServices")
print("-* IP rule data migration completed...")
# 9.5.0-9.6.0
def upgrade_countrys(commodel):
country_list = public.M('firewall_country').select()
if len(country_list) == 0:
return
from safeModelV2.firewallModel import main as firewallModel
firewallmodel = firewallModel()
print("-* Area rule data being migrated...")
for country in country_list:
ports = country['ports']
brief = country['brief']
types = country['types']
print("Rules for relocation areas:{}".format(brief))
if __isUfw or not __isFirewalld:
if not ports:
public.ExecShell('iptables -D INPUT -m set --match-set ' + brief + ' src -j ' + types.upper())
else:
public.ExecShell(
'iptables -D INPUT -m set --match-set ' + brief + ' src -p tcp --destination-port ' + ports + ' -j ' + types.upper()
)
else:
if not ports:
o, e = public.ExecShell(
"firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 -m set --match-set {} src -j {}".format(
brief, types.upper()))
if e != '':
public.ExecShell(
'firewall-cmd --permanent --remove-rich-rule=\'rule source ipset="{}" {}\''.format(brief,
types.upper()))
else:
o, e = public.ExecShell(
'firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 -m set --match-set {} src -p tcp --dport {} -j {}'.format(
brief, ports, types.upper()))
if e != '':
public.ExecShell(
'firewall-cmd --permanent --remove-rich-rule=\'rule source ipset="' + brief + '" port port="' + ports + '" protocol=tcp ' + types.upper() + '\'')
commodel.firewall.reload()
for country in country_list:
ports = country['ports']
brief = country['brief']
types = country['types']
print("Rules for relocation areas:{}".format(brief))
o, e = public.ExecShell("ipset destroy " + brief)
if e != '':
public.ExecShell("firewall-cmd --permanent --delete-ipset=" + brief)
tmp_file = "/tmp/firewall_{}.txt".format(brief)
if os.path.exists(tmp_file): # bt 9.5.0之后
command = '''grep -q "in_bt_country" {filename} || awk '{{print "add in_bt_country_" $2, $3}}' {filename} > {filename}.tmp && mv {filename}.tmp {filename}'''.format(
filename=tmp_file
)
public.ExecShell(command)
_ipset = "in_bt_country_" + brief
public.ExecShell(
'ipset create {} hash:net maxelem 1000000; ipset restore -f {}'.format(_ipset, tmp_file)
)
if ports:
public.ExecShell(
'iptables -I IN_BT_Country -m set --match-set {} src -p tcp --destination-port {} -j {}'.format(
_ipset,
ports,
types.upper())
)
else:
public.ExecShell(
'iptables -I IN_BT_Country -m set --match-set {} src -j {}'.format(_ipset, types.upper())
)
public.ExecShell("systemctl reload BT-FirewallServices")
else: # bt 9.5.0之前
public.M("firewall_country").where("id=?", (country['id'],)).delete()
get_tmp = public.dict_obj()
get_tmp.country = country['country']
get_tmp.types = country['types']
get_tmp.ports = country['ports']
get_tmp.choose = "all"
get_tmp.is_update = True
res = firewallmodel.create_countrys(get_tmp)
if res['status'] is False:
print(f"[ {brief} ] Area rule migrated fail : {res['message']}")
print("-* Area rule data migration completed...")
# 9.5.0-9.6.0
def upgrade_malicious_ip():
pl_path = "/www/server/panel/config/firewalld_malicious_ip.pl"
if not os.path.exists(pl_path):
return
if not check_ipset_exist("malicious_ipset"):
return
print("-* Migration of malicious IP blocking data in progress...")
# 移除旧规则
if __isUfw or not __isFirewalld:
public.ExecShell('rm -rf /var/log/FIREWALL-ACCESS-LOG*')
public.ExecShell('rm -rf /etc/rsyslog.d/firewall-access-log.conf')
public.ExecShell('rm -rf /etc/logrotate.d/firewall-access-log')
public.ExecShell(
'iptables -D INPUT -m conntrack --ctstate NEW -j LOG --log-prefix "FIREWALL-ACCESS: " --log-level 4')
public.ExecShell('iptables -D INPUT -j IP-DAILY-LOG')
public.ExecShell('iptables -D IP-DAILY-LOG -m recent --name DAILY_IPS --rcheck --seconds 86400 -j RETURN')
public.ExecShell(
'iptables -D IP-DAILY-LOG -m recent --name DAILY_IPS --set -j LOG --log-prefix "DAILY-IP: " --log-level 4')
public.ExecShell('iptables -D IP-DAILY-LOG -j RETURN')
public.ExecShell('iptables -X IP-DAILY-LOG')
public.ExecShell("iptables -D INPUT -m set --match-set malicious_ipset src -j DROP")
else:
public.ExecShell(
"firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 1 -m conntrack --ctstate NEW -j LOG --log-prefix 'FIREWALL-ACCESS: ' --log-level 4")
public.ExecShell("firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 2 -j IP-DAILY-LOG")
public.ExecShell(
"firewall-cmd --permanent --direct --remove-rule ipv4 filter IP-DAILY-LOG 0 -m recent --name DAILY_IPS --rcheck --seconds 86400 -j RETURN")
public.ExecShell(
"firewall-cmd --permanent --direct --remove-rule ipv4 filter IP-DAILY-LOG 1 -m recent --name DAILY_IPS --set -j LOG --log-prefix 'DAILY-IP: ' --log-level 4")
public.ExecShell("firewall-cmd --permanent --direct --remove-rule ipv4 filter IP-DAILY-LOG 2 -j RETURN")
public.ExecShell("firewall-cmd --permanent --direct --remove-chain ipv4 filter IP-DAILY-LOG")
public.ExecShell(
"firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 -m set --match-set malicious_ipset src -j DROP")
commodel.firewall.reload()
public.ExecShell("ipset destroy malicious_ipset")
read = public.readFile(pl_path)
if read.strip() == "open":
tmp_file = "/tmp/firewall_malicious_ip.txt"
command = '''grep -q "in_bt" {filename} || awk '{{print "add in_bt_" $2, $3 ,"timeout", 86400}}' {filename} > {filename}.tmp && mv {filename}.tmp {filename}'''.format(
filename=tmp_file)
public.ExecShell(command)
public.ExecShell("sh /www/server/panel/script/open_malicious_ip.sh")
public.ExecShell("ipset restore -f {}".format(tmp_file))
public.ExecShell("systemctl reload BT-FirewallServices")
print("-* Malicious IP blocking data migration completed...")
def upgrade_port_forward(commodel):
"""
升级端口转发规则
"""
port_forward_list = commodel.firewall.list_port_forward()
print("-* Migrating port forwarding rule data...")
for port_forward in port_forward_list:
print("Migrating port forwarding rules:{}".format(port_forward))
info = {
"Family": "ipv4",
"Protocol": port_forward["Protocol"],
"S_Address": port_forward['S_Address'],
"S_Port": port_forward['S_Port'],
"T_Address": port_forward['T_Address'],
"T_Port": port_forward['T_Port'],
}
commodel.firewall.port_forward(info, "remove")
commodel.iptables.port_forward(info, "add")
public.ExecShell("systemctl reload BT-FirewallServices")
commodel.firewall.reload()
print("-* Port forwarding rule data migration complete...")
if __name__ == '__main__':
try:
from firewallModelV2.comModel import main as comModel
import time
commodel = comModel()
upgrade_iprule(commodel)
upgrade_countrys(commodel)
upgrade_malicious_ip()
upgrade_port_forward(commodel)
print("yakpanel: FireWall Migrate Service Finish...")
except Exception as e:
import traceback
print("-" * 50)
print(traceback.format_exc())
print("-" * 50)
print(f"Error: FireWall Migrate Error: {e}")

15
script/upgrade_flask.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
is_flask1=$(/www/server/panel/pyenv/bin/pip3 list|grep 'Flask'|grep ' 1.')
if [ "${is_flask1}" = "" ];then
exit;
fi
/www/server/panel/pyenv/bin/pip3 install flask -U
/www/server/panel/pyenv/bin/pip3 install flask-sock
/www/server/panel/pyenv/bin/pip3 install simple-websocket==0.10.0 -I
rm -f /www/server/panel/script/upgrade_flask.sh
bash /www/server/panel/init.sh reload

20
script/upgrade_gevent.sh Normal file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
check_gevent_version(){
is_gevent140=$(/www/server/panel/pyenv/bin/pip3 list|grep 'gevent'|grep ' 1.4.0')
}
check_gevent_version
if [ "${is_gevent140}" = "" ];then
exit;
fi
/www/server/panel/pyenv/bin/pip3 install gevent -U
check_gevent_version
if [ "${is_gevent140}" = "" ];then
rm -f /www/server/panel/script/upgrade_gevent.sh
bash /www/server/panel/init.sh reload
fi

View File

@@ -0,0 +1,19 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo 'skip telegram'
#
#is_flask1=$(/www/server/panel/pyenv/bin/pip3 list|grep 'telegram'|grep '0.0.1')
#if [ "${is_flask1}" != "" ];then
# echo "Y" | /www/server/panel/pyenv/bin/pip3 uninstall telegram
#fi
#
#is_flask2=$(/www/server/panel/pyenv/bin/pip3 list|grep 'python-telegram-bot'|grep '20.3' )
#if [ "${is_flask2}" = "" ];then
# /www/server/panel/pyenv/bin/pip3 install python-telegram-bot==20.3 -I
#else
# exit;
#fi
#
#rm -f /www/server/panel/script/upgrade_telegram.sh
#bash /www/server/panel/init.sh reload

34
script/vhost_virtual.py Normal file
View File

@@ -0,0 +1,34 @@
#coding: utf-8
#多用户放行端口脚本
import sys,os
os.chdir('/www/server/panel/')
sys.path.insert(0,"class/")
sys.path.insert(0,"class_v2/")
import public
import yaml
public.print_log("vhost_virtual.py")
default_yaml = '{}/vhost_virtual/manifest/config/default.yaml'.format(public.GetConfigValue('setup_path'))
not_accept_port_file = '{}/vhost_virtual/config/not_accept_port.pl'.format(public.GetConfigValue('setup_path'))
import firewalls
get = public.dict_obj()
get.ps = "vhost virtual service"
if not os.path.exists(not_accept_port_file):
with open(default_yaml, 'r') as file:
data=yaml.safe_load(file)
try:
if data["server"].get("address"):
http_port=data["server"]["address"]
#如果存在:用:分割端口,并取第二个端口和去除空格
if ":" in http_port:
get.port=http_port.split(":")[1].strip()
if get.port !="" and public.M('firewall').where("port=?",(get.port,)).count()<1::
firewalls.firewalls().AddAcceptPort(get)
if data["server"].get("httpsAddr"):
https_port = data["server"]["httpsAddr"]
#如果存在:用:分割端口,并取第二个端口和去除空格
if ":" in https_port:
get.port=https_port.split(":")[1].strip()
if get.port !="" and public.M('firewall').where("port=?",(get.port,)).count()<1::
firewalls.firewalls().AddAcceptPort(get)
except Exception as e:
public.print_log("e111--------------:{}".format(e))

8
script/warning_list.py Normal file
View File

@@ -0,0 +1,8 @@
#coding: utf-8
import os,sys
os.chdir('/www/server/panel')
sys.path.insert(0,'class/')
import panelWarning,public,json
args = public.dict_obj()
result = panelWarning.panelWarning().get_list(args)
print(json.dumps(result))

239
script/webserver-ctl.sh Normal file
View File

@@ -0,0 +1,239 @@
#!/bin/bash
action=$1
panel_path="/www/server/panel" # 面板路径
webserver_bin="$panel_path/webserver/sbin/webserver" # nginx二进制文件
webserver_conf="$panel_path/webserver/conf/webserver.conf" # nginx配置文件
webserver_pid="$panel_path/webserver/logs/webserver.pid" # nginx pid文件
PID=0
get_pid() {
if [ ! -f "$webserver_pid" ]; then
PID=0
else
PID=$(cat $webserver_pid)
if [ "$PID" == "" ]; then
PID=0
else
PID=$(ps aux | grep "$PID" | grep -v grep | awk '{print $2}')
PID_11=$(ps aux | grep "$webserver_bin" | grep -v grep | awk '{print $2}')
if [ "$PID" != "$PID_11" ]; then
PID=0
fi
fi
fi
PID=$(ps aux | grep "$webserver_bin" | grep -v grep | awk '{print $2}')
if [ -z "$PID" ]; then
PID=0
else
PID="$PID"
fi
if [ -z "$PID" ]; then
PID=0
fi
}
validate_server_files() {
if [ ! -f "$webserver_conf" ]; then
echo "YakPanel web server configuration not found: $webserver_conf"
exit 1
fi
if [ ! -f "$webserver_bin" ]; then
echo "YakPanel web server binary not found: $webserver_bin"
exit 1
fi
}
start() {
validate_server_files
get_pid
if [ $PID -gt 0 ]; then
echo "YakPanel web server is already running with PID ($PID)"
exit 1
fi
echo -n "Starting YakPanel web server..."
if [ -f "$webserver_pid" ]; then
rm -f $webserver_pid
fi
chmod 700 $webserver_bin
$webserver_bin -c $webserver_conf
if [ $? -ne 0 ]; then
echo "Failed to start YakPanel web server"
exit 1
fi
echo " Started"
}
stop() {
validate_server_files
get_pid
if [ $PID -eq 0 ]; then
echo "YakPanel web server is not running"
exit 1
fi
echo -n "Stopping YakPanel web server..."
$webserver_bin -c $webserver_conf -s stop
pids=$(lsof -c webserver|grep LISTEN|awk '{print $2}'|sort -u)
for pid in $pids; do
kill -9 $pid
done
pids=$(ps aux | grep "$webserver_bin" | grep -v grep | awk '{print $2}')
for pid in $pids; do
kill -9 $pid
child_process=1
done
if [ "$child_process" = 1 ]; then
if [ -f /www/server/panel/data/port.pl ]; then
kill -9 $(lsof -t -i:$(cat /www/server/panel/data/port.pl) -sTCP:LISTEN)
fi
fi
echo " Stopped"
}
restart() {
validate_server_files
get_pid
echo -n "Restarting YakPanel web server..."
if [ $PID -eq 0 ]; then
$webserver_bin -c $webserver_conf
else
$webserver_bin -c $webserver_conf -s reopen
fi
if [ $? -ne 0 ]; then
echo "Failed to restart YakPanel web server"
exit 1
fi
echo " Restarted"
}
status() {
validate_server_files
get_pid
if [ $PID -eq 0 ]; then
echo "YakPanel web server is not running"
else
cmdline=/proc/$PID/cmdline
if [ ! -f $cmdline ]; then
echo "YakPanel web server is not running"
rm -f $webserver_pid
exit 1
fi
echo "YakPanel web server is running with PID ($PID)"
fi
}
reload() {
validate_server_files
get_pid
if [ $PID -eq 0 ]; then
echo "YakPanel web server is not running"
exit 1
fi
echo -n "Reloading YakPanel web server..."
$webserver_bin -c $webserver_conf -s reload
if [ $? -ne 0 ]; then
echo "Failed to reload YakPanel web server"
exit 1
fi
echo " Reloaded"
}
configtest() {
validate_server_files
# 检查配置文件正确性,检查程序自动输出检查结果
$webserver_bin -c $webserver_conf -t
}
download() {
tip_file=$panel_path/data/download.pl
if [ -f $tip_file ]; then
echo "YakPanel web server binary has been downloaded"
exit 1
fi
# 标记已下载
echo "1" > $tip_file
# 获取machine
machine=$(uname -m)
zip_file=$panel_path/data/webserver-$machine.zip
wget -O $zip_file https://node.yakpanel.com/webserver/webserver-$machine.zip
if [ $? -ne 0 ]; then
echo "Failed to download YakPanel web server binary"
rm -f $zip_file
exit 1
fi
# 验证文件hash
hash256=$(sha256sum $zip_file | awk '{print $1}')
cloud_hash256=$(wget -q -O - https://node.yakpanel.com/webserver/webserver-$machine.txt)
if [ "$hash256" != "$cloud_hash256" ]; then
echo "Failed to verify YakPanel web server binary"
rm -f $zip_file
exit 1
fi
# 解压文件
unzip -o $zip_file -d $panel_path/
if [ ! -f $webserver_bin ]; then
echo "Failed to extract YakPanel web server binary"
rm -f $zip_file
exit 1
fi
# 删除临时文件
rm -f $zip_file
# 设置权限
chmod 700 $webserver_bin
echo "YakPanel web server binary has been downloaded"
bash /www/server/panel/init.sh reload
}
case "$action" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
reload)
reload
;;
configtest)
configtest
;;
test)
configtest
;;
download)
download
;;
*)
echo "Usage: $0 {start|stop|restart|status|reload|configtest|test|download}"
exit 1
;;
esac