Files
Nexus/server/background/ip_allowlist_refresh.py
T

92 lines
3.1 KiB
Python
Raw Normal View History

"""Background task: refresh login IP allowlist from subscription URL every 2 hours.
Flow:
1. Read LOGIN_SUBSCRIPTION_URL from settings
2. Fetch & parse proxy nodes → subscription IPs
3. Merge with LOGIN_MANUAL_IPS (manually added, always preserved)
4. Write combined list to LOGIN_ALLOWED_IPS + settings table
"""
import asyncio
import logging
logger = logging.getLogger("nexus.ip_allowlist_refresh")
REFRESH_INTERVAL = 7200 # 2 hours
_last_refresh_at: str = ""
async def ip_allowlist_refresh_loop():
"""Every 2 hours, re-fetch subscription and rebuild IP allowlist."""
global _last_refresh_at
logger.info("IP allowlist refresh loop started (interval=%sh)", REFRESH_INTERVAL // 3600)
# Do one refresh on startup if subscription URL is configured
await asyncio.sleep(5)
await _do_refresh()
while True:
await asyncio.sleep(REFRESH_INTERVAL)
await _do_refresh()
async def _do_refresh():
"""Fetch subscription and update allowlist. Non-fatal on error."""
global _last_refresh_at
from server.config import settings
from server.infrastructure.subscription_parser import parse_subscription_content
sub_url = (settings.LOGIN_SUBSCRIPTION_URL or "").strip()
if not sub_url:
return # No subscription configured — nothing to do
logger.info("Refreshing IP allowlist from subscription: %s", sub_url[:60])
try:
import httpx
async with httpx.AsyncClient(timeout=20.0) as client:
resp = await client.get(sub_url, follow_redirects=True)
if resp.status_code != 200:
logger.warning("Subscription URL returned HTTP %s", resp.status_code)
return
sub_ips = parse_subscription_content(resp.text)
except Exception as e:
logger.error("Failed to refresh IP allowlist from subscription: %s", e)
return
# Load manual IPs (preserved)
manual_raw = (settings.LOGIN_MANUAL_IPS or "").strip()
manual_ips = [ip.strip() for ip in manual_raw.split(",") if ip.strip()]
# Merge: subscription first, then manual (dedup)
seen: set[str] = set()
merged: list[str] = []
for ip in sub_ips + manual_ips:
if ip not in seen:
seen.add(ip)
merged.append(ip)
combined = ",".join(merged)
# Write to settings table + update runtime config
try:
from server.infrastructure.database.session import AsyncSessionLocal
from server.infrastructure.database.setting_repo import SettingRepositoryImpl
async with AsyncSessionLocal() as session:
repo = SettingRepositoryImpl(session)
await repo.set("login_allowed_ips", combined)
settings.LOGIN_ALLOWED_IPS = combined
from datetime import datetime, timezone
_last_refresh_at = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
logger.info(
"IP allowlist refreshed: %d subscription + %d manual = %d total",
len(sub_ips), len(manual_ips), len(merged),
)
except Exception as e:
logger.error("Failed to save refreshed IP allowlist: %s", e)
def get_last_refresh_time() -> str:
return _last_refresh_at