service_main.py 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. r"""Linux/macOS 服务主体 —— systemd (`Type=simple`) 的前台守护进程 (2026-09-17 用户令 1)。
  4. 为什么是"前台常驻"而不是让 systemd 直接跑 `guanlan.py serve`:
  5. `guanlan.py serve` 起完 6 个组件就**返回**(组件是 detached 的),systemd 会以为"主进程退出",
  6. 于是 `Restart=always` 会不停重启、也管不了组件死活。所以这里做一个**常驻守护**:
  7. ExecStart=<PY> scripts/service_main.py # 起组件 → 每 15 s 巡检(缺谁补谁)→ 收 SIGTERM 时全停
  8. Type=simple / Restart=always / KillMode=process
  9. 用法:
  10. python scripts/service_main.py # 前台守护(systemd 用这个)
  11. python scripts/service_main.py --once # 只跑一轮巡检就退出(无 root 也能自证)
  12. python scripts/service_main.py --selftest # 前置条件自检
  13. 注册/卸载走 `scripts/service_ctl.py`(需 root),或用 install.sh --service。
  14. """
  15. from __future__ import annotations
  16. import pathlib
  17. import signal
  18. import sys
  19. import threading
  20. ROOT = pathlib.Path(__file__).resolve().parents[1]
  21. sys.path.insert(0, str(ROOT))
  22. sys.path.insert(0, str(ROOT / 'scripts'))
  23. import service_worker as SW # noqa: E402
  24. def main() -> int:
  25. if '--selftest' in sys.argv:
  26. return SW.selftest()
  27. stop_evt = threading.Event()
  28. def _on_term(signum, frame):
  29. SW.log(f'收到信号 {signum} → 停服')
  30. stop_evt.set()
  31. signal.signal(signal.SIGTERM, _on_term)
  32. signal.signal(signal.SIGINT, _on_term)
  33. SW.log('服务模式启动(systemd Type=simple)')
  34. SW.start()
  35. try:
  36. SW.supervise(stop_evt, once=('--once' in sys.argv))
  37. finally:
  38. if '--once' not in sys.argv:
  39. SW.stop()
  40. SW.log('服务已退出')
  41. return 0
  42. if __name__ == '__main__':
  43. sys.exit(main())