main.py 4.7 KB

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