feat: add remnawave panel dry-run mode

This commit is contained in:
3252a8
2026-06-01 10:32:38 +03:00
parent c2344824dc
commit 391487811b
6 changed files with 713 additions and 1 deletions
+62
View File
@@ -287,6 +287,30 @@ class Settings(BaseSettings):
description="Allow legacy referral links like ref_<telegram_id> to continue working. Defaults to True when unset.", # noqa: E501
)
APP_RUNTIME_MODE: str = Field(
default="production",
description="Runtime profile: production, development, staging or test.",
)
PANEL_WRITE_MODE: str = Field(
default="auto",
description=(
"Panel write behavior: auto uses dry-run in development/test runtimes, "
"live always writes to Remnawave, dry_run validates and logs mutations only."
),
)
PANEL_DRY_RUN_VALIDATE_REMOTE: bool = Field(
default=True,
description=(
"When panel dry-run is enabled, validate referenced users and squads "
"via live GET requests."
),
)
PANEL_DRY_RUN_SYNTHETIC_CREATE: bool = Field(
default=True,
description=(
"When panel dry-run is enabled, return synthetic users for create-user attempts."
),
)
PANEL_API_URL: Optional[str] = None
PANEL_API_KEY: Optional[str] = None
USER_TRAFFIC_LIMIT_GB: Optional[float] = Field(default=0.0)
@@ -560,6 +584,17 @@ class Settings(BaseSettings):
ids = self.ADMIN_IDS
return ids[0] if ids else None
@computed_field
@property
def panel_dry_run_enabled(self) -> bool:
mode = str(self.PANEL_WRITE_MODE or "auto").strip().lower().replace("-", "_")
if mode == "dry_run":
return True
if mode == "live":
return False
runtime = str(self.APP_RUNTIME_MODE or "production").strip().lower()
return runtime in {"dev", "development", "local", "test", "testing"}
@computed_field
@property
def trial_traffic_limit_bytes(self) -> int:
@@ -1025,6 +1060,28 @@ class Settings(BaseSettings):
return None
return v
@field_validator("APP_RUNTIME_MODE", mode="before")
@classmethod
def normalize_app_runtime_mode(cls, v):
value = str(v or "production").strip().lower().replace("-", "_")
if not value:
return "production"
aliases = {
"prod": "production",
"dev": "development",
"local_dev": "development",
"testing": "test",
}
return aliases.get(value, value)
@field_validator("PANEL_WRITE_MODE", mode="before")
@classmethod
def validate_panel_write_mode(cls, v):
value = str(v or "auto").strip().lower().replace("-", "_")
if value not in {"auto", "live", "dry_run"}:
raise ValueError("PANEL_WRITE_MODE must be one of: auto, live, dry_run")
return value
# Notification types
LOG_NEW_USERS: bool = Field(
default=True, description="Send notifications for new user registrations"
@@ -1066,6 +1123,11 @@ def get_settings() -> Settings:
logging.warning(
"CRITICAL: PANEL_API_URL is not set. Panel integration will not work."
)
if _settings_instance.panel_dry_run_enabled:
logging.warning(
"PANEL_WRITE_MODE dry-run is enabled: Remnawave write requests will be "
"validated and logged without changing panel users."
)
if not os.getenv("WEBAPP_SESSION_SECRET"):
logging.warning(
"WEBAPP_SESSION_SECRET is not set. A generated secret will be used for this process only." # noqa: E501