This commit is contained in:
oliver committed 2026-09-27 10:39:11 -03:00
1 parent d3d9614825
commit 2e9c775599
34 files changed
+390 -1833

No files matched your search

+53 -50
View File
@@ -19,7 +19,7 @@ from pydantic import BaseModel
# ---------------------- Constants ----------------------
DB_PATH = "/4server/data/contracts.db"
BIN_PATH = "/4server/sbin"
BIN_PATH = os.getenv("SBIN_PATH", "/usr/bin")
API_KEY = os.getenv("API_KEY", "your-secret-api-key")
VERSION = "API: 0.0.9"
@@ -58,11 +58,11 @@ def init_db():
CREATE TABLE IF NOT EXISTS containers (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
UUID CHAR(50) UNIQUE,
container_id CHAR(50) UNIQUE,
email CHAR(100),
expires DATE,
tags TEXT,
env TEXT,
affiliate CHAR(30),
image CHAR(50),
history TEXT,
comment TEXT,
@@ -110,7 +110,7 @@ class ContractItem(BaseModel):
class ContainerModel(BaseModel):
UUID: str
container_id: str
email: Optional[str] = None
expires: Optional[str] = None
tags: Optional[str] = None
@@ -133,12 +133,12 @@ class ContainerModel(BaseModel):
env_list: Optional[str] = None
class UUIDRequest(BaseModel):
UUID: str
class ContainerRequest(BaseModel):
container_id: str
class CommandRequest(BaseModel):
uuid: str
container_id: str
method: int
@@ -153,7 +153,7 @@ class MoveRequest(BaseModel):
class AddKeyRequest(BaseModel):
key: str
uuid: str
container_id: str
class ImageRequest(BaseModel):
@@ -186,19 +186,20 @@ def redirect_to_odoo():
def update_container(request: ContainerModel):
# Fetch existing record
existing = execute_db(
"SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True
"SELECT * FROM containers WHERE UUID = ?", (request.container_id,), fetch=True
)
if not existing:
# If record does not exist, insert a new one with all given fields
execute_db(
"""
INSERT INTO containers (UUID, email, expires, tags, image, history,
INSERT INTO containers (UUID, container_id, email, expires, tags, image, history,
comment, domains, status, created, bump, git, backup_slots, hdd, workers,
utm_source, utm_medium, utm_campaign, domains_slots, secret_list, env_list)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
request.UUID,
request.container_id,
request.container_id,
request.email,
request.expires,
request.tags,
@@ -221,7 +222,7 @@ def update_container(request: ContainerModel):
request.env_list,
),
)
return {"UUID": request.UUID, "status": "created"}
return {"container_id": request.container_id, "status": "created"}
# Existing record found, do partial update
existing = existing[0] # Assuming fetch returns list of dicts
@@ -230,7 +231,7 @@ def update_container(request: ContainerModel):
# Only add fields that are not None in the request
for field in ContainerModel.__fields__:
if field == "UUID":
if field == "container_id":
continue
value = getattr(request, field)
if value is not None:
@@ -238,22 +239,24 @@ def update_container(request: ContainerModel):
params.append(value)
if updates:
# Build SQL dynamically
# Build SQL dynamically; always keep UUID in sync with container_id
set_clause = ", ".join(f"{k}=?" for k in updates.keys())
params.append(request.UUID) # UUID for WHERE clause
set_clause += ", UUID=?"
params.append(request.container_id) # value for UUID=?
params.append(request.container_id) # value for WHERE UUID=?
execute_db(f"UPDATE containers SET {set_clause} WHERE UUID=?", tuple(params))
return {
"UUID": request.UUID,
"container_id": request.container_id,
"status": "updated",
"fields_updated": list(updates.keys()),
}
return {"UUID": request.UUID, "status": "no_change"}
return {"container_id": request.container_id, "status": "no_change"}
@app.post("/container/start", dependencies=[Depends(verify_api_key)])
def start_container(request: UUIDRequest):
return {"message": run_command([f"{BIN_PATH}/startContainer", request.UUID])}
def start_container(request: ContainerRequest):
return {"message": run_command([f"{BIN_PATH}/startContainer", request.container_id])}
@app.post("/container/restartAllContainers", dependencies=[Depends(verify_api_key)])
@@ -262,30 +265,30 @@ def start_container(request: ImageRequest):
@app.post("/container/stop", dependencies=[Depends(verify_api_key)])
def stop_container(request: UUIDRequest):
def stop_container(request: ContainerRequest):
try:
return {"message": run_command([f"{BIN_PATH}/stopContainer", request.UUID])}
return {"message": run_command([f"{BIN_PATH}/stopContainer", request.container_id])}
except HTTPException:
# Command failed, but continue without error to the API
return {"message": "Container stop command executed (may have failed)"}
@app.post("/container/nuke", dependencies=[Depends(verify_api_key)])
def nuke_container(request: UUIDRequest):
def nuke_container(request: ContainerRequest):
status = execute_db(
"SELECT status FROM containers WHERE UUID=?", (request.UUID,), fetch=True
"SELECT status FROM containers WHERE UUID=?", (request.container_id,), fetch=True
)
if not status or status[0]["status"] != "nuke":
raise HTTPException(400, "Container status is not 'nuke'")
return {"message": run_command([f"{BIN_PATH}/nukeContainer", request.UUID])}
return {"message": run_command([f"{BIN_PATH}/nukeContainer", request.container_id])}
@app.post("/container/info", dependencies=[Depends(verify_api_key)])
def info_container(request: Optional[UUIDRequest] = None):
def info_container(request: Optional[ContainerRequest] = None):
# Fields to select
fields = [
"ID",
"UUID",
"container_id",
"email",
"expires",
"tags",
@@ -311,7 +314,7 @@ def info_container(request: Optional[UUIDRequest] = None):
if request:
rows = execute_db(
f"SELECT {field_str} FROM containers WHERE UUID=?",
(str(request.UUID),),
(str(request.container_id),),
fetch=True,
)
else:
@@ -334,20 +337,20 @@ def info_container(request: Optional[UUIDRequest] = None):
@app.post("/container/bump", dependencies=[Depends(verify_api_key)])
def bump_container(request: UUIDRequest):
def bump_container(request: ContainerRequest):
today = datetime.utcnow().strftime("%Y-%m-%d")
execute_db("UPDATE containers SET bump=? WHERE UUID=?", (today, request.UUID))
msg = run_command([f"{BIN_PATH}/bumpContainer", request.UUID])
execute_db("UPDATE containers SET bump=? WHERE UUID=?", (today, request.container_id))
msg = run_command([f"{BIN_PATH}/bumpContainer", request.container_id])
return {"message": msg, "bump_date": today}
@app.post("/container/quota", dependencies=[Depends(verify_api_key)])
def container_quota(request: UUIDRequest):
def container_quota(request: ContainerRequest):
output = run_command(
[
"docker",
"stats",
request.UUID,
request.container_id,
"--no-stream",
"--format",
"{{.MemUsage}},{{.BlockIO}}",
@@ -420,9 +423,9 @@ def restart_all_containers(request: ImageRequest):
@app.post("/client/git", dependencies=[Depends(verify_api_key)])
def git_tool(request: CommandRequest):
if request.method == 1:
command = [f"{BIN_PATH}/gitPull", request.uuid]
command = [f"{BIN_PATH}/gitPull", request.container_id]
elif request.method == 2:
command = [f"{BIN_PATH}/gitRevert", request.uuid]
command = [f"{BIN_PATH}/gitRevert", request.container_id]
else:
raise HTTPException(status_code=400, detail="Invalid method")
@@ -432,10 +435,10 @@ def git_tool(request: CommandRequest):
@app.post("/client/addGitKey", dependencies=[Depends(verify_api_key)])
def add_git_key(request: AddKeyRequest):
if not request.key or not request.uuid:
raise HTTPException(status_code=400, detail="Key and uuid are required")
if not request.key or not request.container_id:
raise HTTPException(status_code=400, detail="Key and container_id are required")
file_path = f"/4server/data/{request.uuid}/git-server/keys/id_rsa.pub"
file_path = f"/4server/data/{request.container_id}/git-server/keys/id_rsa.pub"
try:
# Ensure the directory exists
@@ -446,26 +449,26 @@ def add_git_key(request: AddKeyRequest):
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to save key: {str(e)}")
return {"message": f"Key saved for container {request.uuid}", "file": file_path}
return {"message": f"Key saved for container {request.container_id}", "file": file_path}
@app.get("/client/audit/{uuid}", dependencies=[Depends(verify_api_key)])
def audit_odoo(uuid: str):
return {"message": run_command([f"{BIN_PATH}/ODOO_19/listModules", uuid])}
@app.get("/client/audit/{container_id}", dependencies=[Depends(verify_api_key)])
def audit_odoo(container_id: str):
return {"message": run_command([f"{BIN_PATH}/ODOO/listModules", container_id])}
@app.get("/client/logs/{uuid}", dependencies=[Depends(verify_api_key)])
async def get_odoo_log_summary(uuid: str):
if not re.fullmatch(r"[0-9a-fA-F\-]+", uuid):
@app.get("/client/logs/{container_id}", dependencies=[Depends(verify_api_key)])
async def get_odoo_log_summary(container_id: str):
if not re.fullmatch(r"[a-zA-Z0-9_-]+", container_id):
raise HTTPException(
status_code=400,
detail="Invalid UUID format. Only numbers, letters a-f, and '-' are allowed.",
detail="Invalid container_id format. Only letters, numbers, hyphens, and underscores are allowed.",
)
BASE_LOG_DIR = "/4server/data"
# Build file paths as strings
project_dir = os.path.join(BASE_LOG_DIR, uuid)
project_dir = os.path.join(BASE_LOG_DIR, container_id)
odoo_log_file = os.path.join(project_dir, "logs", "odoo.log")
git_log_file = os.path.join(project_dir, "logs", "git.log")
@@ -510,7 +513,7 @@ async def get_odoo_log_summary(uuid: str):
last_50_git_lines = read_last_lines(git_log_file, 50)
return {
"uuid": str(uuid),
"container_id": str(container_id),
"important_odoo_log_lines": important_odoo_lines,
"last_git_log_lines": last_50_git_lines,
}
@@ -524,7 +527,7 @@ def backup_import(request: ImportRequest):
if not request.filename:
raise HTTPException(status_code=400, detail="Filename is required")
command = [f"{BIN_PATH}/ODOO_19/import", request.filename]
command = [f"{BIN_PATH}/ODOO/import", request.filename]
output = run_command(command)
return {"message": output}
@@ -551,12 +554,12 @@ def backup_move(request: MoveRequest):
class BackupCheckRequest(BaseModel):
uuid: str
container_id: str
@app.post("/backup/check", dependencies=[Depends(verify_api_key)])
def backup_check(request: BackupCheckRequest):
return {"message": run_command([f"{BIN_PATH}/checkBackup", request.uuid])}
return {"message": run_command([f"{BIN_PATH}/checkBackup", request.container_id])}
@app.get("/backup/all", dependencies=[Depends(verify_api_key)])