main.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #!/usr/bin/env python3
  2. """
  3. Main FastAPI application entry point.
  4. """
  5. from contextlib import asynccontextmanager
  6. from pathlib import Path
  7. import threading
  8. import os
  9. import argparse
  10. from fastapi import FastAPI
  11. from fastapi.staticfiles import StaticFiles
  12. from slowapi.errors import RateLimitExceeded
  13. from slowapi.middleware import SlowAPIMiddleware
  14. from web.api.v1 import router as v1_router
  15. from web.ui import router as ui_router
  16. from web.core.config import get_settings
  17. from web.core.startup import initialize_application
  18. from web.core.logging_config import setup_logging
  19. from web.core.limiter import limiter, rate_limit_exceeded_handler
  20. import ap_git
  21. import build_manager
  22. from metadata_manager import (
  23. APSourceMetadataFetcher,
  24. ManifestJSON,
  25. VehiclesManager,
  26. VersionsManager,
  27. )
  28. setup_logging()
  29. @asynccontextmanager
  30. async def lifespan(app: FastAPI):
  31. """
  32. Lifespan context manager for startup and shutdown events.
  33. """
  34. # Startup
  35. settings = get_settings()
  36. initialize_application(settings.base_dir)
  37. repo = ap_git.GitRepo.clone_if_needed(
  38. source=settings.ap_git_url,
  39. dest=settings.source_dir,
  40. recurse_submodules=True,
  41. )
  42. vehicles_manager = VehiclesManager()
  43. manifest_json = ManifestJSON(
  44. url=settings.ap_firmware_manifest_url,
  45. cache_dir=settings.manifest_cache_dir,
  46. )
  47. ap_src_metadata_fetcher = APSourceMetadataFetcher(
  48. ap_repo=repo,
  49. caching_enabled=True,
  50. redis_host=settings.redis_host,
  51. redis_port=settings.redis_port,
  52. )
  53. versions_manager = VersionsManager(
  54. ap_repo=repo,
  55. remotes_json_path=settings.remotes_json_path,
  56. manifest_json=manifest_json,
  57. )
  58. versions_manager.refresh_all()
  59. build_mgr = build_manager.BuildManager(
  60. outdir=settings.outdir_parent,
  61. redis_host=settings.redis_host,
  62. redis_port=settings.redis_port
  63. )
  64. cleaner = build_manager.BuildArtifactsCleaner()
  65. progress_updater = build_manager.BuildProgressUpdater()
  66. inbuilt_builder = None
  67. inbuilt_builder_thread = None
  68. if settings.enable_inbuilt_builder:
  69. from builder.builder import Builder # noqa: E402
  70. inbuilt_builder = Builder(
  71. workdir=settings.workdir_parent,
  72. source_repo=repo
  73. )
  74. inbuilt_builder_thread = threading.Thread(
  75. target=inbuilt_builder.run,
  76. daemon=True
  77. )
  78. inbuilt_builder_thread.start()
  79. versions_manager.start()
  80. cleaner.start()
  81. progress_updater.start()
  82. app.state.repo = repo
  83. app.state.ap_src_metadata_fetcher = ap_src_metadata_fetcher
  84. app.state.manifest_json = manifest_json
  85. app.state.versions_manager = versions_manager
  86. app.state.vehicles_manager = vehicles_manager
  87. app.state.build_manager = build_mgr
  88. app.state.inbuilt_builder = inbuilt_builder
  89. app.state.inbuilt_builder_thread = inbuilt_builder_thread
  90. app.state.limiter = limiter
  91. yield
  92. # Shutdown
  93. versions_manager.stop()
  94. cleaner.stop()
  95. progress_updater.stop()
  96. if inbuilt_builder is not None:
  97. inbuilt_builder.shutdown()
  98. if (inbuilt_builder_thread is not None and
  99. inbuilt_builder_thread.is_alive()):
  100. inbuilt_builder_thread.join()
  101. # Create FastAPI application
  102. app = FastAPI(
  103. title="CustomBuild API",
  104. description="API for ArduPilot Custom Firmware Builder",
  105. version="1.0.0",
  106. docs_url="/api/docs",
  107. redoc_url="/api/redoc",
  108. lifespan=lifespan,
  109. )
  110. # SlowAPIMiddleware is used for rate limiting
  111. app.add_middleware(SlowAPIMiddleware)
  112. app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
  113. # Mount static files
  114. WEB_ROOT = Path(__file__).resolve().parent
  115. app.mount(
  116. "/static",
  117. StaticFiles(directory=str(WEB_ROOT / "static")),
  118. name="static"
  119. )
  120. # Include API v1 router
  121. app.include_router(v1_router, prefix="/api")
  122. # Include Web UI router
  123. app.include_router(ui_router)
  124. @app.get("/health")
  125. async def health_check():
  126. """Health check endpoint."""
  127. return {"status": "healthy"}
  128. if __name__ == "__main__":
  129. parser = argparse.ArgumentParser(description="CustomBuild API Server")
  130. parser.add_argument(
  131. "--port",
  132. type=int,
  133. default=int(os.getenv("WEB_PORT", 8080)),
  134. help="Port to run the server on (default: 8080 or WEB_PORT env var)"
  135. )
  136. args = parser.parse_args()
  137. import uvicorn
  138. uvicorn.run(
  139. "main:app",
  140. host="0.0.0.0",
  141. port=args.port,
  142. reload=True
  143. )