update and curl sample

main
Kar 2025-04-30 11:40:40 +00:00
parent 3f311f59a3
commit 3c4d5badbd
1 changed files with 96 additions and 57 deletions

View File

@ -1,90 +1,129 @@
import os import os
import re
import subprocess import subprocess
import base64 from typing import List
from fastapi import FastAPI, Form, HTTPException, Header, Depends, Request
from fastapi import FastAPI, Form, HTTPException, Header, Request, Depends
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from dotenv import load_dotenv from dotenv import load_dotenv
from slowapi import Limiter from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded from slowapi.errors import RateLimitExceeded
from fastapi.middleware.cors import CORSMiddleware
import re
# Load environment variables # Load .env variables
load_dotenv() load_dotenv()
API_KEY_ENV = os.getenv("API_KEY") API_KEY = os.getenv("API_KEY")
WHITELIST_IPS = os.getenv("WHITELIST_IPS", "127.0.0.1").split(",") WHITELISTED_IPS = os.getenv("WHITELISTED_IPS", "").split(",")
# Constants
VESTA_BIN = "/usr/local/hestia/bin"
DEFAULT_RATE_LIMIT = "10/minute"
# FastAPI setup
app = FastAPI() app = FastAPI()
limiter = Limiter(key_func=get_remote_address) limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.exception_handler(RateLimitExceeded) # ---------------------- Helpers ----------------------
async def rate_limit_handler(request: Request, exc: RateLimitExceeded):
return JSONResponse(status_code=429, content={"detail": "Rate limit exceeded"})
# CORS if needed def check_api_key(x_api_key: str = Header(...)):
app.add_middleware( if x_api_key != API_KEY:
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Middleware to check API key and IP whitelist
async def verify_request(request: Request, x_api_key: str = Header(...)):
client_ip = request.client.host
if client_ip not in WHITELIST_IPS:
raise HTTPException(status_code=403, detail="IP not allowed")
if x_api_key != API_KEY_ENV:
raise HTTPException(status_code=403, detail="Invalid API key") raise HTTPException(status_code=403, detail="Invalid API key")
# Validate usernames/domains def check_whitelisted_ip(request: Request):
def is_valid_name(value): ip = request.client.host
return re.match(r'^[a-zA-Z0-9_-]+$', value) is not None if WHITELISTED_IPS and ip not in WHITELISTED_IPS:
raise HTTPException(status_code=403, detail="IP not allowed")
def valid_username(name: str):
return re.match(r"^[a-zA-Z0-9_]+$", name)
def run_cmd(cmd: List[str]):
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
raise HTTPException(status_code=500, detail=f"Command error: {e.stderr.strip()}")
# ---------------------- Endpoints ----------------------
@app.post("/vesta/add-user") @app.post("/vesta/add-user")
@limiter.limit("10/minute") @limiter.limit(DEFAULT_RATE_LIMIT)
async def add_user( async def add_user(
username: str = Form(...), request: Request,
user: str = Form(...),
password: str = Form(...), password: str = Form(...),
email: str = Form(...), email: str = Form(...),
package: str = Form("default"), package: str = Form("default"),
auth: None = Depends(verify_request) auth: None = Depends(check_api_key),
_: None = Depends(check_whitelisted_ip)
): ):
if not all(map(is_valid_name, [username, package])): if not valid_username(user):
raise HTTPException(status_code=400, detail="Invalid characters in username or package") raise HTTPException(status_code=400, detail="Invalid username")
cmd = [f"{VESTA_BIN}/v-add-user", user, password, email, package]
output = run_cmd(cmd)
return {"message": "User created", "output": output}
cmd = ["/usr/local/hestia/bin/v-add-user", username, password, email, package]
try:
result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return {"message": result.stdout.decode().strip()}
except subprocess.CalledProcessError as e:
raise HTTPException(status_code=500, detail=e.stderr.decode())
@app.post("/vesta/add-domain") @app.post("/vesta/add-domain")
@limiter.limit("10/minute") @limiter.limit(DEFAULT_RATE_LIMIT)
async def add_domain( async def add_domain(
username: str = Form(...), request: Request,
user: str = Form(...),
domain: str = Form(...), domain: str = Form(...),
ssl: bool = Form(True), ssl: bool = Form(False),
php: bool = Form(True), php: bool = Form(False),
auth: None = Depends(verify_request) auth: None = Depends(check_api_key),
_: None = Depends(check_whitelisted_ip)
): ):
if not all(map(is_valid_name, [username])) or not re.match(r'^[a-zA-Z0-9.-]+$', domain): if not valid_username(user):
raise HTTPException(status_code=400, detail="Invalid characters in username or domain") raise HTTPException(status_code=400, detail="Invalid username")
try: cmd = [f"{VESTA_BIN}/v-add-web-domain", user, domain]
subprocess.run(["/usr/local/hestia/bin/v-add-web-domain", username, domain], check=True) output = run_cmd(cmd)
if ssl:
subprocess.run(["/usr/local/hestia/bin/v-add-web-domain-ssl", username, domain], check=True) ssl_out, php_out = None, None
if php:
subprocess.run(["/usr/local/hestia/bin/v-add-web-php", username, domain], check=True) if ssl:
return {"message": f"Domain {domain} added for user {username}"} ssl_cmd = [f"{VESTA_BIN}/v-add-web-domain-ssl", user, domain]
except subprocess.CalledProcessError as e: ssl_out = run_cmd(ssl_cmd)
raise HTTPException(status_code=500, detail=e.stderr.decode())
if php:
php_cmd = [f"{VESTA_BIN}/v-add-web-php", user, domain]
php_out = run_cmd(php_cmd)
return {
"message": "Domain added",
"domain_output": output,
"ssl_output": ssl_out,
"php_output": php_out
}
# ---------------------- Launch ----------------------
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run("HestiaCP_API_Server:app", host="0.0.0.0", port=4000) uvicorn.run("hestia_runner:app", host="0.0.0.0", port=3001)
# .env
#API_KEY=your-secret-key
#WHITELISTED_IPS=127.0.0.1,192.168.1.10
# cd /root/hestia_runner/ && uvicorn hestia_runner:app --host 0.0.0.0 --port 1002
#curl -X POST http://your-server-ip:3001/vesta/add-user \
# -H "x-api-key: your_api_key_here" \
# -F "user=testuser" \
# -F "password=securepass123" \
# -F "email=test@example.com" \
# -F "package=default"
#curl -X POST http://your-server-ip:3001/vesta/add-domain \
# -H "x-api-key: your_api_key_here" \
# -F "user=testuser" \
# -F "domain=example.com" \
# -F "ssl=true" \
# -F "php=true"