fixes
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
Host dev
|
Host dev
|
||||||
Hostname 192.168.111.209
|
Hostname dev
|
||||||
User oliver
|
User oliver
|
||||||
Port 2222
|
Port 2222
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
127.0.0.1 dev
|
|
||||||
::1 dev
|
|
||||||
|
|
||||||
192.168.111.209 dev n8n
|
|
||||||
|
|
||||||
192.168.9.11 saopaulo
|
|
||||||
192.168.9.17 mumbai
|
|
||||||
192.168.9.15 london
|
|
||||||
192.168.9.16 boston
|
|
||||||
@@ -11,3 +11,7 @@
|
|||||||
192.168.9.16 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCLzyfVrbCNJW9tNTkxMy8IDLGyQoqkxlGN+TirLoNyw5GD1XpJgvHIBy//6tDEIuAfL5ouqp0T+PM00nSj6gEjGeTQtjrzAeSXJ5D3Q3+Vl92j1uSPPhnChRnidI2mrjvNuaukaDxBMwIvr9sagTvtZwCupKbuYeW0kQqCwnSIjTS7OwcrCRm3RtzC8bOPpOIHYaLxV6OnyoRXKLv9Lu987nuNoTcZu61jyCHhFTzzN3Y3KRpioshUoHiukgJbJQVnRuVkxQRknWa5gS4ATLLUG70taTs0Ld4Canyrym4aVV9pw5rDwH0rkidvkz8OLX049mIeGhv/QPRH8x5bgtPezKkiOcPW22HG0+z+7zHgbkTKlcvGr1JdhkTN6jqL4HyYdhMj04v7miv8LnxjaBNb3iNFQiAs5aV5LmPevYP+/94TGBIlTVCh5oTyeDJ1OKmZKL9ZUUfu0Ozz+v3O3en4hNMOI8P+uujfIxfvn/Pasxh4r7pTGYQV9nseCmrsl0k=
|
192.168.9.16 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCLzyfVrbCNJW9tNTkxMy8IDLGyQoqkxlGN+TirLoNyw5GD1XpJgvHIBy//6tDEIuAfL5ouqp0T+PM00nSj6gEjGeTQtjrzAeSXJ5D3Q3+Vl92j1uSPPhnChRnidI2mrjvNuaukaDxBMwIvr9sagTvtZwCupKbuYeW0kQqCwnSIjTS7OwcrCRm3RtzC8bOPpOIHYaLxV6OnyoRXKLv9Lu987nuNoTcZu61jyCHhFTzzN3Y3KRpioshUoHiukgJbJQVnRuVkxQRknWa5gS4ATLLUG70taTs0Ld4Canyrym4aVV9pw5rDwH0rkidvkz8OLX049mIeGhv/QPRH8x5bgtPezKkiOcPW22HG0+z+7zHgbkTKlcvGr1JdhkTN6jqL4HyYdhMj04v7miv8LnxjaBNb3iNFQiAs5aV5LmPevYP+/94TGBIlTVCh5oTyeDJ1OKmZKL9ZUUfu0Ozz+v3O3en4hNMOI8P+uujfIxfvn/Pasxh4r7pTGYQV9nseCmrsl0k=
|
||||||
192.168.9.16 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLtNCuNwiidC4IdZci3J0s/vbcTtFZod47BKd8GKyGrucOS402fuZ03F1elrEKjA0PcFNZQR7MbEvF6zDjEfoPY=
|
192.168.9.16 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLtNCuNwiidC4IdZci3J0s/vbcTtFZod47BKd8GKyGrucOS402fuZ03F1elrEKjA0PcFNZQR7MbEvF6zDjEfoPY=
|
||||||
|
|
||||||
|
[dev]:2222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILf8g96UGmWCLf5C5qfGgir3ned83s5HXNpM231A0rUu
|
||||||
|
[dev]:2222 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC45Aw7jQXh3/QOMOCkM4wvz4/yXxpjQU5C2ZMJ/76lTPB/DsvP9C+DGKqO3zzznOhdaYzTb5lipMguWRyH0RYlwYtR7TnYKTZgioXC/+kSdVzgAUeqcUCUIoNH5CSp/lgsAp1XxQTPk3ooZJc57cTnHo78Y+oOj7bF8u8Nin/q+uTTrgXVkbMCZYSX+CA/fHtmatH9vMdAbPv/Bx9M72vijMMZWOP/6hwRhn3C0s2o1rgz/nXe5u3HYIodwzItqdceF3k6hLuZP8Bb+kKz42wXstke5SXivnUm3uDdkLNKjKWnSWoD7TAIA9w6MuFbLBLL6QRBmfcx74F6aeuV55ItzRPVAz0XEqQU55r3nS9r7zLyT0HGvu4Wt+Zf97nHKfWucaf/5UFWoTC9pijYEYbNyhYockyzM+QAx3/n6cfwIjlc/wFkU8nd7fUjfkymTlIX6UiT/12b+TrFuOLBk4mylDGkPge7zfslvH/LOyokzCRsz75Sh3bc7bxB1ppPrjc=
|
||||||
|
[dev]:2222 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK+WCqjyl6CKaDnMAcfqGYty8KoWqh95T0lQK3RFcNfHom4rsZljS2tPlicfRSHT/2zWH7HfQ9T2t/2PYjWjxrc=
|
||||||
|
|
||||||
|
|||||||
189
app/sbin/api
189
app/sbin/api
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Depends, Response
|
from fastapi import FastAPI, HTTPException, Depends, Response
|
||||||
from fastapi.security.api_key import APIKeyHeader
|
from fastapi.security.api_key import APIKeyHeader
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse
|
||||||
@@ -8,9 +7,9 @@ import psutil
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
DB_PATH = "/4server/data/contracts.db"
|
DB_PATH = "/4server/data/contracts.db"
|
||||||
@@ -20,8 +19,6 @@ VERSION = "API: 0.0.5"
|
|||||||
|
|
||||||
# FastAPI app
|
# FastAPI app
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
# Security
|
|
||||||
api_key_header = APIKeyHeader(name="X-API-Key")
|
api_key_header = APIKeyHeader(name="X-API-Key")
|
||||||
|
|
||||||
|
|
||||||
@@ -29,7 +26,16 @@ def verify_api_key(key: str = Depends(api_key_header)):
|
|||||||
if key != API_KEY:
|
if key != API_KEY:
|
||||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||||
|
|
||||||
# ---------------------- Database ----------------------
|
|
||||||
|
# ---------------------- Helpers ----------------------
|
||||||
|
def run_command(cmd: list[str]) -> str:
|
||||||
|
"""Run a shell command and return stdout or raise HTTPException on error."""
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise HTTPException(status_code=500, detail=result.stderr.strip() or "Unknown error")
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
"""Initialize the database with containers table."""
|
"""Initialize the database with containers table."""
|
||||||
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
||||||
@@ -43,12 +49,12 @@ def init_db():
|
|||||||
expires DATE,
|
expires DATE,
|
||||||
tags TEXT,
|
tags TEXT,
|
||||||
env TEXT,
|
env TEXT,
|
||||||
affiliate char(30),
|
affiliate CHAR(30),
|
||||||
image char(50),
|
image CHAR(50),
|
||||||
history text,
|
history TEXT,
|
||||||
comment text,
|
comment TEXT,
|
||||||
domains text,
|
domains TEXT,
|
||||||
status char (20).
|
status CHAR(20),
|
||||||
created DATE,
|
created DATE,
|
||||||
bump DATE
|
bump DATE
|
||||||
)
|
)
|
||||||
@@ -57,6 +63,16 @@ def init_db():
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def execute_db(query: str, params: tuple = (), fetch: bool = False):
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute(query, params)
|
||||||
|
conn.commit()
|
||||||
|
data = cursor.fetchall() if fetch else None
|
||||||
|
conn.close()
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
# ---------------------- Models ----------------------
|
# ---------------------- Models ----------------------
|
||||||
class ContainerModel(BaseModel):
|
class ContainerModel(BaseModel):
|
||||||
UUID: str
|
UUID: str
|
||||||
@@ -65,95 +81,150 @@ class ContainerModel(BaseModel):
|
|||||||
tags: Optional[str] = None
|
tags: Optional[str] = None
|
||||||
env: Optional[str] = None
|
env: Optional[str] = None
|
||||||
affiliate: Optional[str] = None
|
affiliate: Optional[str] = None
|
||||||
image: Optional[str} = None
|
image: Optional[str] = None
|
||||||
history: Optional[str] = None
|
history: Optional[str] = None
|
||||||
comment: Optional[str] = None
|
comment: Optional[str] = None
|
||||||
domains: Optional[str] = None
|
domains: Optional[str] = None
|
||||||
status: str
|
status: str
|
||||||
created: str
|
created: str
|
||||||
|
bump: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class StartContainerRequest(BaseModel):
|
class ContainerIDRequest(BaseModel):
|
||||||
uuid: str
|
container_id: str
|
||||||
email: str
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------- CONTAINER Routes ----------------------
|
class UpdateContainerRequest(ContainerModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InfoContainerRequest(BaseModel):
|
||||||
|
container_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------- Routes ----------------------
|
||||||
@app.get("/", include_in_schema=False)
|
@app.get("/", include_in_schema=False)
|
||||||
def redirect_to_odoo():
|
def redirect_to_odoo():
|
||||||
return RedirectResponse(url="https://ODOO4PROJECTS.com")
|
return RedirectResponse(url="https://ODOO4PROJECTS.com")
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/update", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/update", dependencies=[Depends(verify_api_key)])
|
||||||
def update_container(request: UpdateContainerRequest):
|
def update_container(request: UpdateContainerRequest):
|
||||||
--> Insert the new container into the database. Create, if container does not exist
|
existing = execute_db("SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True)
|
||||||
|
if existing:
|
||||||
|
execute_db("""
|
||||||
|
UPDATE containers SET email=?, expires=?, tags=?, env=?, affiliate=?, image=?,
|
||||||
|
history=?, comment=?, domains=?, status=?, created=?, bump=?
|
||||||
|
WHERE UUID=?
|
||||||
|
""", (
|
||||||
|
request.email, request.expires, request.tags, request.env, request.affiliate,
|
||||||
|
request.image, request.history, request.comment, request.domains, request.status,
|
||||||
|
request.created, request.bump, request.UUID
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
execute_db("""
|
||||||
|
INSERT INTO containers (UUID, email, expires, tags, env, affiliate, image, history,
|
||||||
|
comment, domains, status, created, bump)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""", (
|
||||||
|
request.UUID, request.email, request.expires, request.tags, request.env,
|
||||||
|
request.affiliate, request.image, request.history, request.comment,
|
||||||
|
request.domains, request.status, request.created, request.bump
|
||||||
|
))
|
||||||
|
return {"message": "Container updated or created"}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/start", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/start", dependencies=[Depends(verify_api_key)])
|
||||||
def start_container(request: StartContainerRequest):
|
def start_container(request: ContainerIDRequest):
|
||||||
--> refactor, so that only the container is is given in the request. the shell script BIN_PATH/startContainer is called withtge containerid as parameter
|
return {"message": run_command([f"{BIN_PATH}/startContainer", request.container_id])}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/stop", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/stop", dependencies=[Depends(verify_api_key)])
|
||||||
def stop_container(request: StopContainerRequest):
|
def stop_container(request: ContainerIDRequest):
|
||||||
--> refactor, so that only the container is is given in the request. the shell script BIN_PATH/stopContainer is called withtge containerid as parameter
|
return {"message": run_command([f"{BIN_PATH}/stopContainer", request.container_id])}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/nuke", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/nuke", dependencies=[Depends(verify_api_key)])
|
||||||
def nuke_container(request: StopContainerRequest):
|
def nuke_container(request: ContainerIDRequest):
|
||||||
--> refactor, so that only the container is is given in the request.
|
status = execute_db("SELECT status FROM containers WHERE UUID=?", (request.container_id,), fetch=True)
|
||||||
when the status of the database is "nuke" then
|
if not status or status[0][0] != "nuke":
|
||||||
the shell script BIN_PATH/nukeContainer is called withtge containerid as parameter
|
raise HTTPException(400, "Container status is not 'nuke'")
|
||||||
|
return {"message": run_command([f"{BIN_PATH}/nukeContainer", request.container_id])}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/info", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/info", dependencies=[Depends(verify_api_key)])
|
||||||
def info_container(request: InfoContainerRequest):
|
def info_container(request: InfoContainerRequest):
|
||||||
--> refactor: When no container id is given, query all containers from the database. when a containeris is given, just select that one. Return the result
|
if request.container_id:
|
||||||
|
rows = execute_db("SELECT * FROM containers WHERE UUID=?", (request.container_id,), fetch=True)
|
||||||
--> create an endpoint that changes the container image to the latest version of that image /container/bump besides updating the container image, also update the SQL for this container and put the current date into "bump"
|
else:
|
||||||
|
rows = execute_db("SELECT * FROM containers", fetch=True)
|
||||||
--> add /container/quota return the disk and ram usage of this container. I think you can obtain this from docker
|
return {"containers": rows}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/container/bump", dependencies=[Depends(verify_api_key)])
|
||||||
# ------------------------ SYSTEM Routes
|
def bump_container(request: ContainerIDRequest):
|
||||||
|
today = datetime.utcnow().strftime("%Y-%m-%d")
|
||||||
|
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: ContainerIDRequest):
|
||||||
|
output = run_command([
|
||||||
|
"docker", "stats", request.container_id, "--no-stream",
|
||||||
|
"--format", "{{.MemUsage}},{{.BlockIO}}"
|
||||||
|
])
|
||||||
|
mem_usage, disk_usage = output.split(",")
|
||||||
|
return {"memory_usage": mem_usage, "disk_io": disk_usage}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------- SYSTEM ----------------------
|
||||||
@app.get("/system/containers", dependencies=[Depends(verify_api_key)])
|
@app.get("/system/containers", dependencies=[Depends(verify_api_key)])
|
||||||
def get_containers():
|
def get_containers():
|
||||||
result = subprocess.run([BIN_PATH+'/getContainers'], capture_output=True, text=True)
|
return Response(content=run_command([f"{BIN_PATH}/getContainers"]), media_type="application/json")
|
||||||
if result.returncode != 0:
|
|
||||||
return Response(content='{"error": "Script failed"}', media_type="application/json", status_code=500)
|
|
||||||
return Response(content=result.stdout, media_type="application/json")
|
@app.get("/system/images", dependencies=[Depends(verify_api_key)])
|
||||||
|
def list_images():
|
||||||
|
images = run_command(["docker", "images", "--format", "{{.Repository}}:{{.Tag}}"])
|
||||||
|
return {"images": images.split("\n")}
|
||||||
|
|
||||||
--> create an endpoint that lkists all docker images available on the system /system/images
|
|
||||||
|
|
||||||
@app.get("/system/info", dependencies=[Depends(verify_api_key)])
|
@app.get("/system/info", dependencies=[Depends(verify_api_key)])
|
||||||
def get_system_info():
|
def get_system_info():
|
||||||
return all INFOas JSON
|
|
||||||
in this function add last_update and return the content of /4server/data/update
|
|
||||||
if this file does not exist return NONE
|
|
||||||
also add the latest bump date of all images in the database
|
|
||||||
return the VERSION as well.
|
|
||||||
try:
|
try:
|
||||||
|
alpine_version = None
|
||||||
|
last_update = None
|
||||||
|
bump_dates = execute_db("SELECT MAX(bump) FROM containers", fetch=True)[0][0]
|
||||||
|
if os.path.exists("/4server/data/update"):
|
||||||
|
with open("/4server/data/update") as f:
|
||||||
|
last_update = f.read().strip()
|
||||||
|
if os.path.exists("/etc/alpine-release"):
|
||||||
with open("/etc/alpine-release") as f:
|
with open("/etc/alpine-release") as f:
|
||||||
version = f.read().strip()
|
alpine_version = f.read().strip()
|
||||||
return {"alpine_version": version}
|
mem = psutil.virtual_memory()
|
||||||
except FileNotFoundError:
|
disk = psutil.disk_usage("/")
|
||||||
raise HTTPException(status_code=404, detail="File not found. Press play on tape")
|
cpu_count = psutil.cpu_count(logical=True)
|
||||||
|
return {
|
||||||
|
"alpine_version": alpine_version,
|
||||||
|
"last_update": last_update,
|
||||||
|
"latest_bump": bump_dates,
|
||||||
|
"version": VERSION,
|
||||||
|
"resources": {
|
||||||
|
"memory": {"total": mem.total, "available": mem.available, "used": mem.used},
|
||||||
|
"disk": {"total": disk.total, "used": disk.used, "free": disk.free},
|
||||||
|
"cpu_count": cpu_count
|
||||||
|
}
|
||||||
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.get("/system/resources", dependencies=[Depends(verify_api_key)])
|
@app.post("/system/pull", dependencies=[Depends(verify_api_key)])
|
||||||
def get_resources():
|
def pull_all_images():
|
||||||
|
return {"message": run_command([f"{BIN_PATH}/pullAllContainers"])}
|
||||||
|
|
||||||
--> consolidate this API into /system/info
|
|
||||||
|
|
||||||
mem = psutil.virtual_memory()
|
|
||||||
disk = psutil.disk_usage("/")
|
|
||||||
return {
|
|
||||||
"memory": {"total": mem.total, "available": mem.available, "used": mem.used},
|
|
||||||
"disk": {"total": disk.total, "used": disk.used, "free": disk.free},
|
|
||||||
"cpu_count": psutil.cpu_count(logical=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
--> create an endpoint that docker pulls all containers /system/pull
|
|
||||||
|
|
||||||
# ---------------------- Entry Point ----------------------
|
# ---------------------- Entry Point ----------------------
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,14 +1,55 @@
|
|||||||
this script gets a container UUID
|
#!/bin/bash
|
||||||
|
# Usage: ./start_by_uuid.sh <uuid>
|
||||||
|
# Example: ./start_by_uuid.sh abc-001-xxxx-xxxx-xxxx
|
||||||
|
|
||||||
Wtrite a bash script, that gets all info out off the Sqlite3 Database usind the UUID and stores the values in environment variables
|
DB_PATH="/4server/data/contracts.db"
|
||||||
|
BIN_PATH="/4server/sbin"
|
||||||
|
|
||||||
setr the BIN_BATH to /4srver/sbin/
|
UUID="$1"
|
||||||
|
|
||||||
the uuid looks like xxx-xxx-xxxx-xxxx-xxxx-....
|
if [[ -z "$UUID" ]]; then
|
||||||
|
echo "Usage: $0 <uuid>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
get the second number
|
# Fetch all info from SQLite and export as environment variables
|
||||||
|
eval $(sqlite3 -separator "=" "$DB_PATH" \
|
||||||
|
"SELECT 'UUID=' || UUID,
|
||||||
|
'EMAIL=' || email,
|
||||||
|
'EXPIRES=' || expires,
|
||||||
|
'TAGS=' || tags,
|
||||||
|
'ENV=' || env,
|
||||||
|
'AFFILIATE=' || affiliate,
|
||||||
|
'IMAGE=' || image,
|
||||||
|
'HISTORY=' || history,
|
||||||
|
'COMMENT=' || comment,
|
||||||
|
'DOMAINS=' || domains,
|
||||||
|
'STATUS=' || status,
|
||||||
|
'CREATED=' || created,
|
||||||
|
'BUMP=' || bump
|
||||||
|
FROM containers WHERE UUID='$UUID';" | sed 's/ /\\ /g')
|
||||||
|
|
||||||
|
# Debug: print loaded environment variables
|
||||||
|
# env | grep -E 'UUID|EMAIL|EXPIRES|TAGS|ENV|AFFILIATE|IMAGE|HISTORY|COMMENT|DOMAINS|STATUS|CREATED|BUMP'
|
||||||
|
|
||||||
|
# Extract the second part of UUID (split by "-")
|
||||||
|
SECOND_PART=$(echo "$UUID" | cut -d'-' -f2)
|
||||||
|
|
||||||
|
# Decide which script to run
|
||||||
|
case "$SECOND_PART" in
|
||||||
|
001)
|
||||||
|
"$BIN_PATH/start/n8n"
|
||||||
|
;;
|
||||||
|
002)
|
||||||
|
"$BIN_PATH/start/ODOO18"
|
||||||
|
;;
|
||||||
|
003)
|
||||||
|
"$BIN_PATH/start/ODOO19"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown UUID type: $SECOND_PART"
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
When the number is 001 call the script BIN_PATH/start/n8n
|
|
||||||
When the number is 002 call the sctipr BIN_ÜATH/start/ODOO18
|
|
||||||
WHen the number is 003 call the script BIN_PATH/start/ODOO19
|
|
||||||
|
|
||||||
|
|||||||
233
app/test/api.json
Normal file
233
app/test/api.json
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Container Management API",
|
||||||
|
"version": "0.0.5",
|
||||||
|
"description": "API for managing containers and system resources."
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{ "url": "http://localhost:8888" }
|
||||||
|
],
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {
|
||||||
|
"ApiKeyAuth": {
|
||||||
|
"type": "apiKey",
|
||||||
|
"in": "header",
|
||||||
|
"name": "X-API-Key"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {
|
||||||
|
"ContainerModel": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"UUID": { "type": "string" },
|
||||||
|
"email": { "type": "string" },
|
||||||
|
"expires": { "type": "string", "format": "date" },
|
||||||
|
"tags": { "type": "string" },
|
||||||
|
"env": { "type": "string" },
|
||||||
|
"affiliate": { "type": "string" },
|
||||||
|
"image": { "type": "string" },
|
||||||
|
"history": { "type": "string" },
|
||||||
|
"comment": { "type": "string" },
|
||||||
|
"domains": { "type": "string" },
|
||||||
|
"status": { "type": "string" },
|
||||||
|
"created": { "type": "string", "format": "date" },
|
||||||
|
"bump": { "type": "string", "format": "date" }
|
||||||
|
},
|
||||||
|
"required": ["UUID", "email", "expires", "status", "created"]
|
||||||
|
},
|
||||||
|
"ContainerIDRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"container_id": { "type": "string" }
|
||||||
|
},
|
||||||
|
"required": ["container_id"]
|
||||||
|
},
|
||||||
|
"InfoContainerRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"container_id": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"paths": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Root redirect",
|
||||||
|
"responses": {
|
||||||
|
"302": { "description": "Redirect to ODOO4PROJECTS.com" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/container/update": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Create or update container",
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": { "$ref": "#/components/schemas/ContainerModel" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Container updated or created",
|
||||||
|
"content": { "application/json": { "example": { "message": "Container updated or created" } } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/container/start": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Start a container",
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"requestBody": {
|
||||||
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContainerIDRequest" } } }
|
||||||
|
},
|
||||||
|
"responses": { "200": { "description": "Container started" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/container/stop": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Stop a container",
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"requestBody": {
|
||||||
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContainerIDRequest" } } }
|
||||||
|
},
|
||||||
|
"responses": { "200": { "description": "Container stopped" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/container/nuke": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Nuke a container",
|
||||||
|
"description": "Permanently deletes container if its status is 'nuke'.",
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"requestBody": {
|
||||||
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContainerIDRequest" } } }
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": { "description": "Container nuked" },
|
||||||
|
"400": { "description": "Container status is not 'nuke'" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/container/info": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Get container info",
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"requestBody": {
|
||||||
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/InfoContainerRequest" } } }
|
||||||
|
},
|
||||||
|
"responses": { "200": { "description": "Container info list" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/container/bump": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Bump a container",
|
||||||
|
"description": "Updates bump date and runs bump script.",
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"requestBody": {
|
||||||
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContainerIDRequest" } } }
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": { "description": "Bump successful", "content": { "application/json": {} } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/container/quota": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Get container quota",
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"requestBody": {
|
||||||
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContainerIDRequest" } } }
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Quota usage",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"example": {
|
||||||
|
"memory_usage": "50MiB / 2GiB",
|
||||||
|
"disk_io": "10MB / 200MB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/system/containers": {
|
||||||
|
"get": {
|
||||||
|
"summary": "List running containers",
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Containers in system",
|
||||||
|
"content": { "application/json": {} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/system/images": {
|
||||||
|
"get": {
|
||||||
|
"summary": "List system images",
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "List of docker images",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"example": {
|
||||||
|
"images": ["repo1:tag", "repo2:tag"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/system/info": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Get system info",
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "System resource info",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"example": {
|
||||||
|
"alpine_version": "3.18.2",
|
||||||
|
"last_update": "2025-08-01",
|
||||||
|
"latest_bump": "2025-08-10",
|
||||||
|
"version": "API: 0.0.5",
|
||||||
|
"resources": {
|
||||||
|
"memory": { "total": 16777216, "available": 8388608, "used": 8388608 },
|
||||||
|
"disk": { "total": 100000000, "used": 50000000, "free": 50000000 },
|
||||||
|
"cpu_count": 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/system/pull": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Pull all images",
|
||||||
|
"security": [{ "ApiKeyAuth": [] }],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "All images pulled",
|
||||||
|
"content": { "application/json": { "example": { "message": "All images pulled" } } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
write a curl request that creatrs a container with
|
|
||||||
a uuid starting with 000-001-....
|
|
||||||
email o.arnold@projektbox.de
|
|
||||||
domain n8n.local
|
|
||||||
and the rest with sane info
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
53
app/test/runner.sh
Normal file
53
app/test/runner.sh
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# run_all_tests.sh — simple runner for test_api.sh
|
||||||
|
#
|
||||||
|
|
||||||
|
API_SCRIPT="./test_api.sh"
|
||||||
|
|
||||||
|
# Make sure test_api.sh is executable
|
||||||
|
if [ ! -x "$API_SCRIPT" ]; then
|
||||||
|
echo "Error: $API_SCRIPT not found or not executable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# List of tests to run (must match functions in test_api.sh)
|
||||||
|
TESTS=(
|
||||||
|
root_redirect
|
||||||
|
update_container
|
||||||
|
start_container
|
||||||
|
stop_container
|
||||||
|
nuke_container
|
||||||
|
info_all
|
||||||
|
info_one
|
||||||
|
bump_container
|
||||||
|
container_quota
|
||||||
|
system_containers
|
||||||
|
system_images
|
||||||
|
system_info
|
||||||
|
system_pull
|
||||||
|
)
|
||||||
|
|
||||||
|
ok_count=0
|
||||||
|
fail_count=0
|
||||||
|
|
||||||
|
echo "Running API tests..."
|
||||||
|
echo "===================="
|
||||||
|
|
||||||
|
for test in "${TESTS[@]}"; do
|
||||||
|
# Run test, capture HTTP status code only
|
||||||
|
status=$($API_SCRIPT $test -s -o /dev/null -w "%{http_code}")
|
||||||
|
|
||||||
|
if [[ "$status" == "200" || "$status" == "302" ]]; then
|
||||||
|
echo "[OK ] $test"
|
||||||
|
((ok_count++))
|
||||||
|
else
|
||||||
|
echo "[NOK] $test (HTTP $status)"
|
||||||
|
((fail_count++))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "===================="
|
||||||
|
echo "Summary: $ok_count OK, $fail_count NOK"
|
||||||
|
exit $fail_count
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
start the container
|
|
||||||
|
|
||||||
000-001-
|
|
||||||
|
|
||||||
add a random uuid
|
|
||||||
141
app/test/test.sh
Normal file
141
app/test/test.sh
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# test_api.sh — cURL test suite for Container Management API
|
||||||
|
#
|
||||||
|
|
||||||
|
# ========= CONFIG =========
|
||||||
|
API_KEY="your-secret-api-key"
|
||||||
|
BASE_URL="http://localhost:8888"
|
||||||
|
CONTAINER_ID="123e4567-e89b-12d3-a456-426614174000" # sample UUID
|
||||||
|
# ==========================
|
||||||
|
|
||||||
|
# --- Functions for each endpoint ---
|
||||||
|
|
||||||
|
root_redirect() {
|
||||||
|
curl -i "$BASE_URL/"
|
||||||
|
}
|
||||||
|
|
||||||
|
update_container() {
|
||||||
|
curl -X POST "$BASE_URL/container/update" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: $API_KEY" \
|
||||||
|
-d '{
|
||||||
|
"UUID": "'"$CONTAINER_ID"'",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"expires": "2025-12-31",
|
||||||
|
"status": "active",
|
||||||
|
"created": "2025-08-16"
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
start_container() {
|
||||||
|
curl -X POST "$BASE_URL/container/start" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: $API_KEY" \
|
||||||
|
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_container() {
|
||||||
|
curl -X POST "$BASE_URL/container/stop" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: $API_KEY" \
|
||||||
|
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
|
||||||
|
}
|
||||||
|
|
||||||
|
nuke_container() {
|
||||||
|
curl -X POST "$BASE_URL/container/nuke" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: $API_KEY" \
|
||||||
|
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
|
||||||
|
}
|
||||||
|
|
||||||
|
info_all() {
|
||||||
|
curl -X POST "$BASE_URL/container/info" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: $API_KEY" \
|
||||||
|
-d '{}'
|
||||||
|
}
|
||||||
|
|
||||||
|
info_one() {
|
||||||
|
curl -X POST "$BASE_URL/container/info" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: $API_KEY" \
|
||||||
|
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
|
||||||
|
}
|
||||||
|
|
||||||
|
bump_container() {
|
||||||
|
curl -X POST "$BASE_URL/container/bump" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: $API_KEY" \
|
||||||
|
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
|
||||||
|
}
|
||||||
|
|
||||||
|
container_quota() {
|
||||||
|
curl -X POST "$BASE_URL/container/quota" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: $API_KEY" \
|
||||||
|
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
|
||||||
|
}
|
||||||
|
|
||||||
|
system_containers() {
|
||||||
|
curl -X GET "$BASE_URL/system/containers" \
|
||||||
|
-H "X-API-Key: $API_KEY"
|
||||||
|
}
|
||||||
|
|
||||||
|
system_images() {
|
||||||
|
curl -X GET "$BASE_URL/system/images" \
|
||||||
|
-H "X-API-Key: $API_KEY"
|
||||||
|
}
|
||||||
|
|
||||||
|
system_info() {
|
||||||
|
curl -X GET "$BASE_URL/system/info" \
|
||||||
|
-H "X-API-Key: $API_KEY"
|
||||||
|
}
|
||||||
|
|
||||||
|
system_pull() {
|
||||||
|
curl -X POST "$BASE_URL/system/pull" \
|
||||||
|
-H "X-API-Key: $API_KEY"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Help message ---
|
||||||
|
show_help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $0 <command>
|
||||||
|
|
||||||
|
Available commands:
|
||||||
|
root_redirect - Test root redirect
|
||||||
|
update_container - Create or update container
|
||||||
|
start_container - Start container
|
||||||
|
stop_container - Stop container
|
||||||
|
nuke_container - Nuke container (status must be "nuke")
|
||||||
|
info_all - Get info for all containers
|
||||||
|
info_one - Get info for one container
|
||||||
|
bump_container - Bump container
|
||||||
|
container_quota - Show container quota
|
||||||
|
system_containers - List running containers
|
||||||
|
system_images - List docker images
|
||||||
|
system_info - Show system information
|
||||||
|
system_pull - Pull all container images
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$0 update_container
|
||||||
|
$0 system_info
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Main ---
|
||||||
|
if [ $# -eq 0 ] || [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmd="$1"
|
||||||
|
shift
|
||||||
|
if declare -f "$cmd" >/dev/null 2>&1; then
|
||||||
|
"$cmd" "$@"
|
||||||
|
else
|
||||||
|
echo "Unknown command: $cmd"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
@@ -6,4 +6,6 @@ services:
|
|||||||
- ./app:/app
|
- ./app:/app
|
||||||
tty: true
|
tty: true
|
||||||
privileged: true
|
privileged: true
|
||||||
|
extra_hosts:
|
||||||
|
- "dev:192.168.9.221"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user