#!/usr/bin/env python3 import html import os import re from pathlib import Path SERVICES = ( "gb32960", "jt808", "yutong-mqtt", "vehicle-history", "vehicle-analytics", ) LOG_DIRS = ( "lingniu-gb32960-live", "lingniu-jt808-live", "lingniu-yutong-mqtt-live", "lingniu-vehicle-history-live", "lingniu-vehicle-analytics-live", ) def env(name: str, default: str = "") -> str: return os.environ.get(name, default) def xml_value(value: str) -> str: return html.escape(value, quote=False) def replacements() -> dict[str, str]: identity_jdbc = env("VEHICLE_IDENTITY_MYSQL_JDBC_URL", "jdbc:mysql://127.0.0.1:3306/vehicle_ingest") identity_username = env("VEHICLE_IDENTITY_MYSQL_USERNAME", "") identity_password = env("VEHICLE_IDENTITY_MYSQL_PASSWORD", "") return { "__PROJECT_ROOT__": env("PROJECT_ROOT", str(Path.cwd())), "__JAVA_BIN__": env("JAVA_BIN", "/usr/bin/java"), "__SHARED_ARCHIVE_PATH__": env("INGEST_ARCHIVE_PATH", str(Path.cwd() / "data/archive-live")), "__TDENGINE_JDBC_URL__": env("TDENGINE_JDBC_URL", "jdbc:TAOS-WS://127.0.0.1:6041/vehicle_ts"), "__TDENGINE_USERNAME__": env("TDENGINE_USERNAME", "root"), "__TDENGINE_PASSWORD__": env("TDENGINE_PASSWORD", ""), "__VEHICLE_IDENTITY_MYSQL_JDBC_URL__": identity_jdbc, "__VEHICLE_IDENTITY_MYSQL_USERNAME__": identity_username, "__VEHICLE_IDENTITY_MYSQL_PASSWORD__": identity_password, "__VEHICLE_IDENTITY_MYSQL_REFRESH_INTERVAL__": env("VEHICLE_IDENTITY_MYSQL_REFRESH_INTERVAL", "60s"), "__MYSQL_JDBC_URL__": env("MYSQL_JDBC_URL", identity_jdbc), "__MYSQL_USERNAME__": env("MYSQL_USERNAME", identity_username), "__MYSQL_PASSWORD__": env("MYSQL_PASSWORD", identity_password), "__YUTONG_MQTT_ENABLED__": env("YUTONG_MQTT_ENABLED", "false"), "__YUTONG_MQTT_URI__": env("YUTONG_MQTT_URI", ""), "__YUTONG_MQTT_TOPIC__": env("YUTONG_MQTT_TOPIC", "#"), "__YUTONG_MQTT_USERNAME__": env("YUTONG_MQTT_USERNAME", ""), "__YUTONG_MQTT_PASSWORD__": env("YUTONG_MQTT_PASSWORD", ""), } def render_template(template: str, values: dict[str, str]) -> str: rendered = template for placeholder, value in values.items(): rendered = rendered.replace(placeholder, xml_value(value)) unresolved = sorted(set(re.findall(r"__[A-Z0-9_]+__", rendered))) if unresolved: raise RuntimeError("unresolved launchctl placeholder(s): " + ", ".join(unresolved)) return rendered def main() -> None: root = Path(env("PROJECT_ROOT", str(Path.cwd()))).resolve() template_dir = Path(__file__).resolve().parent launch_agents = Path.home() / "Library/LaunchAgents" launch_agents.mkdir(parents=True, exist_ok=True) (root / "data").mkdir(parents=True, exist_ok=True) for log_dir in LOG_DIRS: (Path("/tmp") / log_dir).mkdir(parents=True, exist_ok=True) values = replacements() for service in SERVICES: template_path = template_dir / f"com.lingniu.{service}.plist.template" output_path = launch_agents / f"com.lingniu.{service}.plist" output_path.write_text(render_template(template_path.read_text(), values), encoding="utf-8") print(output_path) if __name__ == "__main__": main()