【终极版】Hetzner 流量监控自动重建

Q:重建后为什么 IP 不是一样的?

A:这是一个非常正常的现象,这是 ** 云服务器(Cloud Computing)** 的核心机制决定的。

核心原因:IP 是“借”来的,不是“买断”的

在 Hetzner(以及 AWS、阿里云等几乎所有云厂商)中,IP 地址和服务器实例是解耦的:

  1. 当你创建服务器时:Hetzner 从他们巨大的“IP 池”里随机捞一个闲置的 IP 借给你用。

  2. 当你删除服务器时 (server.delete()):你相当于“退房”了。这个 IP 会立刻归还回到池子里,洗干净等待分配给下一个客户。

  3. 当你重建服务器时 (client.servers.create()):Hetzner 再次从池子里随机捞一个 IP 给你。因为之前的 IP 可能已经被别人拿走了,或者还在冷却中,所以你几乎 100% 会拿到一个新的 IP。

这就像你去住酒店:你退房了(删机),下次再来住(重建),前台大概率会给你分一个新的房间号(新 IP),除非你专门付钱“包房”。


这种机制对你有好处吗?

对于你的这个“流量自动重置”项目来说,IP 变了其实是一件好事

  1. 彻底的身份重置:新 IP 意味着你在网络上是一个全新的身份。如果你之前的 IP 因为跑流量被某些网站限速或拉黑了,新 IP 就是一条好汉。

  2. 规避关联:新 IP 能够确保你的新服务器和旧服务器在逻辑上没有任何关联,这对于“刷流量”或“跑业务”来说通常更安全。


如果我非要保持 IP 不变怎么办?

如果你因为域名解析或者其他原因,必须使用同一个 IP,Hetzner 也是支持的,但需要加钱并且修改配置。

这个功能叫 Primary IP (保留 IP)

操作思路(仅供了解):

  1. 你需要在 Hetzner 后台专门购买一个 Primary IP(IPv4 通常需要额外月租,约 €0.5/ 月)。

  2. 这个 IP 就变成了你账户里的固定资产,即使没有服务器挂在上面,它也是你的。

  3. 修改脚本:在 client.servers.create 的时候,不能让它自动分配 IP,而是指定使用你购买的这个 primary_ip_id

❌ 我不建议你在这个项目里这么做,原因如下:

  1. 增加成本:要多花钱。

  2. 可能影响流量计算:Hetzner 的流量限额通常是跟着服务器实例走的。如果你一直用同一个固定 IP,虽然理论上新服务器是新的 20TB,但为了保险起见(避免风控或逻辑混淆),更换 IP 是最稳妥的“清零”方式

既然 IP 变了,我怎么连接?

因为你已经有 Telegram 机器人了,这完全不是问题:

  1. 看机器人通知:每次重建成功,机器人都会发给你 新 IP: 1.2.3.4

  2. 哪吒探针:因为你用了探针,新服务器启动后,探针会自动把新 IP 上报给面板,你甚至都不用管,面板上的链接会自动更新。

结论:习惯就好,这是云服务器自动化的常态。IP 变了说明你的自动化脚本真的在工作!

Q:但是我要去 vertex 里面修改或者重新添加这个新的 IP 的 QB 才行啊,还是麻烦

A:确实,如果还要手动去 Vertex 里面改 IP,那这根本算不上“自动化”,太痛苦了。

要解决这个问题,有两个方案:“不花钱的方案(DDNS)”“花小钱的方案(保留 IP)”

鉴于你已经在用 Docker 和 Python 脚本了,我强烈推荐方案一(DDNS),这是 PT 圈子的标准玩法。


方案一:自动更新域名 (DDNS) —— 推荐 🔥

原理

  1. 你准备一个域名(比如 hz.yourdomain.com),托管在 Cloudflare

  2. 我们在脚本里加一段代码:每次重建完服务器,脚本自动去 Cloudflare 把这个域名的解析记录改成新的 IP

  3. Vertex 设置:在 Vertex 里,把下载器的地址从 IP 改成域名 hz.yourdomain.com

这样不管 IP 怎么变,Vertex 只要认准这个域名就在行了。

操作步骤

1. 准备工作 (Cloudflare)

你需要有一个托管在 Cloudflare 的域名。

  1. 获取 Zone ID:在 Cloudflare 域名概览页右下角可以看到 Zone ID

  2. 获取 API Token

    • 点右上角头像 -> My Profile -> API Tokens -> Create Token。

    • 选择模板 Edit zone DNS

    • 生成后复制 Token。

  3. 创建一条初始记录

    • 在 DNS 设置里,先随便添加一条 A 记录,比如 hz 指向随便一个 IP(脚本一会儿会自动改它)。

    • 关键点:把那个小云朵(Proxy)关掉(变成灰色),仅 DNS 模式。否则 Vertex 连不上 QB 的端口。

2. 修改 OVH 上的脚本 (main.py)

我们需要在脚本里增加一段“通知 Cloudflare 改 IP”的代码。

编辑脚本:

Bash

nano ~/hetzner_monitor/main.py

直接替换为以下最终极版本代码:

Python

# -*- coding: utf-8 -*-
import time
import threading
import telebot
import requests
from hcloud import Client
from hcloud.images.domain import Image
from hcloud.server_types.domain import ServerType
from hcloud.locations.domain import Location

# ================= ⚠️ 配置区域 (请仔细检查) =================

# 1. Hetzner 基础配置
HETZNER_TOKEN = "你的_HETZNER_API_TOKEN"
SERVER_NAME = "My-Server"       # 必须与 Hetzner 后台名称完全一致
SNAPSHOT_ID = 12345678          # 你的快照 ID
LOCATION = "nbg1"
SERVER_TYPE = "cx22"
TRAFFIC_LIMIT_TB = 18.0         # 流量阈值
CHECK_INTERVAL = 300            # 5分钟检查一次

# 2. Telegram 配置
TG_BOT_TOKEN = "你的_TG_BOT_TOKEN"
TG_CHAT_ID = "你的_TG_CHAT_ID"

# 3. Cloudflare DDNS 配置
CF_ENABLE = True                # 是否开启
CF_API_TOKEN = "你的_CF_API_TOKEN"
CF_ZONE_ID = "你的_CF_ZONE_ID"
CF_RECORD_NAME = "hz.yourdomain.com" # 完整域名

# 4. 提醒分级
NOTIFY_LEVELS = [10, 20, 30, 40, 50, 60, 70, 80, 90]

# ==========================================================

LIMIT_BYTES = TRAFFIC_LIMIT_TB * 1024 * 1024 * 1024 * 1024
client = Client(token=HETZNER_TOKEN)
bot = telebot.TeleBot(TG_BOT_TOKEN)
rebuild_lock = threading.Lock()
current_notify_level = 0

def update_cloudflare(new_ip):
    """更新 Cloudflare DNS"""
    if not CF_ENABLE: return "DNS功能未开启"
    print(f"📡 正在向 Cloudflare 推送新 IP: {new_ip}")
    
    headers = {
        "Authorization": f"Bearer {CF_API_TOKEN}",
        "Content-Type": "application/json"
    }
    try:
        # 获取记录ID
        list_url = f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records?name={CF_RECORD_NAME}"
        resp = requests.get(list_url, headers=headers).json()
        
        if not resp.get('success'):
            return f"❌ CF API 错误: {resp.get('errors')}"
        
        if not resp['result']:
            return f"❌ 域名记录不存在: {CF_RECORD_NAME} (请先在 CF 后台手动添加一条A记录)"
            
        record_id = resp['result'][0]['id']
        
        # 更新记录
        update_url = f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records/{record_id}"
        data = {
            "type": "A",
            "name": CF_RECORD_NAME,
            "content": new_ip,
            "ttl": 60,
            "proxied": False
        }
        update_resp = requests.put(update_url, headers=headers, json=data).json()
        
        if update_resp.get('success'):
            return f"✅ DNS 已更新 -> {new_ip}"
        else:
            return f"❌ DNS 更新失败: {update_resp.get('errors')}"
    except Exception as e:
        return f"❌ 网络请求异常: {str(e)}"

def get_server_usage():
    """获取流量 (带错误详情)"""
    try:
        server = client.servers.get_by_name(SERVER_NAME)
        if server is None:
            return None, "❌ 未找到服务器 (名称错误或正在重建)"
        
        current_usage = server.outgoing_traffic
        usage_percent = (current_usage / LIMIT_BYTES) * 100
        return current_usage, {
            "tb": current_usage / (1024 ** 4),
            "gb": current_usage / (1024 ** 3),
            "percent": usage_percent,
            "ip": server.public_net.ipv4.ip
        }
    except Exception as e:
        return None, f"❌ API连接失败: {str(e)}"

def perform_rebuild(source="自动监控"):
    global current_notify_level
    if not rebuild_lock.acquire(blocking=False): return

    try:
        bot.send_message(TG_CHAT_ID, f"🔄 **[{source}]** 开始重建...", parse_mode="Markdown")
        
        # 1. 删除
        try:
            server = client.servers.get_by_name(SERVER_NAME)
            if server: 
                server.delete()
                print("旧服务器已删除,等待 15s...")
                time.sleep(15)
        except Exception as e: print(f"删除跳过: {e}")

        # 2. 重建
        print(f"正在创建新服务器 (Snapshot: {SNAPSHOT_ID})...")
        new_server = client.servers.create(
            name=SERVER_NAME,
            server_type=ServerType(name=SERVER_TYPE),
            image=Image(id=SNAPSHOT_ID),
            location=Location(name=LOCATION)
        )
        new_ip = new_server.server.public_net.ipv4.ip
        current_notify_level = 0
        
        # 3. 更新 DNS
        dns_result = update_cloudflare(new_ip)
        
        msg = f"✅ **重建完成**\nIP: `{new_ip}`\n{dns_result}"
        bot.send_message(TG_CHAT_ID, msg, parse_mode="Markdown")
        print("重建流程结束,冷却 60s...")
        time.sleep(60)
        
    except Exception as e:
        bot.send_message(TG_CHAT_ID, f"❌ 重建炸了: {e}")
        print(f"重建严重错误: {e}")
    finally:
        rebuild_lock.release()

# --- Telegram 命令 ---
@bot.message_handler(commands=['start', 'help'])
def h(m): 
    if str(m.chat.id) == TG_CHAT_ID: 
        bot.reply_to(m, "🤖 监控运行中\n/ll - 查流量\n/rebuild - 强制重建")

@bot.message_handler(commands=['liuliang', 'll', 'status'])
def s(m):
    if str(m.chat.id) != TG_CHAT_ID: return
    u, d = get_server_usage()
    if u is not None:
        bot.reply_to(m, f"📊 流量: **{d['tb']:.4f} TB** ({d['percent']:.2f}%)\nIP: `{d['ip']}`", parse_mode="Markdown")
    else:
        bot.reply_to(m, f"⚠️ 查询失败: {d}")

@bot.message_handler(commands=['rebuild'])
def r(m):
    if str(m.chat.id) != TG_CHAT_ID: return
    threading.Thread(target=perform_rebuild, args=("手动指令",)).start()

# --- 监控循环 ---
def monitor_loop():
    global current_notify_level
    print("🚀 监控线程启动...")
    
    # === 启动自检 (开机先刷一次DNS) ===
    print("🔍 正在执行开机自检...")
    u_start, d_start = get_server_usage()
    if u_start is not None:
        print(f"✅ API 连接成功,当前 IP: {d_start['ip']}")
        print(update_cloudflare(d_start['ip']))
        # 初始化通知等级
        for l in NOTIFY_LEVELS:
            if d_start['percent'] >= l: current_notify_level = l
    else:
        print(f"⚠️ 开机自检失败: {d_start}")
    # ================================

    while True:
        try:
            u, d = get_server_usage()
            
            if u is not None:
                # 成功获取数据
                print(f"[{time.strftime('%H:%M')}] 状态: {d['percent']:.2f}% | IP: {d['ip']}")
                
                # 检查分级提醒
                for l in NOTIFY_LEVELS:
                    if d['percent'] >= l and l > current_notify_level:
                        bot.send_message(TG_CHAT_ID, f"⚠️ 流量提醒: {l}% 已用 ({d['tb']:.4f} TB)")
                        current_notify_level = l
                
                # 检查超标
                if u > LIMIT_BYTES: 
                    perform_rebuild("流量超标")
            else:
                # 获取失败 (这里会打印错误原因!)
                print(f"[{time.strftime('%H:%M')}] ⚠️ 获取失败: {d}")
                
        except Exception as e:
            print(f"主循环异常: {e}")
            
        time.sleep(CHECK_INTERVAL)

if __name__ == "__main__":
    # 1. 启动监控线程
    t = threading.Thread(target=monitor_loop, daemon=True)
    t.start()
    
    # 2. 启动机器人
    print("🤖 正在连接 Telegram...")
    try:
        bot.get_me() # 测试连接
        print("✅ Telegram 连接成功,开始监听...")
        bot.send_message(TG_CHAT_ID, "🚀 Hetzner 监控机器人已上线 (终极版)")
        bot.infinity_polling()
    except Exception as e:
        print(f"❌ Telegram 连接失败 (检查 Token 或 网络): {e}")
        # 即使 TG 挂了,监控线程依然要跑,防止流量超标
        while True: time.sleep(60)

3. 重启容器

保存退出后,执行:

cd ~/hetzner_monitor

# 1. 重新构建 (确保 requests 等库都在)
docker build -t hetzner-bot .

# 2. 重启
docker rm -f hetzner-monitor
docker run -d --name hetzner-monitor --restart always hetzner-bot

# 3. 立即查看日志
docker logs -f hetzner-monitor

4. 最后一步:修改 Vertex 设置

  1. 进入你的 Vertex 面板

  2. 找到你添加的那个下载器(Hetzner 那台)。

  3. Host/IP 这一栏,改成你的域名:hz.yourdomain.com

  4. 保存。

完工!

以后流程变成了:

  1. 服务器炸了 -> 重建。

  2. 脚本拿到新 IP -> 告诉 Cloudflare。

  3. Cloudflare 更新解析。

  4. Vertex 实际上连的是域名,它每分钟会解析一次域名,自动就连上新 IP 了。


方案二:花钱买固定 IP (保留 IP)

如果你实在不想折腾域名,或者觉得 DNS 生效有 1 分钟延迟很不爽,可以选择这个方案。

  1. 购买 IP

    • Hetzner 后台 -> Select Project -> Primary IPs -> Buy Primary IP

    • 选择 IPv4,Location 选你服务器所在的机房(如 nbg1)。

    • 花费:约 €0.5 / 月。

    • 记住这个 Primary IP 的 ID

  2. 修改脚本:

    你需要修改 main.py 里创建服务器的那一段代码,告诉它“别随机分配 IP,用我买的这个”。

    你需要先安装库更新支持:pip install hcloud (容器里已有)

    代码修改处(client.servers.create 部分):

    Python

    # 需要引入新的类
    from hcloud.server_types.domain import ServerType
    from hcloud.images.domain import Image
    from hcloud.locations.domain import Location
    from hcloud.primary_ips.domain import PrimaryIP # <--- 新增
    from hcloud.servers.domain import PublicNetRequest # <--- 新增
    
    # ...
    
    # 你的 Primary IP ID (在 Hetzner 后台看)
    MY_STATIC_IP_ID = 1234567 
    
    # 创建时指定 IP
    client.servers.create(
        name=SERVER_NAME,
        server_type=ServerType(name=SERVER_TYPE),
        image=Image(id=SNAPSHOT_ID),
        location=Location(name=LOCATION),
        # ⬇️ 强制指定使用这个固定 IP
        public_net=PublicNetRequest(
            ipv4=PrimaryIP(id=MY_STATIC_IP_ID)
        )
    )
    

总结

  • 不想花钱:用方案一(Cloudflare DDNS),脚本我都给你写好了,换上 Token 就能用。

  • 想要极致稳定:用方案二(保留 IP),每个月多花几块钱人民币,Vertex 永远不用动。

Q:好的,但是我在浏览器上面输入我解析好的 xxxxxx 这个域名是打不开,这是怎么回事?

A:这是一个非常普遍且正常的现象,请不要慌张。

根据你之前的日志,脚本和 DNS 更新已经 100% 成功了。你现在在浏览器打不开,通常是以下三个原因之一(90% 是因为第一个):

1. 端口问题(最常见原因)🚨

浏览器默认只访问 80 (HTTP) 或 443 (HTTPS) 端口。

但你的服务器如果主要是跑 qBittorrent (QB) 或 哪吒探针,它们通常不监听这两个默认端口,而是有特定的端口。

  • qBittorrent 默认 WebUI 端口通常是:8080

  • 哪吒探针 面板端口通常是:8008

  • SSH 端口是:22 (浏览器打不开)

解决方法:

请尝试在域名后面加上端口号访问:

  • 试试:http://xxxxxx:8080 (如果是 QB)

  • 试试:http://xxxxxx:8008 (如果是探针面板)

2. HTTPS/SSL 证书问题

我们在脚本里设置了 Cloudflare 为 DNS Only (灰云,不代理)。

这意味着 Cloudflare 不会 为你提供免费的 HTTPS 证书。

  • 如果你直接访问 https://xxxxxx,而你的服务器上没有手动配置 SSL 证书,浏览器会直接报错(连接被拒绝或证书无效)。

  • 解决方法:请务必使用 http:// 开头(不要 s),除非你自己手动在服务器里配置了证书。

3. 服务没启动

虽然服务器重建成功了,但你原来的 qBittorrent 或 Nginx/Caddy 并没有设置成 “开机自启”。

快照恢复后,服务器是开机了,但软件没自动跑起来。

解决方法:

请通过 SSH 登录新 IP (xx.xx.xx.xx),手动检查一下服务状态:

Bash

# 检查 QB 是否在运行 (假设是 qbittorrent-nox)
ps -ef | grep qb

# 或者检查端口占用情况
netstat -tulpn

如果发现没运行,你需要手动启动它一次,并设置 systemctl enable 让它下次自动启动。


如何验证“域名解析”是不是真的好了?

要区分是“脚本没生效”还是“网站没配置好”,最简单的办法是:

在你的电脑上打开 CMD (Windows) 或 终端 (Mac),输入:

Bash

ping 域名
  • 如果返回的 IP 是 xx.xx.xx.xx(和你日志里的一样),说明 脚本立大功了,域名完全没问题

  • 打不开网页纯粹是因为你没加端口,或者软件没开。

Q:果然,去掉 s 和加了端口就行,那么我现在是不是就要去 vertex 里面把 QB 的链接改成我的域名链接

A:没错!这就是最后一块拼图! 🧩

你现在 必须 去 Vertex 把连接地址改成域名,这样你的整套“全自动刷流系统”才算真正闭环。

请按照以下步骤操作:

1. 进入 Vertex 修改设置

  1. 打开你的 Vertex 面板

  2. 找到 下载器 (Download Clients) 菜单。

  3. 点击你那台 Hetzner QB 的 编辑 按钮。

  4. 找到 Host (主机 / 地址) 这一栏:

    • 原来的填法xx.xx.xx.xx (或者旧 IP)

    • 现在的填法域名

  5. Port (端口) 这一栏:

    • 保持不变(比如 8080,或者你刚才在浏览器里输入的那个端口)。

  6. 点击 保存测试连接


这样改完后的神奇效果 ✨

从现在开始,你的系统逻辑变成了这样:

  1. 爆炸重开:脚本删机重建,拿到新 IP(比如 1.1.1.1)。

  2. 自动指路:脚本立刻告诉 Cloudflare:“嘿,域名 现在是 1.1.1.1 了!”

  3. 无感连接:Vertex 去连接 QB 时,它只认 域名 这个名字。Cloudflare 会告诉 Vertex 新 IP 是 1.1.1.1,Vertex 就自动连上了。

以后无论你重建 100 次,IP 变 100 次,你都不需要再动 Vertex 一根手指头了! 🥂

快去改吧,改完你就彻底解放双手了!

消息盒子

# 暂无消息 #

只显示最新10条未读和已读信息