feat(api): Add external node management endpoints
This commit is contained in:
@ -14,6 +14,7 @@ CONFIG_FILE = '/etc/hysteria/config.json'
|
||||
CONFIG_ENV_FILE = '/etc/hysteria/.configs.env'
|
||||
WEBPANEL_ENV_FILE = '/etc/hysteria/core/scripts/webpanel/.env'
|
||||
NORMALSUB_ENV_FILE = '/etc/hysteria/core/scripts/normalsub/.env'
|
||||
NODES_JSON_PATH = "/etc/hysteria/nodes.json"
|
||||
|
||||
|
||||
class Command(Enum):
|
||||
|
||||
@ -1,44 +1,43 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from ..schema.response import DetailResponse
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
from ..schema.config.ip import EditInputBody, StatusResponse
|
||||
from ..schema.config.ip import (
|
||||
EditInputBody,
|
||||
StatusResponse,
|
||||
AddNodeBody,
|
||||
DeleteNodeBody,
|
||||
NodeListResponse
|
||||
)
|
||||
import cli_api
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get('/get', response_model=StatusResponse, summary='Get IP Status')
|
||||
@router.get('/get', response_model=StatusResponse, summary='Get Local Server IP Status')
|
||||
async def get_ip_api():
|
||||
"""
|
||||
Retrieves the current status of IP addresses.
|
||||
Retrieves the current status of the main server's IP addresses.
|
||||
|
||||
Returns:
|
||||
StatusResponse: A response model containing the current IP address details.
|
||||
|
||||
Raises:
|
||||
HTTPException: If the IP status is not available (404) or if there is an error processing the request (400).
|
||||
"""
|
||||
try:
|
||||
|
||||
ipv4, ipv6 = cli_api.get_ip_address()
|
||||
if ipv4 or ipv6:
|
||||
return StatusResponse(ipv4=ipv4, ipv6=ipv6) # type: ignore
|
||||
raise HTTPException(status_code=404, detail='IP status not available.')
|
||||
return StatusResponse(ipv4=ipv4, ipv6=ipv6)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
|
||||
|
||||
|
||||
@router.get('/add', response_model=DetailResponse, summary='Add IP')
|
||||
@router.get('/add', response_model=DetailResponse, summary='Detect and Add Local Server IP')
|
||||
async def add_ip_api():
|
||||
"""
|
||||
Adds the auto-detected IP addresses to the .configs.env file.
|
||||
|
||||
Returns:
|
||||
A DetailResponse with a message indicating the IP addresses were added successfully.
|
||||
|
||||
Raises:
|
||||
HTTPException: if an error occurs while adding the IP addresses.
|
||||
"""
|
||||
try:
|
||||
cli_api.add_ip_address()
|
||||
@ -47,19 +46,13 @@ async def add_ip_api():
|
||||
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
|
||||
|
||||
|
||||
@router.post('/edit', response_model=DetailResponse, summary='Edit IP')
|
||||
@router.post('/edit', response_model=DetailResponse, summary='Edit Local Server IP')
|
||||
async def edit_ip_api(body: EditInputBody):
|
||||
"""
|
||||
Edits the IP addresses in the .configs.env file.
|
||||
Edits the main server's IP addresses in the .configs.env file.
|
||||
|
||||
Args:
|
||||
body: An instance of EditInputBody containing the new IPv4 and/or IPv6 addresses.
|
||||
|
||||
Returns:
|
||||
A DetailResponse with a message indicating the IP addresses were edited successfully.
|
||||
|
||||
Raises:
|
||||
HTTPException: if an error occurs while editing the IP addresses.
|
||||
"""
|
||||
try:
|
||||
if not body.ipv4 and not body.ipv6:
|
||||
@ -69,3 +62,54 @@ async def edit_ip_api(body: EditInputBody):
|
||||
return DetailResponse(detail='IP address edited successfully.')
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
|
||||
|
||||
|
||||
@router.get('/nodes', response_model=NodeListResponse, summary='Get All External Nodes')
|
||||
async def get_all_nodes():
|
||||
"""
|
||||
Retrieves the list of all configured external nodes.
|
||||
|
||||
Returns:
|
||||
A list of node objects, each containing a name and an IP.
|
||||
"""
|
||||
if not os.path.exists(cli_api.NODES_JSON_PATH):
|
||||
return []
|
||||
try:
|
||||
with open(cli_api.NODES_JSON_PATH, 'r') as f:
|
||||
content = f.read()
|
||||
if not content:
|
||||
return []
|
||||
return json.loads(content)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to read or parse nodes file: {e}")
|
||||
|
||||
|
||||
@router.post('/nodes/add', response_model=DetailResponse, summary='Add External Node')
|
||||
async def add_node(body: AddNodeBody):
|
||||
"""
|
||||
Adds a new external node to the configuration.
|
||||
|
||||
Args:
|
||||
body: Request body containing the name and IP of the node.
|
||||
"""
|
||||
try:
|
||||
cli_api.add_node(body.name, body.ip)
|
||||
return DetailResponse(detail=f"Node '{body.name}' added successfully.")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.post('/nodes/delete', response_model=DetailResponse, summary='Delete External Node')
|
||||
async def delete_node(body: DeleteNodeBody):
|
||||
"""
|
||||
|
||||
Deletes an external node from the configuration by its name.
|
||||
|
||||
Args:
|
||||
body: Request body containing the name of the node to delete.
|
||||
"""
|
||||
try:
|
||||
cli_api.delete_node(body.name)
|
||||
return DetailResponse(detail=f"Node '{body.name}' deleted successfully.")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
@ -22,3 +22,25 @@ class StatusResponse(BaseModel):
|
||||
|
||||
class EditInputBody(StatusResponse):
|
||||
pass
|
||||
|
||||
class Node(BaseModel):
|
||||
name: str
|
||||
ip: str
|
||||
|
||||
@field_validator('ip', mode='before')
|
||||
def check_ip(cls, v: str, info: ValidationInfo):
|
||||
if v is None:
|
||||
raise ValueError("IP cannot be None")
|
||||
try:
|
||||
ip_address(v)
|
||||
return v
|
||||
except ValueError:
|
||||
raise ValueError(f"'{v}' is not a valid IPv4 or IPv6 address")
|
||||
|
||||
class AddNodeBody(Node):
|
||||
pass
|
||||
|
||||
class DeleteNodeBody(BaseModel):
|
||||
name: str
|
||||
|
||||
NodeListResponse = list[Node]
|
||||
@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel, RootModel
|
||||
|
||||
|
||||
@ -38,8 +38,14 @@ class EditUserInputBody(BaseModel):
|
||||
renew_creation_date: bool = False
|
||||
blocked: bool = False
|
||||
|
||||
class NodeUri(BaseModel):
|
||||
name: str
|
||||
uri: str
|
||||
|
||||
class UserUriResponse(BaseModel):
|
||||
username: str
|
||||
ipv4: str | None = None
|
||||
ipv6: str | None = None
|
||||
normal_sub: str | None = None
|
||||
ipv4: Optional[str] = None
|
||||
ipv6: Optional[str] = None
|
||||
nodes: Optional[List[NodeUri]] = []
|
||||
normal_sub: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
@ -178,10 +178,12 @@ async def show_user_uri_api(username: str):
|
||||
uri_data_list = cli_api.show_user_uri_json([username])
|
||||
if not uri_data_list:
|
||||
raise HTTPException(status_code=404, detail=f'URI for user {username} not found.')
|
||||
|
||||
uri_data = uri_data_list[0]
|
||||
if uri_data.get('error'):
|
||||
raise HTTPException(status_code=404, detail=f"{uri_data['error']}")
|
||||
return uri_data
|
||||
|
||||
return UserUriResponse(**uri_data)
|
||||
except cli_api.ScriptNotFoundError as e:
|
||||
raise HTTPException(status_code=500, detail=f'Server script error: {str(e)}')
|
||||
except cli_api.CommandExecutionError as e:
|
||||
|
||||
Reference in New Issue
Block a user