"""Baota (BT Panel) management API — standalone module, not merged with servers UI.""" from __future__ import annotations import logging from typing import Any from fastapi import APIRouter, Depends, HTTPException, Query, Request from sqlalchemy.ext.asyncio import AsyncSession from server.api.auth_jwt import get_current_admin from server.api.btpanel_schemas import ( BtPanelApplyCert, BtPanelBatchBootstrap, BtPanelConfigUpdate, BtPanelCreateDatabase, BtPanelCreateSite, BtPanelDbBackup, BtPanelDbPassword, BtPanelDomainAdd, BtPanelDomainDelete, BtPanelGlobalSettingsUpdate, BtPanelServiceAction, BtPanelSetSsl, BtPanelSiteAction, ) from server.api.dependencies import get_db from server.application.services.btpanel_service import BtPanelService from server.domain.models import Admin from server.infrastructure.btpanel.client import BtPanelApiError logger = logging.getLogger("nexus.api.btpanel") router = APIRouter(prefix="/api/btpanel", tags=["btpanel"]) def _client_ip(request: Request) -> str | None: return request.client.host if request.client else None def _svc(db: AsyncSession) -> BtPanelService: return BtPanelService(db) def _http_error(exc: Exception) -> HTTPException: if isinstance(exc, ValueError): return HTTPException(status_code=400, detail=str(exc)) if isinstance(exc, BtPanelApiError): return HTTPException(status_code=502, detail=str(exc)) if isinstance(exc, RuntimeError): return HTTPException(status_code=502, detail=str(exc)) logger.exception("btpanel error") return HTTPException(status_code=500, detail="宝塔操作失败") @router.get("/servers") async def list_bt_servers( db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin), ) -> list[dict[str, Any]]: return await _svc(db).list_server_statuses() @router.get("/servers/{server_id}/config") async def get_bt_config( server_id: int, refresh_bt_installed: bool = Query(False, description="强制 SSH 重新检测宝塔是否安装"), db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin), ) -> dict[str, Any]: try: return await _svc(db).get_config(server_id, refresh_bt_installed=refresh_bt_installed) except Exception as exc: raise _http_error(exc) from exc @router.put("/servers/{server_id}/config") async def update_bt_config( server_id: int, body: BtPanelConfigUpdate, request: Request, db: AsyncSession = Depends(get_db), admin: Admin = Depends(get_current_admin), ) -> dict[str, Any]: try: return await _svc(db).update_config( server_id, base_url=body.base_url, api_key=body.api_key, verify_ssl=body.verify_ssl, auto_bootstrap=body.auto_bootstrap, admin_username=admin.username, ip_address=_client_ip(request), ) except Exception as exc: raise _http_error(exc) from exc @router.get("/settings") async def get_bt_global_settings( db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin), ) -> dict[str, Any]: return await _svc(db).get_global_settings() @router.put("/settings") async def update_bt_global_settings( body: BtPanelGlobalSettingsUpdate, request: Request, db: AsyncSession = Depends(get_db), admin: Admin = Depends(get_current_admin), ) -> dict[str, Any]: try: return await _svc(db).update_global_settings( bt_panel_source_ip=body.bt_panel_source_ip, bt_panel_auto_bootstrap_enabled=body.bt_panel_auto_bootstrap_enabled, admin_username=admin.username, ip_address=_client_ip(request), ) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/bootstrap") async def bootstrap_bt_server( server_id: int, request: Request, db: AsyncSession = Depends(get_db), admin: Admin = Depends(get_current_admin), ) -> dict[str, Any]: try: return await _svc(db).bootstrap_server( server_id, source="manual", admin_username=admin.username, ip_address=_client_ip(request), force=True, ) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/batch-bootstrap") async def batch_bootstrap_bt_servers( body: BtPanelBatchBootstrap, request: Request, db: AsyncSession = Depends(get_db), admin: Admin = Depends(get_current_admin), ) -> dict[str, Any]: try: return await _svc(db).batch_bootstrap( body.server_ids, admin_username=admin.username, ip_address=_client_ip(request), ) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/login-url") async def create_bt_login_url( server_id: int, request: Request, db: AsyncSession = Depends(get_db), admin: Admin = Depends(get_current_admin), ) -> dict[str, str]: try: return await _svc(db).create_login_url( server_id, admin_username=admin.username, ip_address=_client_ip(request), ) except Exception as exc: raise _http_error(exc) from exc @router.get("/servers/{server_id}/system/total") async def bt_system_total(server_id: int, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin)): try: return await _svc(db).system_total(server_id) except Exception as exc: raise _http_error(exc) from exc @router.get("/servers/{server_id}/system/disk") async def bt_system_disk(server_id: int, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin)): try: return await _svc(db).system_disk(server_id) except Exception as exc: raise _http_error(exc) from exc @router.get("/servers/{server_id}/system/network") async def bt_system_network(server_id: int, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin)): try: return await _svc(db).system_network(server_id) except Exception as exc: raise _http_error(exc) from exc @router.get("/servers/{server_id}/sites") async def bt_list_sites(server_id: int, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin)): try: return await _svc(db).list_sites(server_id) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/sites/start") async def bt_site_start( server_id: int, body: BtPanelSiteAction, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin), ): try: return await _svc(db).site_start(server_id, body.site_id, body.name) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/sites/stop") async def bt_site_stop( server_id: int, body: BtPanelSiteAction, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin), ): try: return await _svc(db).site_stop(server_id, body.site_id, body.name) except Exception as exc: raise _http_error(exc) from exc @router.get("/servers/{server_id}/sites/php-versions") async def bt_php_versions(server_id: int, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin)): try: return await _svc(db).php_versions(server_id) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/sites") async def bt_create_site( server_id: int, body: BtPanelCreateSite, request: Request, db: AsyncSession = Depends(get_db), admin: Admin = Depends(get_current_admin), ): import json webname_obj = {"domain": body.webname, "domainlist": [], "count": 0} payload = { "webname": json.dumps(webname_obj, ensure_ascii=False), "path": body.path, "type_id": body.type_id, "type": body.type, "version": body.version, "port": body.port, "ps": body.ps, "ftp": "true" if body.ftp else "false", "sql": "true" if body.sql else "false", } try: return await _svc(db).create_site( server_id, payload, admin_username=admin.username, ip_address=_client_ip(request), ) except Exception as exc: raise _http_error(exc) from exc @router.get("/servers/{server_id}/sites/{site_id}/domains") async def bt_list_domains( server_id: int, site_id: int, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin), ): try: return await _svc(db).list_domains(server_id, site_id) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/domains") async def bt_add_domain( server_id: int, body: BtPanelDomainAdd, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin), ): try: return await _svc(db).add_domain(server_id, body.model_dump()) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/domains/delete") async def bt_delete_domain( server_id: int, body: BtPanelDomainDelete, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin), ): try: return await _svc(db).delete_domain(server_id, body.model_dump()) except Exception as exc: raise _http_error(exc) from exc @router.get("/servers/{server_id}/ssl/sites") async def bt_ssl_sites(server_id: int, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin)): try: return await _svc(db).list_ssl_sites(server_id) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/ssl/set") async def bt_set_ssl( server_id: int, body: BtPanelSetSsl, request: Request, db: AsyncSession = Depends(get_db), admin: Admin = Depends(get_current_admin), ): try: return await _svc(db).set_ssl( server_id, body.model_dump(), admin_username=admin.username, ip_address=_client_ip(request), ) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/ssl/apply") async def bt_apply_ssl( server_id: int, body: BtPanelApplyCert, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin), ): try: return await _svc(db).apply_letsencrypt(server_id, body.model_dump()) except Exception as exc: raise _http_error(exc) from exc @router.get("/servers/{server_id}/databases") async def bt_list_databases(server_id: int, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin)): try: return await _svc(db).list_databases(server_id) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/databases") async def bt_create_database( server_id: int, body: BtPanelCreateDatabase, request: Request, db: AsyncSession = Depends(get_db), admin: Admin = Depends(get_current_admin), ): try: return await _svc(db).create_database( server_id, body.model_dump(), admin_username=admin.username, ip_address=_client_ip(request), ) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/databases/password") async def bt_db_password( server_id: int, body: BtPanelDbPassword, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin), ): try: return await _svc(db).reset_database_password(server_id, body.model_dump()) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/databases/backup") async def bt_db_backup( server_id: int, body: BtPanelDbBackup, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin), ): try: return await _svc(db).backup_database(server_id, body.id) except Exception as exc: raise _http_error(exc) from exc @router.get("/servers/{server_id}/crontab") async def bt_crontab(server_id: int, db: AsyncSession = Depends(get_db), _admin: Admin = Depends(get_current_admin)): try: return await _svc(db).list_crontab(server_id) except Exception as exc: raise _http_error(exc) from exc @router.post("/servers/{server_id}/services") async def bt_service_admin( server_id: int, body: BtPanelServiceAction, request: Request, db: AsyncSession = Depends(get_db), admin: Admin = Depends(get_current_admin), ): try: return await _svc(db).service_admin( server_id, body.name, body.type, admin_username=admin.username, ip_address=_client_ip(request), ) except Exception as exc: raise _http_error(exc) from exc