先说一下基本架构:NAS+Virtual Machine Manager+CentOS-Strem9+BT面板+Lucky实现大内网穿透
群晖自带的WebStation安装完Wordpress后,各种不方便,并且输完IP后还要手动加上5000才能访问DSM,这一点让我非常不爽,于是还是想在熟悉的Linux上装博客算了,于是先在虚拟机里装上CENTOS,然后安装BT面板
重点是怎么实现STUN穿透后IP和端口的不固定还能访问,我的办法是用Python实现自动登录上Lucky,然后把获取到的IP端口信息,更新到mysql数据库和Nginx配置文件中。
在ChatGPT的帮助下,写好了Python代码。
主要实现以下几个功能:
1、自动模拟登录Lucky来获取新的IP和端口
2、获取到信息同时webhook到微信,实现随时可以查看最新信息,原来想用邮件,这个比邮件更快。
3、比对上一次获取到的信息,如果没有变化就啥也不做
4、自动将新IP端口写入Mysql
5、自动将新端口写到网站nginx配置信息里,然后重载nginx让新配置生效
然后在BT的计划任务中,设置成每5分钟跑一次。cd /root&&python refresh-stun.py
以下是所有代码分享和记录,省得以后忘记了,博客的意义就在于此了。以下代码存为XX.PY后,还要和网站的.conf文件放一起,然后放到服务器某个目录下面,就可以用了。
import os
import datetime
import mysql.connector
import re
import subprocess
import requests
from playwright.sync_api import sync_playwright
import time
def send_mail(Alist_100, nas100, Blog100):
url = "http://wxpusher.zjiecode.com/api/send/message"
headers = {
"Content-Type": "application/json"
}
payload = {
"appToken": "xxxxxxxxxxxxxxxxxxxx",
"title": "公网IP变化通知",
"content": "<h1>公网信息变化了</h1><br><h2>Alist_100新IP:{0}<br><copy data-clipboard-text=http://{0}>点击复制Alist_100 IP</copy><br><br>nas100新IP:{1}<br><copy data-clipboard-text=http://{1}>点击复制nas100 IP</copy><br><br>Blog100新IP:{2}<br><copy data-clipboard-text=http://wp.fangfa.me:{3}>点击复制wp.fangfa.me新地址</copy><br><br></h2>".format(Alist_100, nas100, Blog100, Blog100.split(":")[-1]),
"contentType": 2,
"uids": ["xxxxxxxxxxxxx"]
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
print("数据发送成功")
return "数据发送成功"
else:
print(f"数据发送失败,状态码: {response.status_code}")
print(f"响应内容: {response.text}")
return f"数据发送失败,状态码: {response.status_code}"
def log_action(log_file_path, timestamp, Alist_100, nas100, Blog100, action, mail_status, db_status):
max_lines = 1000
lines = []
if os.path.exists(log_file_path):
with open(log_file_path, "r") as log_file:
lines = log_file.readlines()
if len(lines) >= max_lines:
lines = []
lines.append(f"{timestamp} {Alist_100} {nas100} {Blog100} {action} {mail_status} {db_status}\n")
with open(log_file_path, "w") as log_file:
log_file.writelines(lines)
def read_last_values(log_file_path):
if os.path.exists(log_file_path):
with open(log_file_path, "r") as log_file:
lines = log_file.readlines()
for line in reversed(lines):
parts = line.strip().rsplit(" ", 6)
if len(parts) == 7 and parts[-3] not in ["出错了:", ""]:
return parts[1], parts[2], parts[3]
return "", "", ""
def replace_port_in_conf(port):
with open('./seofangfa.com.conf', "r", encoding="utf-8") as file:
content = file.read()
updated_content = re.sub(r'XXXXXXXXXXXXXX', port, content)
with open('/www/server/panel/vhost/nginx/seofangfa.com.conf', "w", encoding="utf-8") as file:
file.write(updated_content)
try:
result = subprocess.run(['sudo', 'nginx', '-s', 'reload'], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(f"Nginx 配置已重载: {result.stdout.decode()}")
except subprocess.CalledProcessError as e:
print(f"重载 Nginx 配置失败: {e.stderr.decode()}")
def update_database(Blog100):
conn = None
try:
print("正在连接到数据库...")
conn = mysql.connector.connect(
host="192.168.1.50",
user="ssssssssssssss",
password="ssssssssssssssss",
database="sssssssssssssssss"
)
cursor = conn.cursor()
print("数据库连接成功")
port = Blog100.split(":")[-1]
# replace_port_in_conf(port)
cursor.execute("UPDATE wp_options SET option_value = %s WHERE option_name = 'siteurl'", (f"http://blog.fangfa.me:{port}",))
cursor.execute("UPDATE wp_options SET option_value = %s WHERE option_name = 'home'", (f"http://blog.fangfa.me:{port}",))
conn.commit()
print("数据库更新成功")
return "数据库更新成功"
except mysql.connector.Error as err:
print(f"数据库更新失败: {err}")
return f"数据库更新失败: {err}"
finally:
if conn and conn.is_connected():
cursor.close()
conn.close()
print("数据库连接已关闭")
def run(playwright):
browser = playwright.chromium.launch(headless=True)
context = browser.new_context()
page = context.new_page()
log_file_path = "run_log.txt"
try:
for attempt in range(3):
print("打开登录页面")
page.goto("http://192.168.1.50:16601/ssssssssss/#/login")
page.locator("input").first.wait_for()
print("输入用户名和密码")
page.locator("input").first.fill('ssssssssssss')
page.locator("input[type='password']").fill('ssssssssssssssssss')
print("点击登录按钮")
page.locator("button").first.click()
page.wait_for_load_state('networkidle')
content = page.content()
if '退出登录' in content:
print('登录成功')
page.goto("http://192.168.1.50:16601/sssssssssss/#/stun")
page.wait_for_load_state('networkidle')
print("获取第一个目标元素的值")
Alist_100 = page.locator("#pane-list table tbody tr:nth-child(2) td.el-descriptions__content > span.el-tag.el-tooltip__trigger > span").first.inner_text()
print(f'Alist_100: {Alist_100}')
print("获取第二个目标元素的值")
nas100 = page.locator("#pane-list table tbody tr:nth-child(7) td.el-descriptions__content > span.el-tag.el-tooltip__trigger > span").first.inner_text()
print(f'nas100: {nas100}')
print("获取第三个目标元素的值")
Blog100 = page.locator("#pane-list table tbody tr:nth-child(12) td.el-descriptions__content > span.el-tag.el-tooltip__trigger > span").first.inner_text()
print(f'Blog: {Blog100}')
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print("读取上一次的值")
last_Alist_100, last_nas100, last_Blog100 = read_last_values(log_file_path)
db_status = "未变化,无需更新数据库"
print("比较新值和旧值")
if (Alist_100 == last_Alist_100 and nas100 == last_nas100 and Blog100 == last_Blog100):
action = "值没有变化,不发送通知"
mail_status = "无"
print(action)
else:
action = "值变化了,发送通知"
print(action)
mail_status = send_mail(Alist_100, nas100, Blog100)
# db_status = update_database(Blog100)
port = Blog100.split(":")[-1]
replace_port_in_conf(port)
print("记录日志")
log_action(log_file_path, timestamp, Alist_100, nas100, Blog100, action, mail_status, db_status)
break # 成功获取到值后跳出重试循环
else:
print('登录失败')
log_action(log_file_path, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "N/A", "N/A", "N/A", '登录失败', "N/A", "N/A")
if Alist_100 == "未获取" and nas100 == "未获取" and Blog100 == "未获取":
print("所有值均为未获取,尝试重启 Lucky 并重试")
subprocess.run('sudo nmcli connection up ens3', capture_output=True, text=True, shell=True)
subprocess.run('sudo systemctl restart NetworkManager', capture_output=True, text=True, shell=True)
subprocess.run('sudo systemctl restart lucky.daji', capture_output=True, text=True, shell=True)
time.sleep(5) # 等待5秒钟
continue # 重试
else:
break # 如果不是所有值均为未获取,则不重试
except Exception as e:
print(f'出错了: {e}')
log_action(log_file_path, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "N/A", "N/A", "N/A", f'出错了: {e}', "N/A", "N/A")
finally:
print("关闭浏览器")
browser.close()
with sync_playwright() as playwright:
run(playwright)


评论