Basic Rest API implementation
This commit is contained in:
686
core/api.py
Normal file
686
core/api.py
Normal file
@ -0,0 +1,686 @@
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "/etc/hysteria/core/scripts")
|
||||
|
||||
from fastapi import FastAPI, HTTPException, status, Query
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
from . import cli_api
|
||||
import asyncio
|
||||
|
||||
app = FastAPI(
|
||||
title="Blitz Panel API",
|
||||
description="REST API for Hysteria2 Panel Management",
|
||||
version="1.0.0",
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# Pydantic Models
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
username: str = Field(..., min_length=1, max_length=50)
|
||||
traffic_limit: int = Field(..., ge=0, description="Traffic limit in GB")
|
||||
expiration_days: int = Field(..., ge=0)
|
||||
password: Optional[str] = None
|
||||
creation_date: Optional[str] = None
|
||||
unlimited: bool = False
|
||||
note: Optional[str] = None
|
||||
|
||||
|
||||
class BulkUserCreate(BaseModel):
|
||||
traffic_gb: float = Field(..., ge=0)
|
||||
expiration_days: int = Field(..., ge=0)
|
||||
count: int = Field(..., ge=1, le=1000)
|
||||
prefix: str = Field(..., min_length=1)
|
||||
start_number: int = Field(1, ge=1)
|
||||
unlimited: bool = False
|
||||
|
||||
|
||||
class UserEdit(BaseModel):
|
||||
new_username: Optional[str] = None
|
||||
new_password: Optional[str] = None
|
||||
new_traffic_limit: Optional[int] = Field(None, ge=0)
|
||||
new_expiration_days: Optional[int] = Field(None, ge=0)
|
||||
renew_password: bool = False
|
||||
renew_creation_date: bool = False
|
||||
blocked: Optional[bool] = None
|
||||
unlimited_ip: Optional[bool] = None
|
||||
note: Optional[str] = None
|
||||
|
||||
|
||||
class PortChange(BaseModel):
|
||||
port: int = Field(..., ge=1, le=65535)
|
||||
|
||||
|
||||
class SNIChange(BaseModel):
|
||||
sni: str = Field(..., min_length=1)
|
||||
|
||||
|
||||
class IPConfig(BaseModel):
|
||||
ipv4: Optional[str] = None
|
||||
ipv6: Optional[str] = None
|
||||
|
||||
|
||||
class Hysteria2Install(BaseModel):
|
||||
port: int = Field(..., ge=1, le=65535)
|
||||
sni: str = Field(..., min_length=1)
|
||||
|
||||
|
||||
class NodeCreate(BaseModel):
|
||||
name: str
|
||||
ip: str
|
||||
sni: Optional[str] = None
|
||||
pinSHA256: Optional[str] = None
|
||||
port: Optional[int] = Field(None, ge=1, le=65535)
|
||||
obfs: Optional[str] = None
|
||||
insecure: Optional[bool] = False
|
||||
|
||||
|
||||
class TelegramBotConfig(BaseModel):
|
||||
token: str
|
||||
admin_ids: str
|
||||
backup_interval: Optional[int] = Field(None, ge=1)
|
||||
|
||||
|
||||
class WebPanelConfig(BaseModel):
|
||||
domain: str
|
||||
port: int = Field(..., ge=1, le=65535)
|
||||
admin_username: str
|
||||
admin_password: str
|
||||
expiration_minutes: int = Field(30, ge=1)
|
||||
debug: bool = False
|
||||
decoy_path: str = ""
|
||||
|
||||
|
||||
class NormalSubConfig(BaseModel):
|
||||
domain: str
|
||||
port: int = Field(..., ge=1, le=65535)
|
||||
|
||||
|
||||
class WARPConfig(BaseModel):
|
||||
all_state: Optional[str] = Field(None, pattern="^(on|off)$")
|
||||
popular_sites_state: Optional[str] = Field(None, pattern="^(on|off)$")
|
||||
domestic_sites_state: Optional[str] = Field(None, pattern="^(on|off)$")
|
||||
block_adult_sites_state: Optional[str] = Field(None, pattern="^(on|off)$")
|
||||
|
||||
|
||||
class IPLimiterConfig(BaseModel):
|
||||
block_duration: Optional[int] = Field(None, ge=1)
|
||||
max_ips: Optional[int] = Field(None, ge=1)
|
||||
|
||||
|
||||
class ExtraProxyConfig(BaseModel):
|
||||
name: str
|
||||
uri: str
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Error Handler
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def handle_api_error(func):
|
||||
"""Decorator to handle CLI API errors"""
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
|
||||
async def async_wrapper(*args, **kwargs):
|
||||
try:
|
||||
return await func(*args, **kwargs)
|
||||
except cli_api.InvalidInputError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except cli_api.ScriptNotFoundError as e:
|
||||
raise HTTPException(status_code=500, detail=f"Script error: {str(e)}")
|
||||
except cli_api.CommandExecutionError as e:
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Execution error: {str(e)}"
|
||||
)
|
||||
except cli_api.HysteriaError as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Unexpected error: {str(e)}"
|
||||
)
|
||||
|
||||
return async_wrapper
|
||||
else:
|
||||
|
||||
def sync_wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except cli_api.InvalidInputError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except cli_api.ScriptNotFoundError as e:
|
||||
raise HTTPException(status_code=500, detail=f"Script error: {str(e)}")
|
||||
except cli_api.CommandExecutionError as e:
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Execution error: {str(e)}"
|
||||
)
|
||||
except cli_api.HysteriaError as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Unexpected error: {str(e)}"
|
||||
)
|
||||
|
||||
return sync_wrapper
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Health & Info
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""Health check endpoint"""
|
||||
return {"status": "healthy", "timestamp": datetime.now().isoformat()}
|
||||
|
||||
|
||||
@app.get("/api/v1/info/server")
|
||||
async def get_server_info():
|
||||
"""Get server information"""
|
||||
info = cli_api.server_info()
|
||||
return {"data": info}
|
||||
|
||||
|
||||
@app.get("/api/v1/info/version")
|
||||
async def get_version():
|
||||
"""Get panel version"""
|
||||
version = cli_api.show_version()
|
||||
return {"version": version}
|
||||
|
||||
|
||||
@app.get("/api/v1/info/services")
|
||||
async def get_services_status():
|
||||
"""Get status of all services"""
|
||||
status_data = cli_api.get_services_status()
|
||||
return {"services": status_data}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# User Management
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.get("/api/v1/users")
|
||||
async def list_users():
|
||||
"""List all users"""
|
||||
users = cli_api.list_users()
|
||||
return {"users": users or {}}
|
||||
|
||||
|
||||
@app.get("/api/v1/users/{username}")
|
||||
async def get_user(username: str):
|
||||
"""Get user details"""
|
||||
user = cli_api.get_user(username)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail=f"User '{username}' not found")
|
||||
return {"user": user}
|
||||
|
||||
|
||||
@app.post("/api/v1/users", status_code=status.HTTP_201_CREATED)
|
||||
async def create_user(user: UserCreate):
|
||||
"""Create a new user"""
|
||||
cli_api.add_user(
|
||||
username=user.username,
|
||||
traffic_limit=user.traffic_limit,
|
||||
expiration_days=user.expiration_days,
|
||||
password=user.password,
|
||||
creation_date=user.creation_date,
|
||||
unlimited=user.unlimited,
|
||||
note=user.note,
|
||||
)
|
||||
return {"message": f"User '{user.username}' created successfully"}
|
||||
|
||||
|
||||
@app.post("/api/v1/users/bulk", status_code=status.HTTP_201_CREATED)
|
||||
async def create_bulk_users(bulk: BulkUserCreate):
|
||||
"""Create multiple users at once"""
|
||||
cli_api.bulk_user_add(
|
||||
traffic_gb=bulk.traffic_gb,
|
||||
expiration_days=bulk.expiration_days,
|
||||
count=bulk.count,
|
||||
prefix=bulk.prefix,
|
||||
start_number=bulk.start_number,
|
||||
unlimited=bulk.unlimited,
|
||||
)
|
||||
return {"message": f"Created {bulk.count} users with prefix '{bulk.prefix}'"}
|
||||
|
||||
|
||||
@app.put("/api/v1/users/{username}")
|
||||
async def edit_user(username: str, user: UserEdit):
|
||||
"""Edit user details"""
|
||||
cli_api.edit_user(
|
||||
username=username,
|
||||
new_username=user.new_username,
|
||||
new_password=user.new_password,
|
||||
new_traffic_limit=user.new_traffic_limit,
|
||||
new_expiration_days=user.new_expiration_days,
|
||||
renew_password=user.renew_password,
|
||||
renew_creation_date=user.renew_creation_date,
|
||||
blocked=user.blocked,
|
||||
unlimited_ip=user.unlimited_ip,
|
||||
note=user.note,
|
||||
)
|
||||
return {"message": f"User '{username}' updated successfully"}
|
||||
|
||||
|
||||
@app.post("/api/v1/users/{username}/reset")
|
||||
async def reset_user(username: str):
|
||||
"""Reset user traffic and dates"""
|
||||
cli_api.reset_user(username)
|
||||
return {"message": f"User '{username}' reset successfully"}
|
||||
|
||||
|
||||
@app.delete("/api/v1/users/{username}")
|
||||
async def delete_user(username: str):
|
||||
"""Delete a user"""
|
||||
cli_api.remove_users([username])
|
||||
return {"message": f"User '{username}' deleted successfully"}
|
||||
|
||||
|
||||
@app.delete("/api/v1/users")
|
||||
async def delete_multiple_users(usernames: List[str] = Query(...)):
|
||||
"""Delete multiple users"""
|
||||
cli_api.remove_users(usernames)
|
||||
return {"message": f"Deleted {len(usernames)} users"}
|
||||
|
||||
|
||||
@app.post("/api/v1/users/{username}/kick")
|
||||
async def kick_user(username: str):
|
||||
"""Kick user (disconnect)"""
|
||||
cli_api.kick_users_by_name([username])
|
||||
return {"message": f"User '{username}' kicked successfully"}
|
||||
|
||||
|
||||
@app.get("/api/v1/users/{username}/uri")
|
||||
async def get_user_uri(
|
||||
username: str,
|
||||
qrcode: bool = False,
|
||||
ipv: int = 4,
|
||||
all: bool = False,
|
||||
singbox: bool = False,
|
||||
normalsub: bool = False,
|
||||
):
|
||||
"""Get user connection URI"""
|
||||
uri = cli_api.show_user_uri(username, qrcode, ipv, all, singbox, normalsub)
|
||||
return {"uri": uri}
|
||||
|
||||
|
||||
@app.get("/api/v1/users/uri/batch")
|
||||
async def get_batch_user_uri(usernames: List[str] = Query(...)):
|
||||
"""Get URIs for multiple users"""
|
||||
uris = cli_api.show_user_uri_json(usernames)
|
||||
return {"uris": uris}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Hysteria2 Core Management
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.post("/api/v1/hysteria2/install")
|
||||
async def install_hysteria2(config: Hysteria2Install):
|
||||
"""Install Hysteria2"""
|
||||
cli_api.install_hysteria2(port=config.port, sni=config.sni)
|
||||
return {"message": "Hysteria2 installed successfully"}
|
||||
|
||||
|
||||
@app.post("/api/v1/hysteria2/uninstall")
|
||||
async def uninstall_hysteria2():
|
||||
"""Uninstall Hysteria2"""
|
||||
cli_api.uninstall_hysteria2()
|
||||
return {"message": "Hysteria2 uninstalled successfully"}
|
||||
|
||||
|
||||
@app.post("/api/v1/hysteria2/update")
|
||||
async def update_hysteria2():
|
||||
"""Update Hysteria2 core"""
|
||||
cli_api.update_hysteria2()
|
||||
return {"message": "Hysteria2 updated successfully"}
|
||||
|
||||
|
||||
@app.post("/api/v1/hysteria2/restart")
|
||||
async def restart_hysteria2():
|
||||
"""Restart Hysteria2 service"""
|
||||
cli_api.restart_hysteria2()
|
||||
return {"message": "Hysteria2 restarted successfully"}
|
||||
|
||||
|
||||
@app.get("/api/v1/hysteria2/config/port")
|
||||
async def get_port():
|
||||
"""Get current Hysteria2 port"""
|
||||
port = cli_api.get_hysteria2_port()
|
||||
return {"port": port}
|
||||
|
||||
|
||||
@app.put("/api/v1/hysteria2/config/port")
|
||||
async def change_port(config: PortChange):
|
||||
"""Change Hysteria2 port"""
|
||||
cli_api.change_hysteria2_port(config.port)
|
||||
return {"message": f"Port changed to {config.port}"}
|
||||
|
||||
|
||||
@app.get("/api/v1/hysteria2/config/sni")
|
||||
async def get_sni():
|
||||
"""Get current SNI"""
|
||||
sni = cli_api.get_hysteria2_sni()
|
||||
return {"sni": sni}
|
||||
|
||||
|
||||
@app.put("/api/v1/hysteria2/config/sni")
|
||||
async def change_sni(config: SNIChange):
|
||||
"""Change SNI"""
|
||||
cli_api.change_hysteria2_sni(config.sni)
|
||||
return {"message": f"SNI changed to {config.sni}"}
|
||||
|
||||
|
||||
@app.post("/api/v1/hysteria2/obfs/enable")
|
||||
async def enable_obfs():
|
||||
"""Enable obfuscation"""
|
||||
cli_api.enable_hysteria2_obfs()
|
||||
return {"message": "Obfuscation enabled"}
|
||||
|
||||
|
||||
@app.post("/api/v1/hysteria2/obfs/disable")
|
||||
async def disable_obfs():
|
||||
"""Disable obfuscation"""
|
||||
cli_api.disable_hysteria2_obfs()
|
||||
return {"message": "Obfuscation disabled"}
|
||||
|
||||
|
||||
@app.get("/api/v1/hysteria2/obfs/status")
|
||||
async def check_obfs():
|
||||
"""Check obfuscation status"""
|
||||
status_msg = cli_api.check_hysteria2_obfs()
|
||||
return {"status": status_msg}
|
||||
|
||||
|
||||
@app.post("/api/v1/hysteria2/masquerade/enable")
|
||||
async def enable_masquerade():
|
||||
"""Enable masquerade"""
|
||||
result = cli_api.enable_hysteria2_masquerade()
|
||||
return {"message": result}
|
||||
|
||||
|
||||
@app.post("/api/v1/hysteria2/masquerade/disable")
|
||||
async def disable_masquerade():
|
||||
"""Disable masquerade"""
|
||||
result = cli_api.disable_hysteria2_masquerade()
|
||||
return {"message": result}
|
||||
|
||||
|
||||
@app.get("/api/v1/hysteria2/masquerade/status")
|
||||
async def get_masquerade_status():
|
||||
"""Get masquerade status"""
|
||||
status_msg = cli_api.get_hysteria2_masquerade_status()
|
||||
return {"status": status_msg}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Traffic & Statistics
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.get("/api/v1/traffic/status")
|
||||
async def get_traffic_status():
|
||||
"""Get traffic status for all users"""
|
||||
data = cli_api.traffic_status(no_gui=True, display_output=False)
|
||||
return {"traffic": data}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# IP Configuration
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.get("/api/v1/config/ip")
|
||||
async def get_ip_addresses():
|
||||
"""Get configured IP addresses"""
|
||||
ipv4, ipv6 = cli_api.get_ip_address()
|
||||
return {"ipv4": ipv4, "ipv6": ipv6}
|
||||
|
||||
|
||||
@app.post("/api/v1/config/ip")
|
||||
async def add_ip_addresses():
|
||||
"""Auto-detect and add IP addresses"""
|
||||
cli_api.add_ip_address()
|
||||
return {"message": "IP addresses added"}
|
||||
|
||||
|
||||
@app.put("/api/v1/config/ip")
|
||||
async def edit_ip_addresses(config: IPConfig):
|
||||
"""Edit IP addresses"""
|
||||
cli_api.edit_ip_address(ipv4=config.ipv4, ipv6=config.ipv6)
|
||||
return {"message": "IP addresses updated"}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Advanced Features
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.post("/api/v1/advanced/tcp-brutal/install")
|
||||
async def install_tcp_brutal():
|
||||
"""Install TCP Brutal"""
|
||||
cli_api.install_tcp_brutal()
|
||||
return {"message": "TCP Brutal installed"}
|
||||
|
||||
|
||||
@app.post("/api/v1/advanced/warp/install")
|
||||
async def install_warp():
|
||||
"""Install WARP"""
|
||||
cli_api.install_warp()
|
||||
return {"message": "WARP installed"}
|
||||
|
||||
|
||||
@app.post("/api/v1/advanced/warp/uninstall")
|
||||
async def uninstall_warp():
|
||||
"""Uninstall WARP"""
|
||||
cli_api.uninstall_warp()
|
||||
return {"message": "WARP uninstalled"}
|
||||
|
||||
|
||||
@app.put("/api/v1/advanced/warp/configure")
|
||||
async def configure_warp(config: WARPConfig):
|
||||
"""Configure WARP settings"""
|
||||
cli_api.configure_warp(
|
||||
all_state=config.all_state,
|
||||
popular_sites_state=config.popular_sites_state,
|
||||
domestic_sites_state=config.domestic_sites_state,
|
||||
block_adult_sites_state=config.block_adult_sites_state,
|
||||
)
|
||||
return {"message": "WARP configured"}
|
||||
|
||||
|
||||
@app.get("/api/v1/advanced/warp/status")
|
||||
async def get_warp_status():
|
||||
"""Get WARP status"""
|
||||
status_data = cli_api.warp_status()
|
||||
return {"status": status_data}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Telegram Bot
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.post("/api/v1/services/telegram/start")
|
||||
async def start_telegram(config: TelegramBotConfig):
|
||||
"""Start Telegram bot"""
|
||||
cli_api.start_telegram_bot(
|
||||
token=config.token,
|
||||
adminid=config.admin_ids,
|
||||
backup_interval=config.backup_interval,
|
||||
)
|
||||
return {"message": "Telegram bot started"}
|
||||
|
||||
|
||||
@app.post("/api/v1/services/telegram/stop")
|
||||
async def stop_telegram():
|
||||
"""Stop Telegram bot"""
|
||||
cli_api.stop_telegram_bot()
|
||||
return {"message": "Telegram bot stopped"}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# WebPanel
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.post("/api/v1/services/webpanel/start")
|
||||
async def start_webpanel(config: WebPanelConfig):
|
||||
"""Start WebPanel"""
|
||||
cli_api.start_webpanel(
|
||||
domain=config.domain,
|
||||
port=config.port,
|
||||
admin_username=config.admin_username,
|
||||
admin_password=config.admin_password,
|
||||
expiration_minutes=config.expiration_minutes,
|
||||
debug=config.debug,
|
||||
decoy_path=config.decoy_path,
|
||||
)
|
||||
return {"message": "WebPanel started"}
|
||||
|
||||
|
||||
@app.post("/api/v1/services/webpanel/stop")
|
||||
async def stop_webpanel():
|
||||
"""Stop WebPanel"""
|
||||
cli_api.stop_webpanel()
|
||||
return {"message": "WebPanel stopped"}
|
||||
|
||||
|
||||
@app.get("/api/v1/services/webpanel/url")
|
||||
async def get_webpanel_url():
|
||||
"""Get WebPanel URL"""
|
||||
url = cli_api.get_webpanel_url()
|
||||
return {"url": url}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Normal-Sub
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.post("/api/v1/services/normalsub/start")
|
||||
async def start_normalsub(config: NormalSubConfig):
|
||||
"""Start Normal-Sub service"""
|
||||
cli_api.start_normalsub(domain=config.domain, port=config.port)
|
||||
return {"message": "Normal-Sub started"}
|
||||
|
||||
|
||||
@app.post("/api/v1/services/normalsub/stop")
|
||||
async def stop_normalsub():
|
||||
"""Stop Normal-Sub service"""
|
||||
cli_api.stop_normalsub()
|
||||
return {"message": "Normal-Sub stopped"}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# IP Limiter
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.post("/api/v1/services/ip-limiter/start")
|
||||
async def start_ip_limiter():
|
||||
"""Start IP limiter service"""
|
||||
cli_api.start_ip_limiter()
|
||||
return {"message": "IP limiter started"}
|
||||
|
||||
|
||||
@app.post("/api/v1/services/ip-limiter/stop")
|
||||
async def stop_ip_limiter():
|
||||
"""Stop IP limiter service"""
|
||||
cli_api.stop_ip_limiter()
|
||||
return {"message": "IP limiter stopped"}
|
||||
|
||||
|
||||
@app.put("/api/v1/services/ip-limiter/config")
|
||||
async def config_ip_limiter(config: IPLimiterConfig):
|
||||
"""Configure IP limiter"""
|
||||
cli_api.config_ip_limiter(
|
||||
block_duration=config.block_duration, max_ips=config.max_ips
|
||||
)
|
||||
return {"message": "IP limiter configured"}
|
||||
|
||||
|
||||
@app.get("/api/v1/services/ip-limiter/config")
|
||||
async def get_ip_limiter_config():
|
||||
"""Get IP limiter configuration"""
|
||||
config = cli_api.get_ip_limiter_config()
|
||||
return config
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Nodes Management
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.post("/api/v1/nodes")
|
||||
async def add_node(node: NodeCreate):
|
||||
"""Add external node"""
|
||||
result = cli_api.add_node(
|
||||
name=node.name,
|
||||
ip=node.ip,
|
||||
sni=node.sni,
|
||||
pinSHA256=node.pinSHA256,
|
||||
port=node.port,
|
||||
obfs=node.obfs,
|
||||
insecure=node.insecure,
|
||||
)
|
||||
return {"message": result}
|
||||
|
||||
|
||||
@app.delete("/api/v1/nodes/{name}")
|
||||
async def delete_node(name: str):
|
||||
"""Delete node"""
|
||||
result = cli_api.delete_node(name)
|
||||
return {"message": result}
|
||||
|
||||
|
||||
@app.get("/api/v1/nodes")
|
||||
async def list_nodes():
|
||||
"""List all nodes"""
|
||||
result = cli_api.list_nodes()
|
||||
return {"nodes": result}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Geo Updates
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.post("/api/v1/geo/update/{country}")
|
||||
async def update_geo(country: str):
|
||||
"""Update geo files for country (iran, china, russia)"""
|
||||
cli_api.update_geo(country)
|
||||
return {"message": f"Geo files updated for {country}"}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Backup & Restore
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@app.post("/api/v1/backup")
|
||||
async def create_backup():
|
||||
"""Create backup"""
|
||||
cli_api.backup_hysteria2()
|
||||
return {"message": "Backup created successfully"}
|
||||
|
||||
|
||||
@app.post("/api/v1/restore")
|
||||
async def restore_backup(backup_file_path: str):
|
||||
"""Restore from backup"""
|
||||
cli_api.restore_hysteria2(backup_file_path)
|
||||
return {"message": "Restored successfully"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
Reference in New Issue
Block a user