mirror of
https://github.com/hansjone/netx.git
synced 2026-10-08 23:33:21 +08:00
Choose bundled or external PostgreSQL in the Setup wizard with connection validation; optional NETX_CREDENTIAL_SECRET_KEY; single desktop shortcut; faster uninstall and tray startup for offline Windows packages. Co-authored-by: Cursor <cursoragent@cursor.com>
268 lines
14 KiB
Python
268 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
# Always load repo-root `.env` (cwd-independent). Optional cwd `.env` overrides last.
|
|
_NETX_ROOT = Path(__file__).resolve().parents[1]
|
|
_ENV_FILES = (str(_NETX_ROOT / ".env"), ".env")
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(
|
|
env_file=_ENV_FILES,
|
|
env_prefix="NETX_",
|
|
extra="ignore",
|
|
)
|
|
|
|
database_url: str = "postgresql+psycopg://netx:netx@127.0.0.1:5432/netx"
|
|
host: str = "127.0.0.1"
|
|
port: int = 8890
|
|
vendor: str = "ZTE"
|
|
source_type: str = "gateway_export_excel"
|
|
parser_config: str = "netx_api/config/parsers/zte_alarm_monitor_v1.yaml"
|
|
frontend_url: str = "http://127.0.0.1:5173"
|
|
# Built Vite UI (web/dist). Empty / missing → API-only root JSON (dev with Vite).
|
|
ui_dist_dir: str = "web/dist"
|
|
oclaw_analyze_url: str = "http://127.0.0.1:8787/admin/api/ops-ai/analyze-sync"
|
|
oclaw_analyze_token: str = ""
|
|
oclaw_health_url: str = "http://127.0.0.1:8787/admin/api/ops-ai/health"
|
|
# analyze-sync runs a full gateway + LLM turn; 35s is often too short (was hardcoded).
|
|
oclaw_connect_timeout_sec: float = 15.0
|
|
oclaw_analyze_read_timeout_sec: float = 180.0
|
|
oclaw_health_timeout_sec: float = 8.0
|
|
# UME RESTCONF integration
|
|
ume_base_url: str = ""
|
|
ume_username: str = ""
|
|
ume_password: str = ""
|
|
ume_verify_tls: bool = False
|
|
ume_timeout_s: float = 20.0
|
|
ume_page_size: int = 1000
|
|
ume_max_pages: int = 2000
|
|
ume_limit_max: int = 5000
|
|
ume_limit_only_page_size: int = 5000
|
|
ume_marker_page_limit: int = 1000
|
|
ume_marker_max_pages: int = 2000
|
|
ume_iterator_500_as_end: bool = True
|
|
ume_auth_header: str = "accessToken"
|
|
ume_content_type: str = "application/yang-data+json;charset=UTF-8"
|
|
ume_token_ttl_s: int = 1800
|
|
ume_token_refresh_skew_s: int = 60
|
|
ume_keepalive_enabled: bool = True
|
|
ume_keepalive_interval_s: int = 600
|
|
ume_keepalive_renew_before_s: int = 900
|
|
ume_sync_alarms_current_enabled: bool = True
|
|
ume_sync_alarms_current_interval_s: int = 18000
|
|
ume_sync_alarms_current_skip_when_ws: bool = True
|
|
# Block WSS until initial REST current-alarm snapshot finishes (see startup gate).
|
|
ume_startup_sync_alarms_before_ws: bool = True
|
|
# Wait this long after process start before the initial REST snapshot (WSS still blocked).
|
|
ume_startup_alarm_sync_delay_s: int = 60
|
|
ume_alarm_cleared_tombstone_s: int = 300
|
|
ume_alarm_ws_enabled: bool = True
|
|
ume_notification_establish_path: str = "/restconf/operations/zte-notifications:establish-subscription"
|
|
ume_notification_delete_path: str = "/restconf/operations/zte-notifications:delete-subscription"
|
|
ume_notification_topic: str = "ALARM"
|
|
ume_sync_inventory_auto_enabled: bool = True
|
|
ume_sync_inventory_every_hours: int = 48
|
|
ume_sync_topology_auto_enabled: bool = True
|
|
ume_sync_topology_every_hours: int = 24
|
|
# Topology REST dump: 0 = no read timeout (wait while the connection stays up).
|
|
ume_topology_timeout_s: float = 0.0
|
|
# Do not reap a still-running topology pull until this age (long UME dumps).
|
|
ume_sync_topology_stale_running_sec: int = 86400
|
|
ume_token_path: str = "/restconf/operations/zte-security:oauth_token"
|
|
ume_token_handshake_path: str = "/restconf/operations/zte-security:oauth_handshake"
|
|
ume_token_logout_path: str = "/restconf/operations/zte-security:oauth_token"
|
|
ume_ne_path: str = "/restconf/data/zte-resources-module:network-elements"
|
|
ume_alarms_path: str = "/restconf/data/zte-alarms:alarms/alarm-list"
|
|
ume_topo_nodes_path: str = "/restconf/data/zte-resources-module:TopoNodes"
|
|
ume_topological_links_path: str = "/restconf/data/zte-resources-module:TopologicalLinks"
|
|
ume_sync_alarms_history_every_hours: int = 24
|
|
# Managed NE credentials (Fernet). Empty = auto-generate & persist to credential_secret_file.
|
|
credential_secret_key: str = ""
|
|
credential_secret_file: str = "data/auth/credential_secret"
|
|
# Shared-server worker caps (sized for multi-operator use; raise if bastion/DB allow).
|
|
ne_connect_max_workers: int = 8
|
|
ne_connect_timeout_sec: int = 30
|
|
ne_collect_max_workers: int = 8
|
|
ne_collect_read_timeout_sec: int = 120
|
|
ne_collect_stale_run_sec: int = 900
|
|
ne_collect_pending_stale_sec: int = 180
|
|
ne_collect_run_timeout_cap_sec: int = 600
|
|
ne_collection_data_dir: str = "data/ne_collections"
|
|
# Batch CLI collect scheduler (policy.enabled defaults False — manual until turned on).
|
|
ne_collect_scheduler_enabled: bool = True
|
|
ne_collect_scheduler_tick_sec: int = 60
|
|
ne_collect_startup_grace_sec: int = 3600
|
|
# Config sync (periodic running-config backup into DB)
|
|
config_sync_scheduler_enabled: bool = True
|
|
config_sync_scheduler_tick_sec: int = 60
|
|
# After process start / unexpected restart, wait before any scheduled sync.
|
|
config_sync_startup_grace_sec: int = 3600
|
|
# Fabric LLDP collect (network topology management).
|
|
# Scheduler thread may run, but policy.enabled defaults False — no collect until operator turns it on.
|
|
lldp_collect_scheduler_enabled: bool = True
|
|
lldp_collect_scheduler_tick_sec: int = 60
|
|
lldp_collect_startup_grace_sec: int = 3600
|
|
# Reclaim hung discover jobs (updated_at / created_at older than these).
|
|
lldp_collect_stale_run_sec: int = 7200
|
|
lldp_collect_pending_stale_sec: int = 300
|
|
# Fabric inventory lifecycle: detach dangling refs + purge fully orphaned nodes.
|
|
fabric_reconcile_scheduler_enabled: bool = True
|
|
# Default 6h — light full-table GC; deletes still purge orphans immediately on detach.
|
|
fabric_reconcile_interval_sec: int = 21600
|
|
# Port traffic monitoring (CLI rate bit/s samples)
|
|
port_traffic_scheduler_enabled: bool = True
|
|
port_traffic_scheduler_tick_sec: int = 15
|
|
# Business state monitoring (LLDP snapshot batches; Phase1)
|
|
biz_state_scheduler_enabled: bool = True
|
|
biz_state_scheduler_tick_sec: int = 15
|
|
biz_state_dispatch_workers: int = 2
|
|
# Heavy CLI lane (interface detail / routes / MAC): own SSH + longer timeouts.
|
|
# show interface on large boxes can take ~20 minutes for a single command.
|
|
biz_state_heavy_read_timeout_sec: int = 1500
|
|
biz_state_heavy_run_timeout_cap_sec: int = 2400
|
|
biz_state_heavy_workers: int = 4
|
|
# Collect spool: CLI/raw + parsed records on disk; flush to DB every N cmds.
|
|
biz_state_spool_dir: str = "data/biz_state_spool"
|
|
biz_state_persist_every_cmds: int = 8
|
|
# Cap raw_text loaded into Postgres from spool (0 = unlimited).
|
|
biz_state_raw_max_bytes: int = 8 * 1024 * 1024
|
|
# Manual log import: max upload / zip uncompressed bytes and text file count.
|
|
biz_state_import_max_bytes: int = 256 * 1024 * 1024
|
|
biz_state_import_max_files: int = 200
|
|
# Dedicated biz_state worker process(es); general worker skips biz_state scheduler.
|
|
biz_state_dedicated_workers: bool = True
|
|
# Global ceiling for simultaneous running batches (across all workers).
|
|
biz_state_max_concurrent_tasks: int = 16
|
|
biz_state_worker_collect_threads: int = 8
|
|
biz_state_persist_workers: int = 4
|
|
# TextFSM / normalize workers (off SSH collect threads). Default 8.
|
|
biz_state_parse_workers: int = 8
|
|
# When true (default), primary+aux parse runs on parse pool overlapping next CLI.
|
|
biz_state_parse_async: bool = True
|
|
# How many biz_state_worker processes start_netx should launch (same host).
|
|
biz_state_worker_replicas: int = 2
|
|
# Managed NE exec: max CLI commands per request (lab can raise; hard-capped in ne_exec).
|
|
ne_exec_max_commands: int = 5
|
|
# Opt-in: allow per-NE exec_policy (linux_shell/unrestricted). Default off — UI hidden.
|
|
ne_exec_policy_enabled: bool = False
|
|
# Background exec jobs (MCP getNeExecJob / POST /v1/managed-ne/exec-jobs).
|
|
ne_exec_job_dir: str = "data/ne_exec_jobs"
|
|
ne_exec_job_max_concurrent: int = 3
|
|
ne_exec_job_timeout_s: int = 900
|
|
# Auto-async when batch NE count >= this (0 = only when async=true).
|
|
ne_exec_async_min_nes: int = 4
|
|
# WebCRT interactive terminal sessions (multi-operator concurrent terminals).
|
|
webcrt_max_sessions: int = 40
|
|
# Per-user cap (0 = unlimited beyond global max).
|
|
webcrt_max_sessions_per_user: int = 5
|
|
webcrt_idle_timeout_sec: int = 1800
|
|
webcrt_connect_timeout_sec: int = 90
|
|
webcrt_attach_timeout_sec: int = 60
|
|
# Keep device PTY after WS drop so the UI can re-attach (reconnect / remount).
|
|
webcrt_detach_grace_sec: int = 120
|
|
webcrt_data_dir: str = "data/webcrt"
|
|
# SSH transport keepalive (seconds); 30s recommended behind NAT/firewall.
|
|
webcrt_keepalive_sec: int = 30
|
|
# Device anti-idle CLI nudge (0 = off). Keep off: NEs close idle VTY themselves.
|
|
webcrt_anti_idle_sec: int = 0
|
|
webcrt_anti_idle_payload: str = " "
|
|
# Cap stdout queue depth (drop oldest when full) to protect memory.
|
|
webcrt_out_queue_max: int = 2000
|
|
# Persist per-session transcripts under webcrt_data_dir/sessions/.
|
|
webcrt_session_log_enabled: bool = True
|
|
# Reader: short blocking wait instead of fixed 40ms spin (seconds).
|
|
webcrt_reader_poll_sec: float = 0.01
|
|
# WebCRT SFTP transfer limits (streamed; default 512 MiB per file).
|
|
webcrt_sftp_max_file_bytes: int = 512 * 1024 * 1024
|
|
webcrt_sftp_chunk_bytes: int = 64 * 1024
|
|
# Cap directory listings so huge folders cannot pin the API/UI.
|
|
webcrt_sftp_list_max_entries: int = 5000
|
|
webcrt_sftp_list_timeout_sec: float = 30.0
|
|
# Local app login / audit (lab defaults; override in production)
|
|
auth_enabled: bool = True
|
|
# Empty = auto-generate & persist to auth_secret_file (recommended).
|
|
# Set explicitly only when you want a shared/ops-managed secret.
|
|
auth_secret: str = ""
|
|
auth_secret_file: str = "data/auth/jwt_secret"
|
|
auth_token_ttl_sec: int = 3600
|
|
# Refresh token lifetime (default 7 days). Used with POST /v1/auth/refresh.
|
|
auth_refresh_ttl_sec: int = 604800
|
|
# When true, each login revokes other JWT sessions for that user (single active browser login).
|
|
auth_single_session: bool = True
|
|
# Login brute-force protection (in-process; resets on restart).
|
|
auth_login_max_failures: int = 10
|
|
auth_login_window_sec: int = 300
|
|
auth_login_lockout_sec: int = 900
|
|
auth_password_min_len: int = 8
|
|
# Idle revoke: if last_seen_at older than this, JWT session is revoked (0 = off).
|
|
auth_idle_timeout_sec: int = 7200
|
|
# Browser session cookies (HttpOnly). Bearer header still works for API tokens / scripts.
|
|
auth_cookie_enabled: bool = True
|
|
# None = auto (HTTPS / X-Forwarded-Proto); True/False force.
|
|
auth_cookie_secure: bool | None = None
|
|
auth_cookie_samesite: str = "lax"
|
|
# Optional Redis URL for shared login rate-limit (empty = in-process only).
|
|
auth_redis_url: str = ""
|
|
bootstrap_admin_username: str = "admin"
|
|
bootstrap_admin_password: str = "admin123"
|
|
# Written on first boot for MCP; path relative to cwd / absolute
|
|
auth_mcp_token_file: str = "data/auth/mcp_token"
|
|
# Cap non-revoked API keys (MCP / DSH / scripts). 0 = unlimited.
|
|
auth_api_token_max_count: int = 20
|
|
# Comma-separated peers allowed to supply X-Forwarded-For / X-Real-IP
|
|
# (Vite dev proxy, local nginx). Add your reverse-proxy LAN IP in production.
|
|
trusted_proxy_ips: str = "127.0.0.1,::1"
|
|
# Expose /docs /redoc /openapi.json without auth when true (lab only).
|
|
docs_enabled: bool = False
|
|
# Refuse start when bind host is non-loopback and insecure defaults remain.
|
|
allow_insecure_defaults: bool = False
|
|
# Async audit writer. Successful GET http.* are dropped; mutating http.* always kept.
|
|
# sample_n only applies to leftover unclassified events.
|
|
audit_async: bool = True
|
|
audit_sample_n: int = 5
|
|
# Prefer Alembic on API start; brownfield patches live in schema_patches + revisions.
|
|
# Auth column ensures still run as a safety net before bootstrap.
|
|
skip_legacy_startup_ddl: bool = True
|
|
# Run `alembic upgrade head` during API startup (recommended default).
|
|
alembic_upgrade_on_start: bool = True
|
|
# Optional dedicated SQLAlchemy URL for /v1/sql/* (read-only DB role recommended).
|
|
sql_readonly_database_url: str = ""
|
|
# When true (default), API also runs config_sync / lldp / port_traffic / fabric_reconcile schedulers.
|
|
# Production split: set false and run `python -m netx_api.worker` beside the API.
|
|
run_inline_schedulers: bool = True
|
|
# Worker→API heartbeat file (used when run_inline_schedulers=false).
|
|
scheduler_heartbeat_path: str = "data/runtime/scheduler_heartbeat.json"
|
|
# SQLAlchemy QueuePool for multi-user API + collectors + UME WS.
|
|
# Rule of thumb: pool_size + max_overflow >= HTTP/WS peak + cli_max_concurrent + sidebands.
|
|
db_pool_size: int = 40
|
|
db_max_overflow: int = 40
|
|
db_pool_recycle_sec: int = 1800
|
|
db_pool_timeout_sec: int = 30
|
|
# Global Netmiko/SSH concurrency across discover / collect / config_sync / port_traffic.
|
|
cli_max_concurrent: int = 24
|
|
# Per-feature concurrency hard ceiling (API body / policy cannot exceed this).
|
|
cli_feature_hard_cap: int = 32
|
|
# Shared timeout watchdog pool (not per-task executors).
|
|
cli_timeout_pool_workers: int = 24
|
|
# Port-traffic: how many devices may collect in parallel on the scheduler tick.
|
|
port_traffic_dispatch_workers: int = 6
|
|
# Bound async audit queue; drop oldest when full to protect RSS.
|
|
audit_queue_max: int = 5000
|
|
# Cap NE collection output files (bytes); 0 = unlimited (not recommended).
|
|
ne_collect_max_output_bytes: int = 8 * 1024 * 1024
|
|
# WebCRT session transcript rotate size (bytes); 0 disables rotate.
|
|
webcrt_session_log_max_bytes: int = 4 * 1024 * 1024
|
|
# oclaw alarm forwarder: requeue attempts before drop on send failure.
|
|
oclaw_forward_max_retries: int = 3
|
|
oclaw_forward_queue_max: int = 5000
|
|
# Cap persisted UME raw_json blobs (inventory / alarms / sync batch meta).
|
|
ume_raw_json_max_bytes: int = 64 * 1024
|
|
# Delete NE collection job dirs older than N days (0 disables).
|
|
ne_collection_keep_days: int = 14
|
|
|
|
|
|
settings = Settings()
|