release: nexus btpanel session fix and app-v2
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Resolve public/private IPs for server alert Telegram messages."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ipaddress
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger("nexus.server_alert_ips")
|
||||
|
||||
|
||||
def is_public_ip(value: str | None) -> bool:
|
||||
text = (value or "").strip()
|
||||
if not text:
|
||||
return False
|
||||
try:
|
||||
addr = ipaddress.ip_address(text)
|
||||
except ValueError:
|
||||
return False
|
||||
return not (
|
||||
addr.is_private
|
||||
or addr.is_loopback
|
||||
or addr.is_link_local
|
||||
or addr.is_reserved
|
||||
)
|
||||
|
||||
|
||||
def is_private_ip(value: str | None) -> bool:
|
||||
text = (value or "").strip()
|
||||
if not text:
|
||||
return False
|
||||
try:
|
||||
addr = ipaddress.ip_address(text)
|
||||
except ValueError:
|
||||
return False
|
||||
return addr.is_private or addr.is_loopback or addr.is_link_local
|
||||
|
||||
|
||||
def classify_domain_ip(domain: str | None) -> tuple[str | None, str | None]:
|
||||
"""Return (public_ip, private_ip) when domain is a literal IP."""
|
||||
text = (domain or "").strip()
|
||||
if not text:
|
||||
return None, None
|
||||
try:
|
||||
ipaddress.ip_address(text)
|
||||
except ValueError:
|
||||
return None, None
|
||||
if is_public_ip(text):
|
||||
return text, None
|
||||
if is_private_ip(text):
|
||||
return None, text
|
||||
return None, None
|
||||
|
||||
|
||||
def merge_alert_ips(
|
||||
*,
|
||||
domain: str | None = None,
|
||||
extra_attrs: dict[str, Any] | None = None,
|
||||
system_info: dict[str, Any] | None = None,
|
||||
) -> tuple[str | None, str | None]:
|
||||
"""Pick public/private IPs for Telegram display.
|
||||
|
||||
外网 SSOT:servers.domain 为公网 IP 时优先(运维登记连接地址,如 120.79.11.13)。
|
||||
内网:Agent 上报 private_ip 优先,否则 domain 为私网 IP 时使用。
|
||||
Agent ipify 仅作 domain/extra 均无公网时的兜底。
|
||||
"""
|
||||
public_ip: str | None = None
|
||||
private_ip: str | None = None
|
||||
|
||||
info = system_info if isinstance(system_info, dict) else {}
|
||||
attrs = extra_attrs if isinstance(extra_attrs, dict) else {}
|
||||
dom_pub, dom_priv = classify_domain_ip(domain)
|
||||
|
||||
if dom_pub:
|
||||
public_ip = dom_pub
|
||||
|
||||
if not public_ip:
|
||||
for key in ("public_ip", "wan_ip", "external_ip"):
|
||||
raw = attrs.get(key)
|
||||
if raw and is_public_ip(str(raw)):
|
||||
public_ip = str(raw).strip()
|
||||
break
|
||||
|
||||
if not public_ip:
|
||||
for key in ("public_ip", "wan_ip", "external_ip"):
|
||||
raw = info.get(key)
|
||||
if raw and is_public_ip(str(raw)):
|
||||
public_ip = str(raw).strip()
|
||||
break
|
||||
|
||||
for key in ("private_ip", "lan_ip", "internal_ip"):
|
||||
raw = info.get(key)
|
||||
if raw and is_private_ip(str(raw)):
|
||||
private_ip = str(raw).strip()
|
||||
break
|
||||
|
||||
if not private_ip and dom_priv:
|
||||
private_ip = dom_priv
|
||||
|
||||
return public_ip, private_ip
|
||||
|
||||
|
||||
def format_alert_ip_line(public_ip: str | None, private_ip: str | None) -> str:
|
||||
pub = (public_ip or "").strip() or "—"
|
||||
priv = (private_ip or "").strip() or "—"
|
||||
return f"IP地址:{pub}(外) {priv}(内)"
|
||||
|
||||
|
||||
async def resolve_alert_ips_for_server(server_id: int) -> tuple[str | None, str | None]:
|
||||
"""Load server row + Redis heartbeat system_info for Telegram IP lines."""
|
||||
from server.infrastructure.database.server_repo import ServerRepositoryImpl
|
||||
from server.infrastructure.database.session import AsyncSessionLocal
|
||||
from server.infrastructure.redis.client import get_redis
|
||||
|
||||
domain: str | None = None
|
||||
extra_attrs: dict[str, Any] | None = None
|
||||
system_info: dict[str, Any] | None = None
|
||||
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
server = await ServerRepositoryImpl(session).get_by_id(server_id)
|
||||
if server:
|
||||
domain = server.domain
|
||||
extra_attrs = server.extra_attrs if isinstance(server.extra_attrs, dict) else None
|
||||
except Exception as e:
|
||||
logger.warning("resolve_alert_ips DB read failed server_id=%s: %s", server_id, e)
|
||||
|
||||
try:
|
||||
redis = get_redis()
|
||||
raw = await redis.hget(f"heartbeat:{server_id}", "system_info")
|
||||
if raw:
|
||||
parsed = json.loads(raw)
|
||||
if isinstance(parsed, dict):
|
||||
system_info = parsed
|
||||
except Exception as e:
|
||||
logger.debug("resolve_alert_ips Redis read failed server_id=%s: %s", server_id, e)
|
||||
|
||||
return merge_alert_ips(domain=domain, extra_attrs=extra_attrs, system_info=system_info)
|
||||
Reference in New Issue
Block a user