hestia runner
parent
0911c853c9
commit
dd74ee21a9
|
@ -0,0 +1,90 @@
|
|||
import os
|
||||
import subprocess
|
||||
import base64
|
||||
from fastapi import FastAPI, Form, HTTPException, Header, Depends, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from dotenv import load_dotenv
|
||||
from slowapi import Limiter
|
||||
from slowapi.util import get_remote_address
|
||||
from slowapi.errors import RateLimitExceeded
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import re
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
API_KEY_ENV = os.getenv("API_KEY")
|
||||
WHITELIST_IPS = os.getenv("WHITELIST_IPS", "127.0.0.1").split(",")
|
||||
|
||||
app = FastAPI()
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
app.state.limiter = limiter
|
||||
|
||||
@app.exception_handler(RateLimitExceeded)
|
||||
async def rate_limit_handler(request: Request, exc: RateLimitExceeded):
|
||||
return JSONResponse(status_code=429, content={"detail": "Rate limit exceeded"})
|
||||
|
||||
# CORS if needed
|
||||
app.add_middleware(
|
||||
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")
|
||||
|
||||
# Validate usernames/domains
|
||||
def is_valid_name(value):
|
||||
return re.match(r'^[a-zA-Z0-9_-]+$', value) is not None
|
||||
|
||||
@app.post("/vesta/add-user")
|
||||
@limiter.limit("10/minute")
|
||||
async def add_user(
|
||||
username: str = Form(...),
|
||||
password: str = Form(...),
|
||||
email: str = Form(...),
|
||||
package: str = Form("default"),
|
||||
auth: None = Depends(verify_request)
|
||||
):
|
||||
if not all(map(is_valid_name, [username, package])):
|
||||
raise HTTPException(status_code=400, detail="Invalid characters in username or package")
|
||||
|
||||
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")
|
||||
@limiter.limit("10/minute")
|
||||
async def add_domain(
|
||||
username: str = Form(...),
|
||||
domain: str = Form(...),
|
||||
ssl: bool = Form(True),
|
||||
php: bool = Form(True),
|
||||
auth: None = Depends(verify_request)
|
||||
):
|
||||
if not all(map(is_valid_name, [username])) or not re.match(r'^[a-zA-Z0-9.-]+$', domain):
|
||||
raise HTTPException(status_code=400, detail="Invalid characters in username or domain")
|
||||
|
||||
try:
|
||||
subprocess.run(["/usr/local/hestia/bin/v-add-web-domain", username, domain], check=True)
|
||||
if ssl:
|
||||
subprocess.run(["/usr/local/hestia/bin/v-add-web-domain-ssl", username, domain], check=True)
|
||||
if php:
|
||||
subprocess.run(["/usr/local/hestia/bin/v-add-web-php", username, domain], check=True)
|
||||
return {"message": f"Domain {domain} added for user {username}"}
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise HTTPException(status_code=500, detail=e.stderr.decode())
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run("HestiaCP_API_Server:app", host="0.0.0.0", port=4000)
|
Loading…
Reference in New Issue