相对管理后台的 ssl来说,其实网站的 ssl 证书才是正事,毕竟这个关系到网站的访问。按照官方的说法在开放 80 端口的情况下,南墙可以自动申请更新证书,不过后台没找到配置的地方,我的 v4 的 80 也是不通的,所以就需要自己去维护管理证书了。
然而,上午在问了管理之后,得到的答复是没有 api,可以自己抓包进行修改。
嗐,这么看来其实也没啥,最起码说明后台的 api 接口是可以直接拿来用的。即使是有 api 文档,也是得自己去看,去写,没有的话 curl 抓包一样能解决问题。按照之前的方法,只直接复制 curl 给 cursor 就可以了。
api 文件baby_nanqiang_api_tools.py内容:
#!/usr/bin/env python3
import requests
import json
import jwt
from datetime import datetime
import os
import urllib3
# 禁用 SSL 验证警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class NanQiangAPI:
def __init__(self, base_url="https://lang.bi:443"):
self.base_url = base_url
self.session = requests.Session()
self.session.verify = False # 忽略SSL证书验证
self.token = None
self._setup_headers()
def _setup_headers(self):
"""设置请求头"""
self.headers = {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
'cache-control': 'no-cache',
'content-type': 'application/json',
'origin': self.base_url,
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': f'{self.base_url}/',
'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
}
def _update_headers_with_token(self):
"""更新请求头,添加token"""
if self.token:
self.headers['Authorization'] = self.token # 直接使用token,不添加'Bearer '前缀
def delete_cert(self, cert_id):
"""
删除指定ID的证书
:param cert_id: 证书ID
:return: 删除结果
"""
if not self.is_logged_in():
print("请先登录")
return None
url = f"{self.base_url}/api/v1/certs/{cert_id}"
try:
response = self.session.delete(
url,
headers=self.headers
)
response_data = response.json()
if 'err' in response_data:
print(f"删除证书失败: {response_data['err']}")
return None
# 检查删除是否成功
if response_data.get('result') == 'success' and response_data.get('RowsAffected') > 0:
print(f"证书 {cert_id} 删除成功")
return True
else:
print(f"证书 {cert_id} 删除失败: 未找到证书或删除操作未生效")
return False
except requests.exceptions.RequestException as e:
print(f"删除证书请求失败: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"解析响应数据失败: {str(e)}")
return None
def parse_cert_list(self, cert_list):
"""
解析证书列表数据
:param cert_list: 证书列表数据
:return: 解析后的证书信息列表
"""
if not cert_list:
return None
parsed_certs = []
for cert in cert_list:
try:
# 解析SNI字段(JSON字符串)
sni_list = json.loads(cert.get('sni', '[]'))
parsed_cert = {
'id': cert.get('id'),
'sni': sni_list,
'expire_time': cert.get('expire_time'),
'update_time': cert.get('update_time')
}
parsed_certs.append(parsed_cert)
except json.JSONDecodeError as e:
print(f"解析SNI字段失败: {str(e)}")
continue
except Exception as e:
print(f"解析证书数据失败: {str(e)}")
continue
return parsed_certs
def get_cert_list(self):
"""
获取证书列表
:return: 证书列表
"""
if not self.is_logged_in():
print("请先登录")
return None
url = f"{self.base_url}/api/v1/certs/"
try:
response = self.session.get(
url,
headers=self.headers
)
response_data = response.json()
if 'err' in response_data:
print(f"获取证书列表失败: {response_data['err']}")
return None
return response_data
except requests.exceptions.RequestException as e:
print(f"获取证书列表请求失败: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"解析响应数据失败: {str(e)}")
return None
def login(self, username, password, otp=""):
"""
登录接口
:param username: 用户名
:param password: 密码
:param otp: 双因素认证码(可选)
:return: 登录响应
"""
url = f"{self.base_url}/api/v1/users/login"
data = {
"usr": username,
"pwd": password,
"otp": otp
}
try:
response = self.session.post(
url,
headers=self.headers,
json=data
)
# 获取响应数据
response_data = response.json()
# 检查是否有错误信息
if 'err' in response_data:
print(f"登录失败: {response_data['err']}")
return None
# 保存token
if 'token' in response_data:
self.token = response_data['token']
self._update_headers_with_token()
# # 解析token信息
# try:
# # 使用 jwt.decode 替代 jwt.decode_complete
# token_data = jwt.decode(self.token, options={"verify_signature": False})
# exp_timestamp = token_data.get('exp')
# if exp_timestamp:
# exp_date = datetime.fromtimestamp(exp_timestamp)
# print(f"Token 有效期至: {exp_date}")
# except Exception as e:
# print(f"无法解析token信息: {str(e)}")
return response_data
except requests.exceptions.RequestException as e:
print(f"登录请求失败: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"解析响应数据失败: {str(e)}")
return None
def check_cert(self, cert_content, key_content, mode=0):
"""
检查证书
:param cert_content: 证书内容
:param key_content: 私钥内容
:param mode: 模式,默认为0
:return: 检查结果
"""
if not self.is_logged_in():
print("请先登录")
return None
url = f"{self.base_url}/api/v1/certs/check"
# 准备multipart/form-data数据
files = {
'mode': (None, str(mode)),
'cert': (None, cert_content),
'key': (None, key_content)
}
try:
# 临时移除content-type,让requests自动设置
headers = self.headers.copy()
headers.pop('content-type', None)
response = self.session.post(
url,
headers=headers,
files=files
)
response_data = response.json()
if 'err' in response_data:
print(f"证书检查失败: {response_data['err']}")
return None
return response_data
except requests.exceptions.RequestException as e:
print(f"证书检查请求失败: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"解析响应数据失败: {str(e)}")
return None
def check_cert_from_files(self, cert_file_path, key_file_path, mode=0):
"""
从文件检查证书
:param cert_file_path: 证书文件路径
:param key_file_path: 私钥文件路径
:param mode: 模式,默认为0
:return: 检查结果
"""
try:
with open(cert_file_path, 'r') as f:
cert_content = f.read()
with open(key_file_path, 'r') as f:
key_content = f.read()
return self.check_cert(cert_content, key_content, mode)
except FileNotFoundError as e:
print(f"文件不存在: {str(e)}")
return None
except Exception as e:
print(f"读取文件失败: {str(e)}")
return None
def submit_cert_config(self, check_result):
"""
提交证书配置
:param check_result: 证书检查的结果数据
:return: 提交结果
"""
if not self.is_logged_in():
print("请先登录")
return None
if not check_result:
print("无效的证书检查结果")
return None
url = f"{self.base_url}/api/v1/certs/config"
# 准备提交数据
data = {
"id": check_result.get("id", 0),
"sni": check_result.get("sni", "[]"),
"cert": check_result.get("cert", ""),
"key": check_result.get("key", ""),
"expire_time": check_result.get("expire_time", ""),
"update_time": check_result.get("update_time", "")
}
try:
response = self.session.post(
url,
headers=self.headers,
json=data
)
response_data = response.json()
if 'err' in response_data:
print(f"证书配置提交失败: {response_data['err']}")
return None
return response_data
except requests.exceptions.RequestException as e:
print(f"证书配置提交请求失败: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"解析响应数据失败: {str(e)}")
return None
def is_logged_in(self):
"""
检查是否已登录
:return: bool
"""
return self.token is not None
def main():
# 使用示例
api = NanQiangAPI()
# 登录信息
username = "obaby"
password = "obaby@mars"
# 执行登录
result = api.login(username, password)
if result:
print("登录成功:")
print(json.dumps(result, indent=2, ensure_ascii=False))
print(f"Token: {api.token}")
# 获取证书列表
cert_list = api.get_cert_list()
if cert_list:
# 解析证书列表
parsed_certs = api.parse_cert_list(cert_list)
if parsed_certs:
print("解析后的证书列表:")
print(json.dumps(parsed_certs, indent=2, ensure_ascii=False))
# # 删除证书示例
# cert_id = 4 # 要删除的证书ID
# delete_result = api.delete_cert(cert_id)
# if delete_result:
# print(f"证书 {cert_id} 删除成功")
# else:
# print(f"证书 {cert_id} 删除失败")
# 证书检查示例
cert_file = "path/to/cert.pem"
key_file = "path/to/key.pem"
if os.path.exists(cert_file) and os.path.exists(key_file):
return
# 先检查证书
cert_result = api.check_cert_from_files(cert_file, key_file)
if cert_result:
print("证书检查结果:")
print(json.dumps(cert_result, indent=2, ensure_ascii=False))
# 提交证书配置
submit_result = api.submit_cert_config(cert_result)
if submit_result:
print("证书配置提交成功:")
print(json.dumps(submit_result, indent=2, ensure_ascii=False))
else:
print("证书配置提交失败")
else:
print("登录失败")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
import requests
import json
import jwt
from datetime import datetime
import os
import urllib3
# 禁用 SSL 验证警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class NanQiangAPI:
def __init__(self, base_url="https://lang.bi:443"):
self.base_url = base_url
self.session = requests.Session()
self.session.verify = False # 忽略SSL证书验证
self.token = None
self._setup_headers()
def _setup_headers(self):
"""设置请求头"""
self.headers = {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
'cache-control': 'no-cache',
'content-type': 'application/json',
'origin': self.base_url,
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': f'{self.base_url}/',
'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
}
def _update_headers_with_token(self):
"""更新请求头,添加token"""
if self.token:
self.headers['Authorization'] = self.token # 直接使用token,不添加'Bearer '前缀
def delete_cert(self, cert_id):
"""
删除指定ID的证书
:param cert_id: 证书ID
:return: 删除结果
"""
if not self.is_logged_in():
print("请先登录")
return None
url = f"{self.base_url}/api/v1/certs/{cert_id}"
try:
response = self.session.delete(
url,
headers=self.headers
)
response_data = response.json()
if 'err' in response_data:
print(f"删除证书失败: {response_data['err']}")
return None
# 检查删除是否成功
if response_data.get('result') == 'success' and response_data.get('RowsAffected') > 0:
print(f"证书 {cert_id} 删除成功")
return True
else:
print(f"证书 {cert_id} 删除失败: 未找到证书或删除操作未生效")
return False
except requests.exceptions.RequestException as e:
print(f"删除证书请求失败: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"解析响应数据失败: {str(e)}")
return None
def parse_cert_list(self, cert_list):
"""
解析证书列表数据
:param cert_list: 证书列表数据
:return: 解析后的证书信息列表
"""
if not cert_list:
return None
parsed_certs = []
for cert in cert_list:
try:
# 解析SNI字段(JSON字符串)
sni_list = json.loads(cert.get('sni', '[]'))
parsed_cert = {
'id': cert.get('id'),
'sni': sni_list,
'expire_time': cert.get('expire_time'),
'update_time': cert.get('update_time')
}
parsed_certs.append(parsed_cert)
except json.JSONDecodeError as e:
print(f"解析SNI字段失败: {str(e)}")
continue
except Exception as e:
print(f"解析证书数据失败: {str(e)}")
continue
return parsed_certs
def get_cert_list(self):
"""
获取证书列表
:return: 证书列表
"""
if not self.is_logged_in():
print("请先登录")
return None
url = f"{self.base_url}/api/v1/certs/"
try:
response = self.session.get(
url,
headers=self.headers
)
response_data = response.json()
if 'err' in response_data:
print(f"获取证书列表失败: {response_data['err']}")
return None
return response_data
except requests.exceptions.RequestException as e:
print(f"获取证书列表请求失败: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"解析响应数据失败: {str(e)}")
return None
def login(self, username, password, otp=""):
"""
登录接口
:param username: 用户名
:param password: 密码
:param otp: 双因素认证码(可选)
:return: 登录响应
"""
url = f"{self.base_url}/api/v1/users/login"
data = {
"usr": username,
"pwd": password,
"otp": otp
}
try:
response = self.session.post(
url,
headers=self.headers,
json=data
)
# 获取响应数据
response_data = response.json()
# 检查是否有错误信息
if 'err' in response_data:
print(f"登录失败: {response_data['err']}")
return None
# 保存token
if 'token' in response_data:
self.token = response_data['token']
self._update_headers_with_token()
# # 解析token信息
# try:
# # 使用 jwt.decode 替代 jwt.decode_complete
# token_data = jwt.decode(self.token, options={"verify_signature": False})
# exp_timestamp = token_data.get('exp')
# if exp_timestamp:
# exp_date = datetime.fromtimestamp(exp_timestamp)
# print(f"Token 有效期至: {exp_date}")
# except Exception as e:
# print(f"无法解析token信息: {str(e)}")
return response_data
except requests.exceptions.RequestException as e:
print(f"登录请求失败: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"解析响应数据失败: {str(e)}")
return None
def check_cert(self, cert_content, key_content, mode=0):
"""
检查证书
:param cert_content: 证书内容
:param key_content: 私钥内容
:param mode: 模式,默认为0
:return: 检查结果
"""
if not self.is_logged_in():
print("请先登录")
return None
url = f"{self.base_url}/api/v1/certs/check"
# 准备multipart/form-data数据
files = {
'mode': (None, str(mode)),
'cert': (None, cert_content),
'key': (None, key_content)
}
try:
# 临时移除content-type,让requests自动设置
headers = self.headers.copy()
headers.pop('content-type', None)
response = self.session.post(
url,
headers=headers,
files=files
)
response_data = response.json()
if 'err' in response_data:
print(f"证书检查失败: {response_data['err']}")
return None
return response_data
except requests.exceptions.RequestException as e:
print(f"证书检查请求失败: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"解析响应数据失败: {str(e)}")
return None
def check_cert_from_files(self, cert_file_path, key_file_path, mode=0):
"""
从文件检查证书
:param cert_file_path: 证书文件路径
:param key_file_path: 私钥文件路径
:param mode: 模式,默认为0
:return: 检查结果
"""
try:
with open(cert_file_path, 'r') as f:
cert_content = f.read()
with open(key_file_path, 'r') as f:
key_content = f.read()
return self.check_cert(cert_content, key_content, mode)
except FileNotFoundError as e:
print(f"文件不存在: {str(e)}")
return None
except Exception as e:
print(f"读取文件失败: {str(e)}")
return None
def submit_cert_config(self, check_result):
"""
提交证书配置
:param check_result: 证书检查的结果数据
:return: 提交结果
"""
if not self.is_logged_in():
print("请先登录")
return None
if not check_result:
print("无效的证书检查结果")
return None
url = f"{self.base_url}/api/v1/certs/config"
# 准备提交数据
data = {
"id": check_result.get("id", 0),
"sni": check_result.get("sni", "[]"),
"cert": check_result.get("cert", ""),
"key": check_result.get("key", ""),
"expire_time": check_result.get("expire_time", ""),
"update_time": check_result.get("update_time", "")
}
try:
response = self.session.post(
url,
headers=self.headers,
json=data
)
response_data = response.json()
if 'err' in response_data:
print(f"证书配置提交失败: {response_data['err']}")
return None
return response_data
except requests.exceptions.RequestException as e:
print(f"证书配置提交请求失败: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"解析响应数据失败: {str(e)}")
return None
def is_logged_in(self):
"""
检查是否已登录
:return: bool
"""
return self.token is not None
def main():
# 使用示例
api = NanQiangAPI()
# 登录信息
username = "obaby"
password = "obaby@mars"
# 执行登录
result = api.login(username, password)
if result:
print("登录成功:")
print(json.dumps(result, indent=2, ensure_ascii=False))
print(f"Token: {api.token}")
# 获取证书列表
cert_list = api.get_cert_list()
if cert_list:
# 解析证书列表
parsed_certs = api.parse_cert_list(cert_list)
if parsed_certs:
print("解析后的证书列表:")
print(json.dumps(parsed_certs, indent=2, ensure_ascii=False))
# # 删除证书示例
# cert_id = 4 # 要删除的证书ID
# delete_result = api.delete_cert(cert_id)
# if delete_result:
# print(f"证书 {cert_id} 删除成功")
# else:
# print(f"证书 {cert_id} 删除失败")
# 证书检查示例
cert_file = "path/to/cert.pem"
key_file = "path/to/key.pem"
if os.path.exists(cert_file) and os.path.exists(key_file):
return
# 先检查证书
cert_result = api.check_cert_from_files(cert_file, key_file)
if cert_result:
print("证书检查结果:")
print(json.dumps(cert_result, indent=2, ensure_ascii=False))
# 提交证书配置
submit_result = api.submit_cert_config(cert_result)
if submit_result:
print("证书配置提交成功:")
print(json.dumps(submit_result, indent=2, ensure_ascii=False))
else:
print("证书配置提交失败")
else:
print("登录失败")
if __name__ == "__main__":
main()
#!/usr/bin/env python3 import requests import json import jwt from datetime import datetime import os import urllib3 # 禁用 SSL 验证警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) class NanQiangAPI: def __init__(self, base_url="https://lang.bi:443"): self.base_url = base_url self.session = requests.Session() self.session.verify = False # 忽略SSL证书验证 self.token = None self._setup_headers() def _setup_headers(self): """设置请求头""" self.headers = { 'accept': 'application/json, text/plain, */*', 'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7', 'cache-control': 'no-cache', 'content-type': 'application/json', 'origin': self.base_url, 'pragma': 'no-cache', 'priority': 'u=1, i', 'referer': f'{self.base_url}/', 'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"macOS"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-origin', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36' } def _update_headers_with_token(self): """更新请求头,添加token""" if self.token: self.headers['Authorization'] = self.token # 直接使用token,不添加'Bearer '前缀 def delete_cert(self, cert_id): """ 删除指定ID的证书 :param cert_id: 证书ID :return: 删除结果 """ if not self.is_logged_in(): print("请先登录") return None url = f"{self.base_url}/api/v1/certs/{cert_id}" try: response = self.session.delete( url, headers=self.headers ) response_data = response.json() if 'err' in response_data: print(f"删除证书失败: {response_data['err']}") return None # 检查删除是否成功 if response_data.get('result') == 'success' and response_data.get('RowsAffected') > 0: print(f"证书 {cert_id} 删除成功") return True else: print(f"证书 {cert_id} 删除失败: 未找到证书或删除操作未生效") return False except requests.exceptions.RequestException as e: print(f"删除证书请求失败: {str(e)}") return None except json.JSONDecodeError as e: print(f"解析响应数据失败: {str(e)}") return None def parse_cert_list(self, cert_list): """ 解析证书列表数据 :param cert_list: 证书列表数据 :return: 解析后的证书信息列表 """ if not cert_list: return None parsed_certs = [] for cert in cert_list: try: # 解析SNI字段(JSON字符串) sni_list = json.loads(cert.get('sni', '[]')) parsed_cert = { 'id': cert.get('id'), 'sni': sni_list, 'expire_time': cert.get('expire_time'), 'update_time': cert.get('update_time') } parsed_certs.append(parsed_cert) except json.JSONDecodeError as e: print(f"解析SNI字段失败: {str(e)}") continue except Exception as e: print(f"解析证书数据失败: {str(e)}") continue return parsed_certs def get_cert_list(self): """ 获取证书列表 :return: 证书列表 """ if not self.is_logged_in(): print("请先登录") return None url = f"{self.base_url}/api/v1/certs/" try: response = self.session.get( url, headers=self.headers ) response_data = response.json() if 'err' in response_data: print(f"获取证书列表失败: {response_data['err']}") return None return response_data except requests.exceptions.RequestException as e: print(f"获取证书列表请求失败: {str(e)}") return None except json.JSONDecodeError as e: print(f"解析响应数据失败: {str(e)}") return None def login(self, username, password, otp=""): """ 登录接口 :param username: 用户名 :param password: 密码 :param otp: 双因素认证码(可选) :return: 登录响应 """ url = f"{self.base_url}/api/v1/users/login" data = { "usr": username, "pwd": password, "otp": otp } try: response = self.session.post( url, headers=self.headers, json=data ) # 获取响应数据 response_data = response.json() # 检查是否有错误信息 if 'err' in response_data: print(f"登录失败: {response_data['err']}") return None # 保存token if 'token' in response_data: self.token = response_data['token'] self._update_headers_with_token() # # 解析token信息 # try: # # 使用 jwt.decode 替代 jwt.decode_complete # token_data = jwt.decode(self.token, options={"verify_signature": False}) # exp_timestamp = token_data.get('exp') # if exp_timestamp: # exp_date = datetime.fromtimestamp(exp_timestamp) # print(f"Token 有效期至: {exp_date}") # except Exception as e: # print(f"无法解析token信息: {str(e)}") return response_data except requests.exceptions.RequestException as e: print(f"登录请求失败: {str(e)}") return None except json.JSONDecodeError as e: print(f"解析响应数据失败: {str(e)}") return None def check_cert(self, cert_content, key_content, mode=0): """ 检查证书 :param cert_content: 证书内容 :param key_content: 私钥内容 :param mode: 模式,默认为0 :return: 检查结果 """ if not self.is_logged_in(): print("请先登录") return None url = f"{self.base_url}/api/v1/certs/check" # 准备multipart/form-data数据 files = { 'mode': (None, str(mode)), 'cert': (None, cert_content), 'key': (None, key_content) } try: # 临时移除content-type,让requests自动设置 headers = self.headers.copy() headers.pop('content-type', None) response = self.session.post( url, headers=headers, files=files ) response_data = response.json() if 'err' in response_data: print(f"证书检查失败: {response_data['err']}") return None return response_data except requests.exceptions.RequestException as e: print(f"证书检查请求失败: {str(e)}") return None except json.JSONDecodeError as e: print(f"解析响应数据失败: {str(e)}") return None def check_cert_from_files(self, cert_file_path, key_file_path, mode=0): """ 从文件检查证书 :param cert_file_path: 证书文件路径 :param key_file_path: 私钥文件路径 :param mode: 模式,默认为0 :return: 检查结果 """ try: with open(cert_file_path, 'r') as f: cert_content = f.read() with open(key_file_path, 'r') as f: key_content = f.read() return self.check_cert(cert_content, key_content, mode) except FileNotFoundError as e: print(f"文件不存在: {str(e)}") return None except Exception as e: print(f"读取文件失败: {str(e)}") return None def submit_cert_config(self, check_result): """ 提交证书配置 :param check_result: 证书检查的结果数据 :return: 提交结果 """ if not self.is_logged_in(): print("请先登录") return None if not check_result: print("无效的证书检查结果") return None url = f"{self.base_url}/api/v1/certs/config" # 准备提交数据 data = { "id": check_result.get("id", 0), "sni": check_result.get("sni", "[]"), "cert": check_result.get("cert", ""), "key": check_result.get("key", ""), "expire_time": check_result.get("expire_time", ""), "update_time": check_result.get("update_time", "") } try: response = self.session.post( url, headers=self.headers, json=data ) response_data = response.json() if 'err' in response_data: print(f"证书配置提交失败: {response_data['err']}") return None return response_data except requests.exceptions.RequestException as e: print(f"证书配置提交请求失败: {str(e)}") return None except json.JSONDecodeError as e: print(f"解析响应数据失败: {str(e)}") return None def is_logged_in(self): """ 检查是否已登录 :return: bool """ return self.token is not None def main(): # 使用示例 api = NanQiangAPI() # 登录信息 username = "obaby" password = "obaby@mars" # 执行登录 result = api.login(username, password) if result: print("登录成功:") print(json.dumps(result, indent=2, ensure_ascii=False)) print(f"Token: {api.token}") # 获取证书列表 cert_list = api.get_cert_list() if cert_list: # 解析证书列表 parsed_certs = api.parse_cert_list(cert_list) if parsed_certs: print("解析后的证书列表:") print(json.dumps(parsed_certs, indent=2, ensure_ascii=False)) # # 删除证书示例 # cert_id = 4 # 要删除的证书ID # delete_result = api.delete_cert(cert_id) # if delete_result: # print(f"证书 {cert_id} 删除成功") # else: # print(f"证书 {cert_id} 删除失败") # 证书检查示例 cert_file = "path/to/cert.pem" key_file = "path/to/key.pem" if os.path.exists(cert_file) and os.path.exists(key_file): return # 先检查证书 cert_result = api.check_cert_from_files(cert_file, key_file) if cert_result: print("证书检查结果:") print(json.dumps(cert_result, indent=2, ensure_ascii=False)) # 提交证书配置 submit_result = api.submit_cert_config(cert_result) if submit_result: print("证书配置提交成功:") print(json.dumps(submit_result, indent=2, ensure_ascii=False)) else: print("证书配置提交失败") else: print("登录失败") if __name__ == "__main__": main()
账号不要设置动态密码,如果设置了,那就创建一个新账号。
获取证书的脚本参考上一篇文章,对应的路径自己调整。更新证书的代码site_cert_auto_update_tool.py:
#!/usr/bin/env python3
import os
import subprocess
import hashlib
import json
from datetime import datetime
import logging
from baby_nanqiang_api_tools import NanQiangAPI
# Configuration
CERT_SOURCE_DIR = "/root/.acme.sh/h4ck.org.cn_ecc"
CERT_FILE = "fullchain.cer"
KEY_FILE = "h4ck.org.cn.key"
HASH_FILE = "web_cert_hash.json"
CERT_SCRIPT = "get_web_cert.sh"
def setup_logging():
"""设置日志"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('web_cert_update.log'),
logging.StreamHandler()
]
)
def get_file_hash(file_path):
"""计算文件的SHA-256哈希值"""
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def save_cert_hash(cert_hash, key_hash):
"""保存证书和私钥的哈希值到JSON文件"""
with open(HASH_FILE, 'w') as f:
json.dump({
'cert_hash': cert_hash,
'key_hash': key_hash
}, f)
def load_cert_hash():
"""从JSON文件加载证书和私钥的哈希值"""
try:
with open(HASH_FILE, 'r') as f:
data = json.load(f)
return data.get('cert_hash'), data.get('key_hash')
except (FileNotFoundError, json.JSONDecodeError):
return None, None
def run_get_cert_script(script_path=None):
"""
执行获取证书的脚本
:param script_path: 脚本路径,如果为None则使用默认的get_web_cert.sh
:return: bool 是否执行成功
"""
try:
# 如果没有指定脚本路径,使用默认的get_web_cert.sh
if script_path is None:
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), CERT_SCRIPT)
# 检查脚本是否存在
if not os.path.exists(script_path):
logging.error(f"错误: 脚本文件 {script_path} 不存在")
return False
# 检查脚本是否可执行
if not os.access(script_path, os.X_OK):
logging.error(f"错误: 脚本文件 {script_path} 没有执行权限")
return False
# 执行脚本
result = subprocess.run(['sh', script_path],
capture_output=True,
text=True)
# 检查执行结果
if result.returncode == 0:
logging.info("证书获取脚本执行成功")
if result.stdout:
logging.info("脚本输出:\n%s", result.stdout)
return True
else:
logging.error(f"证书获取脚本执行异常,返回码: {result.returncode}")
if result.stderr:
logging.error("异常输出:\n%s", result.stderr)
return True
except Exception as e:
logging.error(f"执行证书获取脚本时发生错误: {str(e)}")
return False
def read_file_content(file_path):
"""读取文件内容"""
try:
with open(file_path, 'r') as f:
return f.read()
except Exception as e:
logging.error(f"读取文件 {file_path} 失败: {str(e)}")
return None
def is_cert_expired(expire_time_str):
"""
检查证书是否过期或即将过期(7天内)
:param expire_time_str: 过期时间字符串
:return: bool 是否过期或即将过期
"""
try:
expire_time = datetime.strptime(expire_time_str, "%Y-%m-%d %H:%M:%S")
now = datetime.now()
days_until_expire = (expire_time - now).days
return days_until_expire <= 7
except Exception as e:
logging.error(f"解析过期时间失败: {str(e)}")
return False
def process_same_sni_certs(api, parsed_certs, current_sni, current_cert_id):
"""
处理具有相同SNI的证书
:param api: API实例
:param parsed_certs: 解析后的证书列表
:param current_sni: 当前证书的SNI
:param current_cert_id: 当前证书的ID
:return: None
"""
# 筛选出相同SNI的证书
same_sni_certs = [cert for cert in parsed_certs
if cert['sni'] == current_sni and cert['id'] != current_cert_id]
if not same_sni_certs:
return
# 按过期时间排序(从早到晚)
same_sni_certs.sort(key=lambda x: datetime.strptime(x['expire_time'], "%Y-%m-%d %H:%M:%S"))
# 检查是否有过期或即将过期的证书
for cert in same_sni_certs:
if is_cert_expired(cert['expire_time']):
logging.info(f"删除过期证书 ID: {cert['id']}")
if not api.delete_cert(cert['id']):
logging.error(f"删除证书 {cert['id']} 失败")
# 检查是否有过期时间相同的证书
if len(same_sni_certs) > 1:
# 获取第一个证书的过期时间作为基准
base_expire_time = same_sni_certs[0]['expire_time']
# 删除过期时间相同的证书(保留第一个)
for cert in same_sni_certs[1:]:
if cert['expire_time'] == base_expire_time:
logging.info(f"删除重复过期时间的证书 ID: {cert['id']}")
if not api.delete_cert(cert['id']):
logging.error(f"删除证书 {cert['id']} 失败")
def main():
# 设置日志
setup_logging()
try:
# 执行证书获取脚本
if not run_get_cert_script():
logging.error("获取证书失败,退出程序")
return
# 检查证书文件是否存在
cert_path = os.path.join(CERT_SOURCE_DIR, CERT_FILE)
key_path = os.path.join(CERT_SOURCE_DIR, KEY_FILE)
if not (os.path.exists(cert_path) and os.path.exists(key_path)):
logging.error("证书文件不存在,退出程序")
return
# 计算新文件的哈希值
new_cert_hash = get_file_hash(cert_path)
new_key_hash = get_file_hash(key_path)
# 获取旧的哈希值
old_cert_hash, old_key_hash = load_cert_hash()
# 检查文件是否发生变化
if new_cert_hash != old_cert_hash or new_key_hash != old_key_hash:
logging.info("证书文件已发生变化,开始更新流程")
# 读取证书和私钥内容
cert_content = read_file_content(cert_path)
key_content = read_file_content(key_path)
if not cert_content or not key_content:
logging.error("读取证书文件失败")
return
# 初始化API
api = NanQiangAPI()
# 登录
if not api.login("obaby", "obaby@mars"):
logging.error("登录失败")
return
# 检查证书
check_result = api.check_cert(cert_content, key_content)
if not check_result:
logging.error("证书检查失败")
return
# 提交证书配置
if not api.submit_cert_config(check_result):
logging.error("提交证书配置失败")
return
# 获取证书列表
cert_list = api.get_cert_list()
if not cert_list:
logging.error("获取证书列表失败")
return
# 解析证书列表
parsed_certs = api.parse_cert_list(cert_list)
if not parsed_certs:
logging.error("解析证书列表失败")
return
# 获取当前证书的SNI
current_sni = check_result.get('sni', '[]')
try:
current_sni = json.loads(current_sni)
except json.JSONDecodeError:
logging.error("解析当前证书SNI失败")
return
# 处理相同SNI的证书
process_same_sni_certs(api, parsed_certs, current_sni, check_result.get('id'))
# 保存新的哈希值
save_cert_hash(new_cert_hash, new_key_hash)
logging.info("证书更新完成")
else:
logging.info("证书文件未发生变化,无需更新")
except Exception as e:
logging.error(f"程序执行出错: {str(e)}", exc_info=True)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
import os
import subprocess
import hashlib
import json
from datetime import datetime
import logging
from baby_nanqiang_api_tools import NanQiangAPI
# Configuration
CERT_SOURCE_DIR = "/root/.acme.sh/h4ck.org.cn_ecc"
CERT_FILE = "fullchain.cer"
KEY_FILE = "h4ck.org.cn.key"
HASH_FILE = "web_cert_hash.json"
CERT_SCRIPT = "get_web_cert.sh"
def setup_logging():
"""设置日志"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('web_cert_update.log'),
logging.StreamHandler()
]
)
def get_file_hash(file_path):
"""计算文件的SHA-256哈希值"""
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def save_cert_hash(cert_hash, key_hash):
"""保存证书和私钥的哈希值到JSON文件"""
with open(HASH_FILE, 'w') as f:
json.dump({
'cert_hash': cert_hash,
'key_hash': key_hash
}, f)
def load_cert_hash():
"""从JSON文件加载证书和私钥的哈希值"""
try:
with open(HASH_FILE, 'r') as f:
data = json.load(f)
return data.get('cert_hash'), data.get('key_hash')
except (FileNotFoundError, json.JSONDecodeError):
return None, None
def run_get_cert_script(script_path=None):
"""
执行获取证书的脚本
:param script_path: 脚本路径,如果为None则使用默认的get_web_cert.sh
:return: bool 是否执行成功
"""
try:
# 如果没有指定脚本路径,使用默认的get_web_cert.sh
if script_path is None:
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), CERT_SCRIPT)
# 检查脚本是否存在
if not os.path.exists(script_path):
logging.error(f"错误: 脚本文件 {script_path} 不存在")
return False
# 检查脚本是否可执行
if not os.access(script_path, os.X_OK):
logging.error(f"错误: 脚本文件 {script_path} 没有执行权限")
return False
# 执行脚本
result = subprocess.run(['sh', script_path],
capture_output=True,
text=True)
# 检查执行结果
if result.returncode == 0:
logging.info("证书获取脚本执行成功")
if result.stdout:
logging.info("脚本输出:\n%s", result.stdout)
return True
else:
logging.error(f"证书获取脚本执行异常,返回码: {result.returncode}")
if result.stderr:
logging.error("异常输出:\n%s", result.stderr)
return True
except Exception as e:
logging.error(f"执行证书获取脚本时发生错误: {str(e)}")
return False
def read_file_content(file_path):
"""读取文件内容"""
try:
with open(file_path, 'r') as f:
return f.read()
except Exception as e:
logging.error(f"读取文件 {file_path} 失败: {str(e)}")
return None
def is_cert_expired(expire_time_str):
"""
检查证书是否过期或即将过期(7天内)
:param expire_time_str: 过期时间字符串
:return: bool 是否过期或即将过期
"""
try:
expire_time = datetime.strptime(expire_time_str, "%Y-%m-%d %H:%M:%S")
now = datetime.now()
days_until_expire = (expire_time - now).days
return days_until_expire <= 7
except Exception as e:
logging.error(f"解析过期时间失败: {str(e)}")
return False
def process_same_sni_certs(api, parsed_certs, current_sni, current_cert_id):
"""
处理具有相同SNI的证书
:param api: API实例
:param parsed_certs: 解析后的证书列表
:param current_sni: 当前证书的SNI
:param current_cert_id: 当前证书的ID
:return: None
"""
# 筛选出相同SNI的证书
same_sni_certs = [cert for cert in parsed_certs
if cert['sni'] == current_sni and cert['id'] != current_cert_id]
if not same_sni_certs:
return
# 按过期时间排序(从早到晚)
same_sni_certs.sort(key=lambda x: datetime.strptime(x['expire_time'], "%Y-%m-%d %H:%M:%S"))
# 检查是否有过期或即将过期的证书
for cert in same_sni_certs:
if is_cert_expired(cert['expire_time']):
logging.info(f"删除过期证书 ID: {cert['id']}")
if not api.delete_cert(cert['id']):
logging.error(f"删除证书 {cert['id']} 失败")
# 检查是否有过期时间相同的证书
if len(same_sni_certs) > 1:
# 获取第一个证书的过期时间作为基准
base_expire_time = same_sni_certs[0]['expire_time']
# 删除过期时间相同的证书(保留第一个)
for cert in same_sni_certs[1:]:
if cert['expire_time'] == base_expire_time:
logging.info(f"删除重复过期时间的证书 ID: {cert['id']}")
if not api.delete_cert(cert['id']):
logging.error(f"删除证书 {cert['id']} 失败")
def main():
# 设置日志
setup_logging()
try:
# 执行证书获取脚本
if not run_get_cert_script():
logging.error("获取证书失败,退出程序")
return
# 检查证书文件是否存在
cert_path = os.path.join(CERT_SOURCE_DIR, CERT_FILE)
key_path = os.path.join(CERT_SOURCE_DIR, KEY_FILE)
if not (os.path.exists(cert_path) and os.path.exists(key_path)):
logging.error("证书文件不存在,退出程序")
return
# 计算新文件的哈希值
new_cert_hash = get_file_hash(cert_path)
new_key_hash = get_file_hash(key_path)
# 获取旧的哈希值
old_cert_hash, old_key_hash = load_cert_hash()
# 检查文件是否发生变化
if new_cert_hash != old_cert_hash or new_key_hash != old_key_hash:
logging.info("证书文件已发生变化,开始更新流程")
# 读取证书和私钥内容
cert_content = read_file_content(cert_path)
key_content = read_file_content(key_path)
if not cert_content or not key_content:
logging.error("读取证书文件失败")
return
# 初始化API
api = NanQiangAPI()
# 登录
if not api.login("obaby", "obaby@mars"):
logging.error("登录失败")
return
# 检查证书
check_result = api.check_cert(cert_content, key_content)
if not check_result:
logging.error("证书检查失败")
return
# 提交证书配置
if not api.submit_cert_config(check_result):
logging.error("提交证书配置失败")
return
# 获取证书列表
cert_list = api.get_cert_list()
if not cert_list:
logging.error("获取证书列表失败")
return
# 解析证书列表
parsed_certs = api.parse_cert_list(cert_list)
if not parsed_certs:
logging.error("解析证书列表失败")
return
# 获取当前证书的SNI
current_sni = check_result.get('sni', '[]')
try:
current_sni = json.loads(current_sni)
except json.JSONDecodeError:
logging.error("解析当前证书SNI失败")
return
# 处理相同SNI的证书
process_same_sni_certs(api, parsed_certs, current_sni, check_result.get('id'))
# 保存新的哈希值
save_cert_hash(new_cert_hash, new_key_hash)
logging.info("证书更新完成")
else:
logging.info("证书文件未发生变化,无需更新")
except Exception as e:
logging.error(f"程序执行出错: {str(e)}", exc_info=True)
if __name__ == "__main__":
main()
#!/usr/bin/env python3 import os import subprocess import hashlib import json from datetime import datetime import logging from baby_nanqiang_api_tools import NanQiangAPI # Configuration CERT_SOURCE_DIR = "/root/.acme.sh/h4ck.org.cn_ecc" CERT_FILE = "fullchain.cer" KEY_FILE = "h4ck.org.cn.key" HASH_FILE = "web_cert_hash.json" CERT_SCRIPT = "get_web_cert.sh" def setup_logging(): """设置日志""" logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('web_cert_update.log'), logging.StreamHandler() ] ) def get_file_hash(file_path): """计算文件的SHA-256哈希值""" sha256_hash = hashlib.sha256() with open(file_path, "rb") as f: for byte_block in iter(lambda: f.read(4096), b""): sha256_hash.update(byte_block) return sha256_hash.hexdigest() def save_cert_hash(cert_hash, key_hash): """保存证书和私钥的哈希值到JSON文件""" with open(HASH_FILE, 'w') as f: json.dump({ 'cert_hash': cert_hash, 'key_hash': key_hash }, f) def load_cert_hash(): """从JSON文件加载证书和私钥的哈希值""" try: with open(HASH_FILE, 'r') as f: data = json.load(f) return data.get('cert_hash'), data.get('key_hash') except (FileNotFoundError, json.JSONDecodeError): return None, None def run_get_cert_script(script_path=None): """ 执行获取证书的脚本 :param script_path: 脚本路径,如果为None则使用默认的get_web_cert.sh :return: bool 是否执行成功 """ try: # 如果没有指定脚本路径,使用默认的get_web_cert.sh if script_path is None: script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), CERT_SCRIPT) # 检查脚本是否存在 if not os.path.exists(script_path): logging.error(f"错误: 脚本文件 {script_path} 不存在") return False # 检查脚本是否可执行 if not os.access(script_path, os.X_OK): logging.error(f"错误: 脚本文件 {script_path} 没有执行权限") return False # 执行脚本 result = subprocess.run(['sh', script_path], capture_output=True, text=True) # 检查执行结果 if result.returncode == 0: logging.info("证书获取脚本执行成功") if result.stdout: logging.info("脚本输出:\n%s", result.stdout) return True else: logging.error(f"证书获取脚本执行异常,返回码: {result.returncode}") if result.stderr: logging.error("异常输出:\n%s", result.stderr) return True except Exception as e: logging.error(f"执行证书获取脚本时发生错误: {str(e)}") return False def read_file_content(file_path): """读取文件内容""" try: with open(file_path, 'r') as f: return f.read() except Exception as e: logging.error(f"读取文件 {file_path} 失败: {str(e)}") return None def is_cert_expired(expire_time_str): """ 检查证书是否过期或即将过期(7天内) :param expire_time_str: 过期时间字符串 :return: bool 是否过期或即将过期 """ try: expire_time = datetime.strptime(expire_time_str, "%Y-%m-%d %H:%M:%S") now = datetime.now() days_until_expire = (expire_time - now).days return days_until_expire <= 7 except Exception as e: logging.error(f"解析过期时间失败: {str(e)}") return False def process_same_sni_certs(api, parsed_certs, current_sni, current_cert_id): """ 处理具有相同SNI的证书 :param api: API实例 :param parsed_certs: 解析后的证书列表 :param current_sni: 当前证书的SNI :param current_cert_id: 当前证书的ID :return: None """ # 筛选出相同SNI的证书 same_sni_certs = [cert for cert in parsed_certs if cert['sni'] == current_sni and cert['id'] != current_cert_id] if not same_sni_certs: return # 按过期时间排序(从早到晚) same_sni_certs.sort(key=lambda x: datetime.strptime(x['expire_time'], "%Y-%m-%d %H:%M:%S")) # 检查是否有过期或即将过期的证书 for cert in same_sni_certs: if is_cert_expired(cert['expire_time']): logging.info(f"删除过期证书 ID: {cert['id']}") if not api.delete_cert(cert['id']): logging.error(f"删除证书 {cert['id']} 失败") # 检查是否有过期时间相同的证书 if len(same_sni_certs) > 1: # 获取第一个证书的过期时间作为基准 base_expire_time = same_sni_certs[0]['expire_time'] # 删除过期时间相同的证书(保留第一个) for cert in same_sni_certs[1:]: if cert['expire_time'] == base_expire_time: logging.info(f"删除重复过期时间的证书 ID: {cert['id']}") if not api.delete_cert(cert['id']): logging.error(f"删除证书 {cert['id']} 失败") def main(): # 设置日志 setup_logging() try: # 执行证书获取脚本 if not run_get_cert_script(): logging.error("获取证书失败,退出程序") return # 检查证书文件是否存在 cert_path = os.path.join(CERT_SOURCE_DIR, CERT_FILE) key_path = os.path.join(CERT_SOURCE_DIR, KEY_FILE) if not (os.path.exists(cert_path) and os.path.exists(key_path)): logging.error("证书文件不存在,退出程序") return # 计算新文件的哈希值 new_cert_hash = get_file_hash(cert_path) new_key_hash = get_file_hash(key_path) # 获取旧的哈希值 old_cert_hash, old_key_hash = load_cert_hash() # 检查文件是否发生变化 if new_cert_hash != old_cert_hash or new_key_hash != old_key_hash: logging.info("证书文件已发生变化,开始更新流程") # 读取证书和私钥内容 cert_content = read_file_content(cert_path) key_content = read_file_content(key_path) if not cert_content or not key_content: logging.error("读取证书文件失败") return # 初始化API api = NanQiangAPI() # 登录 if not api.login("obaby", "obaby@mars"): logging.error("登录失败") return # 检查证书 check_result = api.check_cert(cert_content, key_content) if not check_result: logging.error("证书检查失败") return # 提交证书配置 if not api.submit_cert_config(check_result): logging.error("提交证书配置失败") return # 获取证书列表 cert_list = api.get_cert_list() if not cert_list: logging.error("获取证书列表失败") return # 解析证书列表 parsed_certs = api.parse_cert_list(cert_list) if not parsed_certs: logging.error("解析证书列表失败") return # 获取当前证书的SNI current_sni = check_result.get('sni', '[]') try: current_sni = json.loads(current_sni) except json.JSONDecodeError: logging.error("解析当前证书SNI失败") return # 处理相同SNI的证书 process_same_sni_certs(api, parsed_certs, current_sni, check_result.get('id')) # 保存新的哈希值 save_cert_hash(new_cert_hash, new_key_hash) logging.info("证书更新完成") else: logging.info("证书文件未发生变化,无需更新") except Exception as e: logging.error(f"程序执行出错: {str(e)}", exc_info=True) if __name__ == "__main__": main()
添加定时任务,每天,或者每几天:
0 2 * * * /usr/bin/python3 /home/soft/baby-nanqiang-cert-tools/site_cert_auto_update_tool.py >> /home/soft/baby-nanqiang-cert-tools/web_cert_manager.log 2>&1
0 2 * * * /usr/bin/python3 /home/soft/baby-nanqiang-cert-tools/site_cert_auto_update_tool.py >> /home/soft/baby-nanqiang-cert-tools/web_cert_manager.log 2>&1
0 2 * * * /usr/bin/python3 /home/soft/baby-nanqiang-cert-tools/site_cert_auto_update_tool.py >> /home/soft/baby-nanqiang-cert-tools/web_cert_manager.log 2>&1
最终效果:
35 comments
为什么要谢你,那算推广吗?我写了那么多,怎么没人来谢我,哼
我这是推广加生态完善好吧。丰富他们的技术能力,嘻嘻
有推广费吗
然鹅 并没有
自动申请及部署,能部署到cdn上就牛逼了!你这是企业级别的要是能部署到宝塔面板,用户量可能就更大了
cdn有接口也可以 但是没必要 多数cdn都支持自动申请
华为云的CDN就不太行,不过华为云的API是真丰富,调用很方便。
纯cdn不带防御 又是大厂的 直接不敢用
随时被刷
封面图越来越抽象了 哈哈哈哈
哈哈哈
看不懂 但是封面图我的菜~
哈哈哈 好歹有一样
现在SSL周期越来越短了,CF自动SSL挺省心的。 对了我看雷池蛮不错的。
也试过 太多功能不对个人版开放
才发现真是这样已经卸载了雷池
主要是专业版授权太贵了 一年四千
有空我也试试南墙 主要没有多余的机器😂
可以部署在一台机器上
uucorp 是个昵称还是个应用?
组织昵称,哈哈哈,官方名称
现在还有免费的一年期的吗?前段时间我在一个群里问过,有人说有,但是没下文。一直用的腾讯云申请的,手动太麻烦了。
早就没了,腾讯的的确麻烦。
每次看到“南墙”,都有一种不撞南墙不死心的想法
现在免费的SSL证书时间真的越来越短了。卖的证书,加起来比我服务器贵了好几倍,想方设法赚我们的血汗钱,太南了!
就是这个意思,哈哈哈。撞南墙
习惯了一年更换一次SSL证书
一年的有效期真不错,现在找不到免费一年的了
我就2域名 直接搞了付费的了
PS:你这咋没评论回复提醒邮件?
有的 进垃圾邮件了吧
这个回复有了 上个没有
我在宝塔里设了更新SSL证书,不知道效果如何,等到期时候看看。
宝塔的应该可以
之前一年更新一次证书,倒可以接受,现在三个月一次,证书又多,还得靠自动化更新方便。
是啊,没有自动化真的太麻烦了
SSL证书我看那个1panel可以定时更新,就从宝塔换过来了,还挺好用的,但是套到防火墙或CDN还是要自己手动去下载证书传过去,这个南墙是自己部署CDN防火墙的应用吗?我刚搜官网去看了下,懒了,不想折腾了。
waf 防火墙,如果直接暴露 ip 没有任何防御,容易给打挂了,尤其是无聊的人挺多的。