153 lines
5.3 KiB
Python
153 lines
5.3 KiB
Python
"""IP 更换核心逻辑"""
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
from datetime import datetime, timezone
|
||
|
||
from database import Config, ProxyMachine, get_session, ensure_singleton_config
|
||
from services.aws_eip import create_ec2_client, is_valid_instance_id, rotate_elastic_ip
|
||
from services.lightsail_static_ip import (
|
||
create_lightsail_client,
|
||
is_valid_lightsail_instance_name,
|
||
rotate_lightsail_static_ip,
|
||
)
|
||
from services.cloudflare_dns import CloudflareAuth, upsert_a_record
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def _get_cf_auth(config: Config) -> CloudflareAuth:
|
||
auth_type = (config.cloudflare_auth_type or "").strip() or "api_token"
|
||
if auth_type == "api_token":
|
||
return CloudflareAuth(auth_type="api_token", api_token=config.cf_api_token)
|
||
return CloudflareAuth(
|
||
auth_type="global_key",
|
||
email=config.cf_email,
|
||
api_key=config.cf_api_key,
|
||
)
|
||
|
||
|
||
def change_ip_for_machine(machine: ProxyMachine, config: Config) -> dict[str, str]:
|
||
"""为指定机器更换 IP 并更新 DNS"""
|
||
|
||
if not machine.enabled:
|
||
raise ValueError(f"机器 {machine.name} 已被禁用")
|
||
|
||
aws_service = (machine.aws_service or "ec2").strip().lower()
|
||
|
||
# 执行 IP 轮换
|
||
if aws_service == "lightsail":
|
||
if not is_valid_lightsail_instance_name(machine.aws_instance_id):
|
||
raise ValueError(
|
||
f"Lightsail 实例名格式不正确:{machine.aws_instance_id}"
|
||
)
|
||
lightsail = create_lightsail_client(
|
||
region=machine.aws_region,
|
||
aws_access_key=config.aws_access_key,
|
||
aws_secret_key=config.aws_secret_key,
|
||
)
|
||
logger.info(
|
||
"Rotating Lightsail Static IP for instance %s (%s)",
|
||
machine.aws_instance_id,
|
||
machine.name,
|
||
)
|
||
aws_result = rotate_lightsail_static_ip(
|
||
lightsail,
|
||
instance_name=machine.aws_instance_id,
|
||
release_old=bool(config.release_old_eip),
|
||
)
|
||
else:
|
||
if not is_valid_instance_id(machine.aws_instance_id):
|
||
raise ValueError(
|
||
f"EC2 Instance ID 格式不正确:{machine.aws_instance_id}(应类似 i-xxxxxxxxxxxxxxxxx)"
|
||
)
|
||
ec2 = create_ec2_client(
|
||
region=machine.aws_region,
|
||
aws_access_key=config.aws_access_key,
|
||
aws_secret_key=config.aws_secret_key,
|
||
)
|
||
logger.info(
|
||
"Rotating Elastic IP for instance %s (%s)",
|
||
machine.aws_instance_id,
|
||
machine.name,
|
||
)
|
||
aws_result = rotate_elastic_ip(
|
||
ec2,
|
||
instance_id=machine.aws_instance_id,
|
||
release_old=bool(config.release_old_eip),
|
||
)
|
||
|
||
public_ip = aws_result.get("public_ip")
|
||
if not public_ip:
|
||
raise RuntimeError("AWS 未返回新的 Public IP")
|
||
|
||
message = f"IP 已更换为 {public_ip}"
|
||
|
||
# 如果机器配置了域名,更新 DNS
|
||
if machine.cf_zone_id and machine.cf_record_name:
|
||
logger.info("Updating Cloudflare A record %s -> %s", machine.cf_record_name, public_ip)
|
||
record = upsert_a_record(
|
||
zone_id=machine.cf_zone_id,
|
||
record_name=machine.cf_record_name,
|
||
ip=public_ip,
|
||
proxied=bool(machine.cf_proxied),
|
||
record_id=machine.cf_record_id,
|
||
auth=_get_cf_auth(config),
|
||
)
|
||
record_id = record.get("id")
|
||
if record_id:
|
||
machine.cf_record_id = record_id
|
||
message = f"已更新 {machine.cf_record_name} -> {public_ip}"
|
||
|
||
return {
|
||
"public_ip": public_ip,
|
||
"message": message,
|
||
}
|
||
|
||
|
||
def run_ip_change_for_machine(machine_id: int) -> dict:
|
||
"""为指定机器执行一次 IP 更换"""
|
||
session = get_session()
|
||
try:
|
||
machine = session.query(ProxyMachine).filter_by(id=machine_id).first()
|
||
if not machine:
|
||
return {"ok": False, "message": "机器不存在"}
|
||
|
||
config = ensure_singleton_config(session)
|
||
started_at = datetime.now(timezone.utc)
|
||
|
||
try:
|
||
result = change_ip_for_machine(machine, config)
|
||
machine.last_run_at = started_at
|
||
machine.last_success = True
|
||
machine.current_ip = result.get("public_ip")
|
||
machine.last_message = result.get("message") or "OK"
|
||
session.add(machine)
|
||
session.commit()
|
||
logger.info("IP change success for %s: %s", machine.name, machine.current_ip)
|
||
return {"ok": True, "machine_name": machine.name, **result}
|
||
except Exception as exc:
|
||
logger.exception("IP change failed for %s", machine.name)
|
||
machine.last_run_at = started_at
|
||
machine.last_success = False
|
||
machine.last_message = str(exc)
|
||
session.add(machine)
|
||
session.commit()
|
||
return {"ok": False, "machine_name": machine.name, "message": str(exc)}
|
||
finally:
|
||
session.close()
|
||
|
||
|
||
# 兼容旧接口
|
||
def run_ip_change() -> dict:
|
||
"""执行一次 IP 更换(兼容旧接口)"""
|
||
session = get_session()
|
||
try:
|
||
# 获取第一台启用的机器
|
||
machine = session.query(ProxyMachine).filter_by(enabled=True).first()
|
||
if not machine:
|
||
return {"ok": False, "message": "没有可用的机器"}
|
||
return run_ip_change_for_machine(machine.id)
|
||
finally:
|
||
session.close()
|