您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

170 行
5.4 KiB

  1. import os
  2. import re
  3. from pathlib import Path
  4. from dotenv import load_dotenv
  5. def _load_dotenv_chain() -> None:
  6. # Load local .env first
  7. load_dotenv(override=True)
  8. # Support shell-style source directives in .env, e.g. ". /data/conf/presence/core.conf"
  9. local_env = Path(".env")
  10. if not local_env.exists():
  11. return
  12. for raw_line in local_env.read_text(encoding="utf-8").splitlines():
  13. line = raw_line.strip()
  14. if not line or line.startswith("#"):
  15. continue
  16. if line.startswith(". "):
  17. target = line[2:].strip().strip('"').strip("'")
  18. elif line.startswith("source "):
  19. target = line[7:].strip().strip('"').strip("'")
  20. else:
  21. continue
  22. if not target:
  23. continue
  24. source_path = Path(target)
  25. if not source_path.is_absolute():
  26. source_path = (local_env.parent / source_path).resolve()
  27. if source_path.exists():
  28. load_dotenv(dotenv_path=source_path, override=True)
  29. # Safety fallback for deployments that keep Keycloak vars in core.conf.
  30. default_core_conf = Path("/data/conf/presence/core.conf")
  31. if default_core_conf.exists():
  32. load_dotenv(dotenv_path=default_core_conf, override=True)
  33. _load_dotenv_chain()
  34. _SHELL_VAR_PATTERN = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)")
  35. def _clean(value: str) -> str:
  36. if value is None:
  37. return ""
  38. text = str(value).strip().strip('"').strip("'")
  39. if not text:
  40. return ""
  41. # Expand ${VAR} and $VAR references using already loaded environment variables.
  42. def _replace(match: re.Match) -> str:
  43. var_name = match.group(1) or match.group(2)
  44. return os.getenv(var_name, "")
  45. return _SHELL_VAR_PATTERN.sub(_replace, text)
  46. def _has_http_scheme(value: str) -> bool:
  47. return value.startswith("http://") or value.startswith("https://")
  48. def _ensure_http_scheme(value: str, default_scheme: str = "https") -> str:
  49. value = _clean(value)
  50. if not value:
  51. return ""
  52. if _has_http_scheme(value):
  53. return value
  54. if value.startswith("/"):
  55. return value
  56. return f"{default_scheme}://{value}"
  57. def _absolute_or_join(value: str, base_url: str) -> str:
  58. value = _clean(value)
  59. if not value:
  60. return ""
  61. if _has_http_scheme(value):
  62. return value
  63. if value.startswith("/"):
  64. if base_url:
  65. return f"{base_url.rstrip('/')}{value}"
  66. return ""
  67. return _ensure_http_scheme(value)
  68. def _env_first(*names: str) -> str:
  69. for name in names:
  70. value = _clean(os.getenv(name))
  71. if value:
  72. return value
  73. return ""
  74. # Keycloak configuration (look in the .env)
  75. SECRET = _env_first("SECRET", "client_secret")
  76. KEYCLOAK_AUDIENCE = _env_first("KEYCLOAK_AUDIENCE", "keycloak_audience")
  77. _raw_keycloak_server = _env_first("KEYCLOAK_SERVER", "keycloak_server")
  78. KEYCLOAK_SERVER = _ensure_http_scheme(_raw_keycloak_server)
  79. _default_realm = _env_first("KEYCLOAK_REALM", "keycloak_realm") or "API.Server.local"
  80. _raw_keycloak_issuer = _env_first("KEYCLOAK_ISSUER", "keycloak_issuer")
  81. if _raw_keycloak_issuer and "${" not in _raw_keycloak_issuer:
  82. KEYCLOAK_ISSUER = _absolute_or_join(_raw_keycloak_issuer, KEYCLOAK_SERVER)
  83. elif KEYCLOAK_SERVER:
  84. KEYCLOAK_ISSUER = f"{KEYCLOAK_SERVER.rstrip('/')}/realms/{_default_realm}"
  85. else:
  86. KEYCLOAK_ISSUER = ""
  87. _raw_keycloak_protocol = _env_first("KEYCLOAK_PROTOCOL_ENDPOINT", "keycloak_protocol_endpoint")
  88. if _raw_keycloak_protocol and "${" not in _raw_keycloak_protocol:
  89. KEYCLOAK_PROTOCOL_ENDPOINT = _absolute_or_join(_raw_keycloak_protocol, KEYCLOAK_SERVER)
  90. elif KEYCLOAK_ISSUER:
  91. KEYCLOAK_PROTOCOL_ENDPOINT = f"{KEYCLOAK_ISSUER.rstrip('/')}/protocol/openid-connect"
  92. else:
  93. KEYCLOAK_PROTOCOL_ENDPOINT = ""
  94. _raw_jwks = _env_first("KEYCLOAK_JWKS_URL", "keycloak_jwks_url")
  95. if _raw_jwks and "${" not in _raw_jwks:
  96. KEYCLOAK_JWKS_URL = _absolute_or_join(_raw_jwks, KEYCLOAK_SERVER)
  97. elif KEYCLOAK_PROTOCOL_ENDPOINT:
  98. KEYCLOAK_JWKS_URL = f"{KEYCLOAK_PROTOCOL_ENDPOINT.rstrip('/')}/certs"
  99. else:
  100. KEYCLOAK_JWKS_URL = ""
  101. _raw_auth = _env_first("KEYCLOAK_AUTH_URL", "keycloak_auth_url")
  102. if _raw_auth and "${" not in _raw_auth:
  103. KEYCLOAK_AUTH_URL = _absolute_or_join(_raw_auth, KEYCLOAK_SERVER)
  104. elif KEYCLOAK_PROTOCOL_ENDPOINT:
  105. KEYCLOAK_AUTH_URL = f"{KEYCLOAK_PROTOCOL_ENDPOINT.rstrip('/')}/auth"
  106. else:
  107. KEYCLOAK_AUTH_URL = ""
  108. _raw_token = _env_first("KEYCLOAK_TOKEN_URL", "keycloak_token_url")
  109. if _raw_token and "${" not in _raw_token:
  110. KEYCLOAK_TOKEN_URL = _absolute_or_join(_raw_token, KEYCLOAK_SERVER)
  111. elif KEYCLOAK_PROTOCOL_ENDPOINT:
  112. KEYCLOAK_TOKEN_URL = f"{KEYCLOAK_PROTOCOL_ENDPOINT.rstrip('/')}/token"
  113. else:
  114. KEYCLOAK_TOKEN_URL = ""
  115. CORE_API_URL = os.getenv("CORE_API_URL", "http://localhost:1902")
  116. MQTT_HOST = os.getenv("MQTT_HOST", "192.168.1.101")
  117. MQTT_PORT = int(os.getenv("MQTT_PORT", "1883"))
  118. MQTT_TOPIC = os.getenv("MQTT_TOPIC", "#")
  119. MQTT_VERSION = os.getenv("MQTT_VERSION", "mqttv311")
  120. MQTT_STATUS_INTERVAL = int(os.getenv("MQTT_STATUS_INTERVAL", "30"))
  121. MQTT_STALE_AFTER = int(os.getenv("MQTT_STALE_AFTER", "30"))
  122. BLE_AI_INFER_CSV = os.getenv(
  123. "BLE_AI_INFER_CSV",
  124. "/data/service/ble-ai-localizer/data/infer/infer.csv",
  125. )
  126. BLE_AI_META_DIR = os.getenv(
  127. "BLE_AI_META_DIR",
  128. "/data/service/ble-ai-localizer/data/maps/",
  129. )
  130. BLE_AI_MAPS_DIR = os.getenv(
  131. "BLE_AI_MAPS_DIR",
  132. "/data/service/ble-ai-localizer/data/maps",
  133. )