433 lines
13 KiB
Python
433 lines
13 KiB
Python
"""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
|
|
from server.infrastructure.btpanel.login_url_lock import LoginUrlLockTimeout
|
|
|
|
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, LoginUrlLockTimeout):
|
|
return HTTPException(status_code=409, 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,
|
|
all_unconfigured=body.all_unconfigured,
|
|
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, Any]:
|
|
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
|