This commit is contained in:
Oliver
2025-09-07 07:57:12 +02:00
parent 80a5062126
commit ac7a7793e7
23 changed files with 1086 additions and 65 deletions

View File

@@ -0,0 +1,91 @@
#!/bin/bash
dump_config (){
echo "========== Odoo Container Configuration =========="
echo "UUID: $UUID"
echo "BRANCH: $BRANCH"
echo "STAGING: $STAGING"
echo
echo "PostgreSQL Host: $POSTGRES_HOST"
echo "PostgreSQL Port: $POSTGRES_PORT"
echo "PostgreSQL Admin: $POSTGRES_ADMIN_USER / $POSTGRES_ADMIN_PASSWORD"
echo "ODOO DB User: $ODOO_DB_USER"
echo "ODOO DB Password: $ODOO_DB_PASSWORD"
echo
echo "BASEURL: $BASEURL"
echo "DATA_DIR: $DATA_DIR"
echo "CUSTOM_DIR: $CUSTOM_DIR"
echo "ENTERPRISE_DIR: $ENTERPRISE_DIR"
echo "LOGS_DIR: $LOGS_DIR"
echo "CONFIG_DIR: $CONFIG_DIR"
echo "CC_DIR: $CC_DIR"
echo "BACKUP_DIR: $BACKUP_DIR"
echo "GIT_DIR: $GIT_DIR"
echo "ETC_DIR: $ETC_DIR"
echo "INSTALL_DIR: $INSTALL_DIR"
echo "SSH_DIR: $SSH_DIR"
echo "HUGO_DIR: $HUGO_DIR"
echo
echo "SERVER_IP: $SERVER_IP"
echo "=================================================="
}
# -----------------------------
# Function: Create PostgreSQL user
# -----------------------------
check_and_create_db() {
echo "Connecting as $POSTGRES_ADMIN_USER to $POSTGRES_HOST:$POSTGRES_PORT"
# -----------------------------
# Check if user exists
# -----------------------------
USER_EXISTS=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='$ODOO_DB_USER';" | grep -q 1 && echo "yes" || echo "no")
if [ "$USER_EXISTS" = "no" ]; then
echo "Creating PostgreSQL user $ODOO_DB_USER..."
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -c "CREATE USER \"$ODOO_DB_USER\" WITH PASSWORD '$ODOO_DB_PASSWORD';"
fi
# -----------------------------
# Check if database exists
# -----------------------------
DB_EXISTS=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='$UUID';" | grep -q 1 && echo "yes" || echo "no")
if [ "$DB_EXISTS" = "no" ]; then
/4server/sbin/ODOO_19/restore $UUID default.zip
fi
}
# -----------------------------
# Function: Check DNS and build Traefik labels
# -----------------------------
check_domains() {
local domains="$1"
local server_ip="$2"
echo "Checking DNS resolution for domains: $domains"
local filtered_domains=""
for domain in $domains; do
ns_ip=$(nslookup "$domain" 2>/dev/null | grep -Eo 'Address: ([0-9]{1,3}\.){3}[0-9]{1,3}' | awk '{print $2}' | tail -n1)
if [[ "$ns_ip" == "$server_ip" ]]; then
filtered_domains+=" $domain"
fi
done
filtered_domains=$(echo "$filtered_domains" | xargs)
DOMAIN_LABEL=""
for domain in $filtered_domains; do
if [ -z "$DOMAIN_LABEL" ]; then
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=Host(\`$domain\`)"
else
DOMAIN_LABEL+=" || Host(\`$domain\`)"
fi
done
echo "$DOMAIN_LABEL"
}

59
app/sbin/ODOO_19/restore Executable file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
export PATH=/4PROJECTS/bin:$PATH
if [ ! -n "$2" ]; then
echo "Missing Parameters <UUID> <FILE>"
exit 0
fi
UUID=$1
source /4server/sbin/helpers
docker run $UUID
BACKUP="/mnt/backup/$2"
TEMPLATE="/mnt/db_images/$2"
echo "TEMPLATE"
docker exec "${1%}" /bin/bash -c "[ -f $TEMPLATE ]"
if docker exec "${1%}" /bin/bash -c "[ -f $BACKUP ]"; then
FILENAME="$BACKUP"
elif docker exec "${1%}" /bin/bash -c "[ -f $TEMPLATE ]"; then
FILENAME="$TEMPLATE"
else
echo "File not exists"
exit 0
fi
echo "Restoring $FILENAME to $1"
### DELETE DATABASE
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
-c "DROP DATABASE IF EXISTS \"$UUID\";"
echo "deleting old restore artifacts"
docker exec "${1%}" rm -rf /root/.local/
if [[ "$1" == *"_"* ]]; then
echo "Neutralize DB"
docker exec "${1%_}" odoo db --db_host beedb -w "${1%_}" -r "$1" load "$1" $FILENAME -f --neutralize
else
echo "No Neutralize DB"
docker exec "${1%}" odoo db --db_host beedb -w "${1%_}" -r "$1" load "$1" $FILENAME -f
fi
docker exec "${1%}" cp -r /root/.local/. /var/lib/odoo/.local/
docker exec "${1%}" chown -R odoo:odoo /var/lib/odoo/.local/
### transfer ownership
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
-c "ALTER DATABASE \"$UUID\" OWNER TO \"$UUID\";"
docker restart "${1%}"

53
app/sbin/nuke/ODOO_19 Executable file
View File

@@ -0,0 +1,53 @@
#!/bin/bash
# Load functions
source /4server/sbin/ODOO_19/ODOO_19.lib
# Config variables
UUID="${UUID:-default}"
STAGING="${STAGING:-false}"
POSTGRES_HOST="${POSTGRES_HOST:-beedb}"
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
ODOO_DB_USER="${UUID}"
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
BASEURL="${BASEURL:-/4server/data/$UUID}"
doas docker stop "$UUID"
doas docker rm "$UUID"
if [ -n "${UUID:-}" ]; then
echo "Removing directory: $BASEURL"
doas rm -rf "$BASEURL"
fi
echo "Dropping PostgreSQL database: $UUID (if exists)..."
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
-c "DROP DATABASE IF EXISTS \"$UUID\";"
echo "Dropping PostgreSQL user: $ODOO_DB_USER (if exists)..."
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres <<EOF
DO
\$do\$
BEGIN
IF EXISTS (
SELECT
FROM pg_catalog.pg_user
WHERE usename = '${ODOO_DB_USER}') THEN
-- Drop user safely
EXECUTE 'DROP USER "' || '${ODOO_DB_USER}' || '"';
END IF;
END
\$do\$;
EOF
echo "✅ Database '$UUID' and user '$ODOO_DB_USER' removed (if they existed)."

36
app/sbin/nukeContainer Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Usage: ./start_by_uuid.sh <uuid>
# Example: ./start_by_uuid.sh abc-001-xxxx-xxxx-xxxx
exec > /4server/data/log/nukeContainer.log 2>&1
echo "$(date '+%Y-%m-%d %H:%M') Nuke container $1"
source /4server/sbin/helpers
BIN_PATH="/4server/sbin"
UUID="$1"
if [[ -z "$UUID" ]]; then
echo "Usage: $0 <uuid>"
exit 1
fi
get_contract_info
# 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/nuke/n8n"
;;
002)
"$BIN_PATH/nuke/ODOO_19"
;;
*)
echo "Unknown UUID type: $SECOND_PART"
exit 2
;;
esac

7
app/sbin/pullAllContainers Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/bash
docker ps -a --format '{{.Image}}' | sort -u | xargs -n1 docker pull

View File

@@ -1,62 +0,0 @@
#!/bin/bash
export PATH=/4PROJECTS/bin:$PATH
if [ ! -n "$2" ]; then
echo "Missing Parameters <UUID> <FILE>"
exit 0
fi
UUID=$1
source /4server/sbin/helpers
get_contract_info
exit 0
BACKUP="/mnt/backup/$2"
TEMPLATE="/mnt/db_images/$2"
echo "$TEMPLATE"
if docker exec "${1%_}" /bin/bash -c "[ -f $BACKUP ]"; then
FILENAME="$BACKUP"
elif docker exec "${1%_}" /bin/bash -c "[ -f $TEMPLATE ]"; then
FILENAME="$TEMPLATE"
else
echo "File not exists"
exit 0
fi
echo "Restoring $FILENAME to $1"
deleteDB $1
echo "deleting old restore artifacts"
docker exec "${1%_}" rm -rf /root/.local/
if [[ "$1" == *"_"* ]]; then
echo "Neutralize DB"
docker exec "${1%_}" odoo db --db_host beedb -w "${1%_}" -r "$1" load "$1" $FILENAME -f --neutralize
else
echo "No Neutralize DB"
docker exec "${1%_}" odoo db --db_host beedb -w "${1%_}" -r "$1" load "$1" $FILENAME -f
fi
docker exec "${1%_}" cp -r /root/.local/. /var/lib/odoo/.local/
docker exec "${1%_}" chown -R odoo:odoo /var/lib/odoo/.local/
docker restart "${1%_}"
echo "ou"
chownDB $1
if [ "$2" != "default.zip" ]; then
exit 1
fi
curl --header "Content-Type: application/json" --request POST --data '{"id":"'$CONTRACT_ID'"}' https://odoo4projects.com/web/hook/12a080e8-fcc0-4e10-8dc5-7587d3e08682

View File

@@ -0,0 +1,91 @@
#!/bin/bash
dump_config (){
echo "========== Odoo Container Configuration =========="
echo "UUID: $UUID"
echo "BRANCH: $BRANCH"
echo "STAGING: $STAGING"
echo
echo "PostgreSQL Host: $POSTGRES_HOST"
echo "PostgreSQL Port: $POSTGRES_PORT"
echo "PostgreSQL Admin: $POSTGRES_ADMIN_USER / $POSTGRES_ADMIN_PASSWORD"
echo "ODOO DB User: $ODOO_DB_USER"
echo "ODOO DB Password: $ODOO_DB_PASSWORD"
echo
echo "BASEURL: $BASEURL"
echo "DATA_DIR: $DATA_DIR"
echo "CUSTOM_DIR: $CUSTOM_DIR"
echo "ENTERPRISE_DIR: $ENTERPRISE_DIR"
echo "LOGS_DIR: $LOGS_DIR"
echo "CONFIG_DIR: $CONFIG_DIR"
echo "CC_DIR: $CC_DIR"
echo "BACKUP_DIR: $BACKUP_DIR"
echo "GIT_DIR: $GIT_DIR"
echo "ETC_DIR: $ETC_DIR"
echo "INSTALL_DIR: $INSTALL_DIR"
echo "SSH_DIR: $SSH_DIR"
echo "HUGO_DIR: $HUGO_DIR"
echo
echo "SERVER_IP: $SERVER_IP"
echo "=================================================="
}
# -----------------------------
# Function: Create PostgreSQL user
# -----------------------------
check_and_create_db() {
echo "Connecting as $POSTGRES_ADMIN_USER to $POSTGRES_HOST:$POSTGRES_PORT"
# -----------------------------
# Check if user exists
# -----------------------------
USER_EXISTS=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='$ODOO_DB_USER';" | grep -q 1 && echo "yes" || echo "no")
if [ "$USER_EXISTS" = "no" ]; then
echo "Creating PostgreSQL user $ODOO_DB_USER..."
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -c "CREATE USER \"$ODOO_DB_USER\" WITH PASSWORD '$ODOO_DB_PASSWORD';"
fi
# -----------------------------
# Check if database exists
# -----------------------------
DB_EXISTS=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='$UUID';" | grep -q 1 && echo "yes" || echo "no")
if [ "$DB_EXISTS" = "no" ]; then
/4server/sbin/ODOO_19/restore $UUID default.zip
fi
}
# -----------------------------
# Function: Check DNS and build Traefik labels
# -----------------------------
check_domains() {
local domains="$1"
local server_ip="$2"
echo "Checking DNS resolution for domains: $domains"
local filtered_domains=""
for domain in $domains; do
ns_ip=$(nslookup "$domain" 2>/dev/null | grep -Eo 'Address: ([0-9]{1,3}\.){3}[0-9]{1,3}' | awk '{print $2}' | tail -n1)
if [[ "$ns_ip" == "$server_ip" ]]; then
filtered_domains+=" $domain"
fi
done
filtered_domains=$(echo "$filtered_domains" | xargs)
DOMAIN_LABEL=""
for domain in $filtered_domains; do
if [ -z "$DOMAIN_LABEL" ]; then
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=Host(\`$domain\`)"
else
DOMAIN_LABEL+=" || Host(\`$domain\`)"
fi
done
echo "$DOMAIN_LABEL"
}

59
app/sbin/sbin/ODOO_19/restore Executable file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
export PATH=/4PROJECTS/bin:$PATH
if [ ! -n "$2" ]; then
echo "Missing Parameters <UUID> <FILE>"
exit 0
fi
source /4server/sbin/helpers
echo "restore"
dump_config
BACKUP="/mnt/backup/$2"
TEMPLATE="/mnt/db_images/$2"
echo "TEMPLATE"
doas docker exec "${1%}" /bin/bash -c "[ -f $TEMPLATE ]"
if doas docker exec "${1%}" /bin/bash -c "[ -f $BACKUP ]"; then
FILENAME="$BACKUP"
elif doas docker exec "${1%}" /bin/bash -c "[ -f $TEMPLATE ]"; then
FILENAME="$TEMPLATE"
else
echo "File not exists"
exit 0
fi
echo "Restoring $FILENAME to $1"
### DELETE DATABASE
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
-c "DROP DATABASE IF EXISTS \"$UUID\";"
echo "deleting old restore artifacts"
doas docker exec "${1%}" rm -rf /root/.local/
if [[ "$1" == *"_"* ]]; then
echo "Neutralize DB"
doas docker exec "${1%_}" odoo db --db_host beedb -w "${1%_}" -r "$1" load "$1" $FILENAME -f --neutralize
else
echo "No Neutralize DB"
doas docker exec "${1%}" odoo db --db_host beedb -w "${1%_}" -r "$1" load "$1" $FILENAME -f
fi
doas docker exec "${1%}" cp -r /root/.local/. /var/lib/odoo/.local/
doas docker exec "${1%}" chown -R odoo:odoo /var/lib/odoo/.local/
### transfer ownership
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
-c "ALTER DATABASE \"$UUID\" OWNER TO \"$UUID\";"
doas docker restart "${1%}"

256
app/sbin/sbin/api Executable file
View File

@@ -0,0 +1,256 @@
#!/usr/bin/env python3
from fastapi import FastAPI, HTTPException, Depends, Response
from fastapi.security.api_key import APIKeyHeader
from fastapi.responses import RedirectResponse
from pydantic import BaseModel
import psutil
import sqlite3
import subprocess
import os
import uvicorn
from typing import Dict, Any, Optional
from datetime import datetime
import json
# ---------------------- Constants ----------------------
DB_PATH = "/4server/data/contracts.db"
BIN_PATH = "/4server/sbin"
API_KEY = os.getenv("API_KEY", "your-secret-api-key")
VERSION = "API: 0.0.7"
# ---------------------- FastAPI App ----------------------
app = FastAPI()
api_key_header = APIKeyHeader(name="X-API-Key")
def verify_api_key(key: str = Depends(api_key_header)):
if key != API_KEY:
raise HTTPException(status_code=403, detail="Unauthorized")
# ---------------------- 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():
"""Initialize the database with containers table."""
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS containers (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
UUID CHAR(50) UNIQUE,
email CHAR(100),
expires DATE,
tags TEXT,
env TEXT,
affiliate CHAR(30),
image CHAR(50),
history TEXT,
comment TEXT,
domains TEXT,
status CHAR(20),
created DATE,
bump DATE,
secret TEXT
)
''')
conn.commit()
conn.close()
def execute_db(query: str, params: tuple = (), fetch: bool = False):
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute(query, params)
conn.commit()
data = cursor.fetchall() if fetch else None
conn.close()
if data and fetch:
return [dict(row) for row in data]
return None
# ---------------------- Models ----------------------
class ContainerModel(BaseModel):
UUID: str
email: Optional[str] = None
expires: Optional[str] = None
tags: Optional[str] = None
env: Optional[Dict[str, Any]] = None
affiliate: Optional[str] = None
image: Optional[str] = None
history: Optional[str] = None
comment: Optional[str] = None
domains: Optional[str] = None
status: Optional[str] = None
created: Optional[str] = None
bump: Optional[str] = None
secret: Optional[Dict[str, Any]] = None
class UUIDRequest(BaseModel):
UUID: str
# ---------------------- Routes ----------------------
@app.get("/", include_in_schema=False)
def redirect_to_odoo():
return RedirectResponse(url="https://ODOO4PROJECTS.com")
@app.post("/container/update", dependencies=[Depends(verify_api_key)])
def update_container(request: ContainerModel):
env_str = json.dumps(request.env) if isinstance(request.env, dict) else request.env
secret_str = json.dumps(request.secret) if isinstance(request.secret, dict) else request.secret
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=?, secret=?
WHERE UUID=?
""", (
request.email, request.expires, request.tags, env_str, request.affiliate,
request.image, request.history, request.comment, request.domains, request.status,
request.created, request.bump, secret_str, request.UUID
))
else:
execute_db("""
INSERT INTO containers (UUID, email, expires, tags, env, affiliate, image, history,
comment, domains, status, created, bump, secret)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
request.UUID, request.email, request.expires, request.tags, env_str,
request.affiliate, request.image, request.history, request.comment,
request.domains, request.status, request.created, request.bump, secret_str
))
return {"message": "Container updated or created"}
@app.post("/container/start", dependencies=[Depends(verify_api_key)])
def start_container(request: UUIDRequest):
return {"message": run_command([f"{BIN_PATH}/startContainer", request.UUID])}
@app.post("/container/stop", dependencies=[Depends(verify_api_key)])
def stop_container(request: UUIDRequest):
return {"message": run_command([f"{BIN_PATH}/stopContainer", request.UUID])}
@app.post("/container/nuke", dependencies=[Depends(verify_api_key)])
def nuke_container(request: UUIDRequest):
status = execute_db("SELECT status FROM containers WHERE UUID=?", (request.UUID,), 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])}
@app.post("/container/info", dependencies=[Depends(verify_api_key)])
def info_container(request: Optional[UUIDRequest] = None):
fields = [
"ID", "UUID", "email", "expires", "tags", "env", "affiliate",
"image", "history", "comment", "domains", "status", "created"
]
field_str = ", ".join(fields)
if request:
rows = execute_db(
f"SELECT {field_str} FROM containers WHERE UUID=?",
(request.UUID,),
fetch=True
)
else:
rows = execute_db(
f"SELECT {field_str} FROM containers",
fetch=True
)
# Map rows to dicts with field names
containers = [dict(zip(fields, row)) for row in rows]
# Wrap in N8N JSON format
n8n_items = [{"json": container} for container in containers]
return n8n_items
@app.post("/container/bump", dependencies=[Depends(verify_api_key)])
def bump_container(request: UUIDRequest):
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])
return {"message": msg, "bump_date": today}
@app.post("/container/quota", dependencies=[Depends(verify_api_key)])
def container_quota(request: UUIDRequest):
output = run_command([
"docker", "stats", request.UUID, "--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)])
def get_containers():
return Response(content=run_command([f"{BIN_PATH}/getContainers"]), 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")}
@app.get("/system/info", dependencies=[Depends(verify_api_key)])
def get_system_info():
try:
alpine_version = None
last_update = None
bump_dates = execute_db("SELECT MAX(bump) AS latest_bump FROM containers", fetch=True)[0]["latest_bump"]
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:
alpine_version = f.read().strip()
mem = psutil.virtual_memory()
disk = psutil.disk_usage("/")
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:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/system/pull", dependencies=[Depends(verify_api_key)])
def pull_all_images():
return {"message": run_command([f"{BIN_PATH}/pullAllContainers"])}
# ---------------------- Entry Point ----------------------
if __name__ == "__main__":
print(VERSION)
init_db()
uvicorn.run(app, host="10.5.0.1", port=8888)

61
app/sbin/sbin/getContainers Executable file
View File

@@ -0,0 +1,61 @@
#!/bin/bash
# Collect running container IDs
container_ids=$(docker ps -q)
# Start JSON array
echo "["
first=true
for cid in $container_ids; do
# Get basic info
name=$(docker inspect --format '{{.Name}}' "$cid" | sed 's/^\/\(.*\)/\1/')
image=$(docker inspect --format '{{.Config.Image}}' "$cid")
container_id=$(docker inspect --format '{{.Id}}' "$cid")
# RAM usage in bytes (from docker stats)
mem_usage=$(docker stats --no-stream --format "{{.MemUsage}}" "$cid")
# mem_usage looks like "12.34MiB / 1.944GiB" — extract the used part
mem_used=$(echo "$mem_usage" | awk '{print $1}')
# Convert mem_used to MiB as a number
# Support KiB, MiB, GiB units
ram_usage=$(echo "$mem_used" | awk '
{
val = substr($0, 1, length($0)-3)
unit = substr($0, length($0)-2, 3)
if (unit == "KiB") print val / 1024
else if (unit == "MiB") print val
else if (unit == "GiB") print val * 1024
else print 0
}')
# Traefik labels (extract labels JSON)
labels=$(docker inspect --format '{{json .Config.Labels}}' "$cid" | jq -r 'to_entries | map(select(.key | test("^traefik.*"))) | map({(.key): .value}) | add')
# Comma between JSON objects
if [ "$first" = false ]; then
echo ","
fi
first=false
# Output JSON object
jq -n \
--arg name "$name" \
--arg id "$container_id" \
--arg image "$image" \
--argjson ram "$ram_usage" \
--argjson tags "$labels" \
'{
name: $name,
id: $id,
image: $image,
ram_mb: $ram,
traefik_tags: $tags
}'
done
# End JSON array
echo "]"

35
app/sbin/sbin/helpers Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
get_contract_info() {
DB_PATH="/4server/data/contracts.db"
echo "get_contract_info $UUID"
while IFS="=" read -r key value; do
if [ -n "$key" ]; then
export "$key=$value"
fi
done < <(sqlite3 "$DB_PATH" "
SELECT 'UUID=' || UUID FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'EMAIL=' || email FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'EXPIRES=' || expires FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'TAGS=' || tags FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'ENV=' || env FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'AFFILIATE=' || affiliate FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'IMAGE=' || image FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'HISTORY=' || history FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'COMMENT=' || comment FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'DOMAINS=' || domains FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'STATUS=' || status FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'CREATED=' || created FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'SECRET=' || secret FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'BUMP=' || bump FROM containers WHERE UUID='$UUID';
")
# Debug: print loaded environment variables
env | grep -E 'UUID|EMAIL|EXPIRES|TAGS|ENV|AFFILIATE|IMAGE|HISTORY|COMMENT|DOMAINS|STATUS|CREATED|BUMP|SECRET'
eval $(echo "$ENV" | jq -r 'to_entries | .[] | "export \(.key)=\(.value)"')
}

BIN
app/sbin/sbin/nebula Executable file

Binary file not shown.

BIN
app/sbin/sbin/nebula-cert Executable file

Binary file not shown.

53
app/sbin/sbin/nuke/ODOO_19 Executable file
View File

@@ -0,0 +1,53 @@
#!/bin/bash
# Load functions
source /4server/sbin/ODOO_19/ODOO_19.lib
# Config variables
UUID="${UUID:-default}"
STAGING="${STAGING:-false}"
POSTGRES_HOST="${POSTGRES_HOST:-beedb}"
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
ODOO_DB_USER="${UUID}"
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
BASEURL="${BASEURL:-/4server/data/$UUID}"
doas docker stop "$UUID"
doas docker rm "$UUID"
if [ -n "${UUID:-}" ]; then
echo "Removing directory: $BASEURL"
doas rm -rf "$BASEURL"
fi
echo "Dropping PostgreSQL database: $UUID (if exists)..."
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
-c "DROP DATABASE IF EXISTS \"$UUID\";"
echo "Dropping PostgreSQL user: $ODOO_DB_USER (if exists)..."
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres <<EOF
DO
\$do\$
BEGIN
IF EXISTS (
SELECT
FROM pg_catalog.pg_user
WHERE usename = '${ODOO_DB_USER}') THEN
-- Drop user safely
EXECUTE 'DROP USER "' || '${ODOO_DB_USER}' || '"';
END IF;
END
\$do\$;
EOF
echo "✅ Database '$UUID' and user '$ODOO_DB_USER' removed (if they existed)."

36
app/sbin/sbin/nukeContainer Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Usage: ./start_by_uuid.sh <uuid>
# Example: ./start_by_uuid.sh abc-001-xxxx-xxxx-xxxx
exec > /4server/data/log/nukeContainer.log 2>&1
echo "$(date '+%Y-%m-%d %H:%M') Nuke container $1"
source /4server/sbin/helpers
BIN_PATH="/4server/sbin"
UUID="$1"
if [[ -z "$UUID" ]]; then
echo "Usage: $0 <uuid>"
exit 1
fi
get_contract_info
# 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/nuke/n8n"
;;
002)
"$BIN_PATH/nuke/ODOO_19"
;;
*)
echo "Unknown UUID type: $SECOND_PART"
exit 2
;;
esac

78
app/sbin/sbin/start/ODOO_19 Executable file
View File

@@ -0,0 +1,78 @@
#!/bin/bash
# Load functions
source /4server/sbin/ODOO_19/ODOO_19.lib
# Config variables
UUID="${UUID:-default}"
BRANCH="${BRANCH:-main}"
STAGING="${STAGING:-false}"
POSTGRES_HOST="${POSTGRES_HOST:-beedb}"
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
ODOO_DB_USER="${UUID}"
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
BASEURL="${BASEURL:-/4server/data/$UUID}"
DATA_DIR="$BASEURL/odoo/"
CUSTOM_DIR="$BASEURL/git/$UUID/custom/"
ENTERPRISE_DIR="$BASEURL/git/$UUID/enterprise/"
LOGS_DIR="$BASEURL/logs/"
CONFIG_DIR="$BASEURL/config/"
CC_DIR="$BASEURL/cc/"
BACKUP_DIR="/BACKUP/$UUID"
GIT_DIR="$BASEURL/git-server/"
ETC_DIR="$BASEURL/etc/"
INSTALL_DIR="$BASEURL/install/"
SSH_DIR="$BASEURL/.ssh/"
HUGO_DIR="$BASEURL/git-server/local/hugo"
SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1)
DOMAIN_LABEL=$(check_domains "$UUID.odoo4projects.com" "$SERVER_IP")
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=Host(\`$UUID.odoo4projects.com\`)"
echo "A"
PORT=$((RANDOM%1000+2200))
doas docker stop "$UUID" 2>/dev/null
doas docker rm "$UUID" 2>/dev/null
echo "B"
EXTRA_DOCKER_PARAMETER=""
if [ -d "$HUGO_DIR" ]; then
EXTRA_DOCKER_PARAMETER="-v $HUGO_DIR:/mnt/hugo"
fi
doas docker run -d --name "$UUID" \
--network 4server_4projects \
--restart=always \
$EXTRA_DOCKER_PARAMETER \
-v "$DATA_DIR/odoo-web-data:/var/lib/odoo" \
-v "$CUSTOM_DIR:/mnt/addons/custom" \
-v "$ENTERPRISE_DIR:/mnt/addons/enterprise" \
-v "$LOGS_DIR:/mnt/logs" \
-v "$CC_DIR:/mnt/cc" \
-v "$BACKUP_DIR:/mnt/backup" \
-v "$CONFIG_DIR:/etc/odoo" \
-v "$GIT_DIR:/git-server" \
-v "$ETC_DIR:/mnt/etc" \
-v "$INSTALL_DIR:/mnt/install" \
-v "$SSH_DIR:/etc/sshkey" \
-p "$PORT:22" \
-e HOST="$POSTGRES_HOST" \
-e USER="$ODOO_DB_USER" \
-e PASSWORD="$ODOO_DB_PASSWORD" \
-e STAGING="$STAGING" \
--label "$DOMAIN_LABEL" \
--label "traefik.http.services.$UUID.loadbalancer.server.port=8069" \
--label "traefic.http.routers.$UUID.entrypoints=web, websecure" \
--label "traefik.http.routers.$UUID.tls.certresolver=production" \
--label "traefik.http.routers.$UUID.tls=true" \
--label "traefik.http.routers.$UUID.service=$UUID" \
docker.odoo4projects.com/4projects/odoo_19:$BRANCH
check_and_create_db

49
app/sbin/sbin/start/n8n Executable file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env bash
echo "Start N8N container ${UUID}"
# Get the hostname of the machine
HOSTNAME=$(hostname)
mkdir -p /4server/data/${UUID}/n8n
mkdir -p /4server/data/${UUID}/data
chmod 777 /4server/data/${UUID}/n8n
chmod 777 /4server/data/${UUID}/data
# Stop the container if it exists
if docker ps -a --format '{{.Names}}' | grep -q "^${UUID}$"; then
echo "$(date '+%Y-%m-%d %H:%M') - stopping existing container $UUID"
docker stop "$UUID"
docker rm "$UUID"
fi
docker run -d \
--name "$UUID" \
-p 5678 \
--cap-add=SYS_ADMIN \
--security-opt seccomp=unconfined \
--restart=always \
-e N8N_HOST="${UUID}.odoo4projects.com" \
-e N8N_PORT=5678 \
-e N8N_PROTOCOL=https \
-e NODE_ENV=production \
-e WEBHOOK_URL="https://${UUID}.odoo4projects.com/" \
-e GENERIC_TIMEZONE="UTC-3" \
-v "/4server/data/${UUID}/n8n:/home/node/.n8n" \
-v "/4server/data/${UUID}/data:/data" \
--label "traefik.enable=true" \
--label "traefik.http.routers.${UUID}.rule=Host(\`${UUID}.odoo4projects.com\`)" \
--label "traefik.http.routers.${UUID}.entrypoints=web,websecure" \
--label "traefik.http.routers.${UUID}.tls=true" \
--label "traefik.http.routers.${UUID}.tls.certresolver=production" \
--label "traefik.http.services.${UUID}.loadbalancer.server.port=5678" \
--network 4server_4projects \
n8nio/n8n:latest
echo "Started $1"

36
app/sbin/sbin/startContainer Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Usage: ./start_by_uuid.sh <uuid>
# Example: ./start_by_uuid.sh abc-001-xxxx-xxxx-xxxx
exec > /4server/data/log/startContainer.log 2>&1
echo "$(date '+%Y-%m-%d %H:%M') Start container $1"
source /4server/sbin/helpers
BIN_PATH="/4server/sbin"
UUID="$1"
if [[ -z "$UUID" ]]; then
echo "Usage: $0 <uuid>"
exit 1
fi
get_contract_info
# 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/ODOO_19"
;;
*)
echo "Unknown UUID type: $SECOND_PART"
exit 2
;;
esac

80
app/sbin/start/ODOO_19 Executable file
View File

@@ -0,0 +1,80 @@
#!/bin/bash
# Load functions
source /4server/sbin/ODOO_19/ODOO_19.lib
# Config variables
UUID="${UUID:-default}"
BRANCH="${BRANCH:-main}"
STAGING="${STAGING:-false}"
POSTGRES_HOST="${POSTGRES_HOST:-beedb}"
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
ODOO_DB_USER="${UUID}"
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
BASEURL="${BASEURL:-/4server/data/$UUID}"
DATA_DIR="$BASEURL/odoo/"
CUSTOM_DIR="$BASEURL/git/$UUID/custom/"
ENTERPRISE_DIR="$BASEURL/git/$UUID/enterprise/"
LOGS_DIR="$BASEURL/logs/"
CONFIG_DIR="$BASEURL/config/"
CC_DIR="$BASEURL/cc/"
BACKUP_DIR="/BACKUP/$UUID"
GIT_DIR="$BASEURL/git-server/"
ETC_DIR="$BASEURL/etc/"
INSTALL_DIR="$BASEURL/install/"
SSH_DIR="$BASEURL/.ssh/"
HUGO_DIR="$BASEURL/git-server/local/hugo"
SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1)
dump_config
DOMAIN_LABEL=$(check_domains "$UUID.odoo4projects.com" "$SERVER_IP")
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=Host(\`$UUID.odoo4projects.com\`)"
PORT=$((RANDOM%1000+2200))
doas docker stop "$UUID" 2>/dev/null
doas docker rm "$UUID" 2>/dev/null
EXTRA_DOCKER_PARAMETER=""
if [ -d "$HUGO_DIR" ]; then
EXTRA_DOCKER_PARAMETER="-v $HUGO_DIR:/mnt/hugo"
fi
doas docker run -d --name "$UUID" \
--network 4server_4projects \
--restart=always \
$EXTRA_DOCKER_PARAMETER \
-v "$DATA_DIR/odoo-web-data:/var/lib/odoo" \
-v "$CUSTOM_DIR:/mnt/addons/custom" \
-v "$ENTERPRISE_DIR:/mnt/addons/enterprise" \
-v "$LOGS_DIR:/mnt/logs" \
-v "$CC_DIR:/mnt/cc" \
-v "$BACKUP_DIR:/mnt/backup" \
-v "$CONFIG_DIR:/etc/odoo" \
-v "$GIT_DIR:/git-server" \
-v "$ETC_DIR:/mnt/etc" \
-v "$INSTALL_DIR:/mnt/install" \
-v "$SSH_DIR:/etc/sshkey" \
-p "$PORT:22" \
-e HOST="$POSTGRES_HOST" \
-e USER="$ODOO_DB_USER" \
-e PASSWORD="$ODOO_DB_PASSWORD" \
-e STAGING="$STAGING" \
--label "$DOMAIN_LABEL" \
--label "traefik.http.services.$UUID.loadbalancer.server.port=8069" \
--label "traefic.http.routers.$UUID.entrypoints=web, websecure" \
--label "traefik.http.routers.$UUID.tls.certresolver=production" \
--label "traefik.http.routers.$UUID.tls=true" \
--label "traefik.http.routers.$UUID.service=$UUID" \
docker.odoo4projects.com/4projects/odoo_19:$BRANCH
check_and_create_db

View File

@@ -26,9 +26,6 @@ case "$SECOND_PART" in
"$BIN_PATH/start/n8n"
;;
002)
"$BIN_PATH/start/ODOO_18"
;;
003)
"$BIN_PATH/start/ODOO_19"
;;
*)