"""入口。 生产(服务器上,systemd 起): python -m prrack.main --db /root/phonerail-rack/data/rack.db \\ --mqtt-env /root/phonerail/mqtt.env --static /root/phonerail-rack/frontend/dist 本机开发(不连 broker,起 N 块模拟板子,每秒一条遥测): python -m prrack.main --sim 2 --db dev.db --static ../frontend/dist ⛔ 只许绑回环地址:api.py 按 L2 信任门户注入的 X-Auth-User,这个信任只在「外面够不着本进程」时成立。 """ import argparse import asyncio import ipaddress import logging import os import sys import uvicorn from .api import create_app from .bus import MemBroker, MemBus, PahoBus from .db import Db, now_ms from .hub import Hub from .recovery import RecoveryManager from .simboard import HUNG, SimBoard log = logging.getLogger("prrack") def read_env(path: str) -> dict: out = {} with open(path, encoding="utf-8") as f: for line in f: line = line.strip() if line and not line.startswith("#") and "=" in line: k, v = line.split("=", 1) out[k.strip()] = v.strip().strip('"') return out def build(args): db = Db(args.db) sims = [] if args.sim: broker = MemBroker() bus = MemBus(broker) for i in range(args.sim): present = (True, True, True, False, True) if i == 0 else (True,) * 5 sb = SimBoard(MemBus(broker), "5157ab%06x" % i, present=present, seed=i + 1) if i == 0: sb.set_phone(2, HUNG) # 留一台「卡死」的,给自动恢复看 sims.append(sb) else: env = read_env(args.mqtt_env) bus = PahoBus(args.mqtt_host, args.mqtt_port, env.get("BACKEND_USER", "phonerail-backend"), env["BACKEND_PASS"], "phonerail-rack-%d" % os.getpid()) hub = Hub(db, bus) rec = RecoveryManager(hub, now_ms) async def up(): await hub.start() for sb in sims: sb.start() if sims: async def ticker(): while True: for sb in sims: sb.tick(now_ms()) await asyncio.sleep(1) asyncio.ensure_future(ticker()) async def down(): await hub.stop() db.close() static = args.static if args.static and os.path.isdir(args.static) else None app = create_app(hub, rec, static, startup=[up], shutdown=[down]) return app def ws_library_ok() -> bool: for mod in ("websockets", "wsproto"): try: __import__(mod) return True except ImportError: pass return False def main(argv=None): ap = argparse.ArgumentParser(description="PhoneRail 机架管理后端") ap.add_argument("--db", default="rack.db") ap.add_argument("--static", default=None) ap.add_argument("--bind", default="127.0.0.1") ap.add_argument("--port", type=int, default=7764) ap.add_argument("--mqtt-host", default="127.0.0.1") ap.add_argument("--mqtt-port", type=int, default=1884) ap.add_argument("--mqtt-env", default="/root/phonerail/mqtt.env") ap.add_argument("--sim", type=int, default=0, help="不连 broker,起 N 块模拟板子") args = ap.parse_args(argv) if not ipaddress.ip_address(args.bind).is_loopback: sys.exit("拒绝绑 %s:只许回环地址(见文件头)" % args.bind) if not ws_library_ok(): sys.exit("没装 websockets / wsproto:uvicorn 会把 /api/ws 当普通 GET,界面能开但永远不刷新") logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") uvicorn.run(build(args), host=args.bind, port=args.port, log_level="info") if __name__ == "__main__": main()