Files
zyt/app/scripts/run_local_workstation.py
2026-10-23 11:01:21 +08:00

102 lines
4.1 KiB
Python

"""Start the source workstation against a loopback backend with isolated login state."""
from __future__ import annotations
import functools
import os
import socket
import sys
from dataclasses import replace
from pathlib import Path
from urllib.parse import urlsplit
import certifi
def validate_local_url(api: str) -> None:
parsed = urlsplit(api)
if (parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password
or parsed.query or parsed.fragment):
raise ValueError("本地入口需要不带凭据的 HTTPS 地址")
addresses = {row[4][0] for row in socket.getaddrinfo(parsed.hostname, parsed.port or 443)}
if not addresses or not addresses <= {"127.0.0.1", "::1"}:
raise ValueError("本地入口仅连接解析到本机的后端")
def validate_effective_config(config, requested_api: str) -> None:
from doctor_workstation.config import normalize_api_base_url
validate_local_url(config.api_base_url)
if config.api_base_url != normalize_api_base_url(requested_api) or not config.verify_ssl:
raise ValueError("本地独立配置已改动,请在 .local-workstation/config 中核对本地服务器设置")
def local_config_type(api: str):
"""Pin this explicit local entry, including later login/config updates."""
from doctor_workstation.config import AppConfig, normalize_api_base_url
validate_local_url(api)
local_api = normalize_api_base_url(api)
class LocalAppConfig(AppConfig):
def _merge_preferences(self):
config = super()._merge_preferences()
# Use the locked login behavior with the selected local endpoint.
# Shared Qt settings and stale local preferences must not send a
# local verification session back to the production backend.
return replace(config, api_base_url=local_api, debug_mode=False,
demo_mode=False, verify_ssl=True)
return LocalAppConfig
def main() -> int:
# Select the native backend before Qt creates network objects or scans TLS
# plugins. Selecting it later can load conflicting OpenSSL DLLs on Windows.
if sys.platform == "win32":
os.environ["QT_TLS_BACKEND"] = "schannel"
root = Path(__file__).resolve().parents[1]
api = os.environ.get("DOCTOR_LOCAL_API_URL", "https://css.zhenyangtang.com.cn")
validate_local_url(api)
certificate = Path(os.environ.get(
"DOCTOR_LOCAL_CA_FILE",
"D:/phpstudy_pro/Extensions/Nginx1.15.11/conf/ssl/css.zhenyangtang.com.cn.pem",
))
private_root = root / ".local-workstation"
private_root.mkdir(parents=True, exist_ok=True)
ca_bundle = private_root / "trusted-ca.pem"
ca_bundle.write_bytes(Path(certifi.where()).read_bytes() + b"\n" + certificate.read_bytes())
os.environ.update({
"DOCTOR_CONFIG_DIR": str(private_root / "config"),
"DOCTOR_LOG_DIR": str(private_root / "logs"),
"DOCTOR_API_BASE_URL": api,
"DOCTOR_DEMO_MODE": "false",
"DOCTOR_VERIFY_SSL": "true",
"SSL_CERT_FILE": str(ca_bundle),
})
sys.path.insert(0, str(root / "src"))
# This explicit source-only entry does not edit the formal build configuration.
LocalAppConfig = local_config_type(api)
config = LocalAppConfig.load()
validate_effective_config(config, api)
config.save_preferences()
if "--check" in sys.argv:
print("Local backend: loopback HTTPS; TLS verification enabled; isolated configuration and credential namespace")
return 0
from PySide6.QtCore import QSettings
from doctor_workstation import app as workstation_app
workstation_app.AppConfig = LocalAppConfig
workstation_app.LoginWindow = functools.partial(
workstation_app.LoginWindow,
settings=QSettings("ZhenYangTang", "DoctorWorkstationLocalSource"),
)
# The normal credential service remains untouched even after a local login.
workstation_app.TokenStore = functools.partial(
workstation_app.TokenStore, service_name="zyt-doctor-workstation-local-source"
)
return workstation_app.main()
if __name__ == "__main__":
raise SystemExit(main())