Files
ProxyAuto/services/ip_change.py

153 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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()