diff --git a/app/sbin/ODOO_19/ODOO_19.lib b/app/sbin/ODOO_19/ODOO_19.lib deleted file mode 100644 index 7a63bba..0000000 --- a/app/sbin/ODOO_19/ODOO_19.lib +++ /dev/null @@ -1,92 +0,0 @@ -#!/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 "check and create" -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" -} diff --git a/app/sbin/ODOO_19/audit b/app/sbin/ODOO_19/audit deleted file mode 100755 index 1c5b49f..0000000 --- a/app/sbin/ODOO_19/audit +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -/4server/sbin/ODOO_19/listModules $1 diff --git a/app/sbin/ODOO_19/dbVersion b/app/sbin/ODOO_19/dbVersion deleted file mode 100755 index ca12723..0000000 --- a/app/sbin/ODOO_19/dbVersion +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python3 -import csv -import sys -import zipfile - -if len(sys.argv) < 2: - print("Usage: python3 check_odoo_version.py ") - sys.exit(1) - -zip_path = sys.argv[1] -base_version = None - -with zipfile.ZipFile(zip_path, 'r') as z: - # Assume there is only one .sql file in the zip - sql_files = [f for f in z.namelist() if f.endswith('.sql')] - if not sql_files: - print("No .sql file found in the zip.") - sys.exit(1) - - sql_file_name = sql_files[0] - - with z.open(sql_file_name, 'r') as f: - # Decode bytes to string - lines = (line.decode('utf-8') for line in f) - - # Skip lines until COPY command - for line in lines: - if line.startswith("COPY public.ir_module_module"): - break - - # Read the COPY data until the terminator '\.' - reader = csv.reader(lines, delimiter='\t', quotechar='"', escapechar='\\') - for row in reader: - if row == ['\\.']: # End of COPY - break - if len(row) < 12: - continue - module_name = row[7].strip() # 8th column = name - if module_name == "base": - base_version = row[11].strip() # 12th column = latest_version - break - -if base_version: - print(base_version.split(".")[0]) -else: - print("Base module not found in dump.") - diff --git a/app/sbin/ODOO_19/import b/app/sbin/ODOO_19/import deleted file mode 100755 index 8b5d6af..0000000 --- a/app/sbin/ODOO_19/import +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# Create the tmp directory if it doesn't exist -mkdir -p /4server/tmp/ - -# Save original stdout -exec 3>&1 - -# Redirect all other output to log -exec > /4server/data/log/importDb.log 2>&1 -echo "$(date '+%Y-%m-%d %H:%M') Import file $1" - -# Generate random 8-digit filename -RANDOM_FILE="/4server/tmp/import_$(date '+%Y%m%d_%H%M').zip" - -# Download file from Google Drive using gdown -gdown "$1" -O "$RANDOM_FILE" - -# Execute dbVersion on the downloaded file and capture output -VERSION=$(/4server/sbin/ODOO_19/dbVersion "$RANDOM_FILE") - -# Output JSON to original stdout -cat <&3 -{ - "version":"$VERSION", - "file":"$RANDOM_FILE" -} -EOF - -# Close saved stdout -exec 3>&- - diff --git a/app/sbin/ODOO_19/listModules b/app/sbin/ODOO_19/listModules deleted file mode 100755 index 07a4c09..0000000 --- a/app/sbin/ODOO_19/listModules +++ /dev/null @@ -1,124 +0,0 @@ -#!/bin/bash -export PATH=/4PROJECTS/bin:$PATH -if [ ! -n "$1" ]; then - echo "Missing Parameters " - exit 0 -fi - -UUID=$1 - -source /4server/sbin/helpers - -get_contract_info - -# Query installed modules from database and save CSV in variable -DB_MODULES_CSV=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" PAGER= psql \ - -h "$POSTGRES_HOST" \ - -U "$POSTGRES_ADMIN_USER" \ - -p "$POSTGRES_PORT" \ - -d "$UUID" \ - --csv \ - -c " -SELECT - name, - latest_version, - author, - summary -FROM ir_module_module -WHERE state = 'installed' -ORDER BY name; -") - -# Check if container exists and is running -if ! doas docker ps --format "{{.Names}}" | grep -q "^${UUID}$"; then - echo "Error: Container $UUID is not running" >&2 - exit 1 -fi - -# Get addons_path from odoo.conf in container (try multiple possible locations) -ADDONS_PATH_RAW="" -for ODOO_CONF in "/etc/odoo.conf" "/etc/odoo/odoo.conf" "/opt/odoo/etc/odoo.conf"; do - ADDONS_PATH_RAW=$(doas docker exec "$UUID" grep "^addons_path" "$ODOO_CONF" 2>/dev/null | sed 's/.*=//' | xargs) - [ -n "$ADDONS_PATH_RAW" ] && break -done - -# Also check standard Odoo library locations -ODOO_LIB_PATHS="" -# Check standard docker installation path -if doas docker exec "$UUID" test -d "/lib/python3/dist-packages/odoo/addons" 2>/dev/null; then - ODOO_LIB_PATHS="/lib/python3/dist-packages/odoo/addons" -fi -# Also check other possible locations -for lib_path in $(doas docker exec "$UUID" find /usr/lib /usr/local/lib -maxdepth 3 -type d -path "*/python3*/site-packages/odoo/addons" 2>/dev/null); do - ODOO_LIB_PATHS="$ODOO_LIB_PATHS $lib_path" -done - -# Collect all module directories from filesystem -FILESYSTEM_MODULES=$(mktemp) -trap "rm -f $FILESYSTEM_MODULES" EXIT - -# Collect from addons_path directories -if [ -n "$ADDONS_PATH_RAW" ]; then - OLD_IFS="$IFS" - IFS=',' read -ra ADDPATHS <<< "$ADDONS_PATH_RAW" - IFS="$OLD_IFS" - for addons_dir in "${ADDPATHS[@]}"; do - addons_dir=$(echo "$addons_dir" | xargs) - [ -z "$addons_dir" ] && continue - - # Get all directories in this addons path - doas docker exec "$UUID" find "$addons_dir" -maxdepth 1 -type d 2>/dev/null | \ - sed "s|^$addons_dir/||" | sed "s|^$addons_dir$||" | grep -v "^$" >> "$FILESYSTEM_MODULES" - done -fi - -# Also collect from standard Odoo library locations -if [ -n "$ODOO_LIB_PATHS" ]; then - for lib_path in $ODOO_LIB_PATHS; do - lib_path=$(echo "$lib_path" | xargs) - [ -z "$lib_path" ] && continue - - # Get all directories in this library path - doas docker exec "$UUID" find "$lib_path" -maxdepth 1 -type d 2>/dev/null | \ - sed "s|^$lib_path/||" | sed "s|^$lib_path$||" | grep -v "^$" >> "$FILESYSTEM_MODULES" - done -fi - -# Sort and deduplicate filesystem modules -sort -u "$FILESYSTEM_MODULES" > "${FILESYSTEM_MODULES}.sorted" -mv "${FILESYSTEM_MODULES}.sorted" "$FILESYSTEM_MODULES" - -# Process database modules and find missing ones -MISSING_MODULES=$(mktemp) -trap "rm -f $FILESYSTEM_MODULES $MISSING_MODULES" EXIT - -# Process each line from database CSV (skip header) -echo "$DB_MODULES_CSV" | tail -n +2 | while IFS= read -r line; do - [ -z "$line" ] && continue - - # Extract module name from first CSV field - # Handle quoted CSV: extract first field, remove surrounding quotes - if echo "$line" | grep -q '^"'; then - # First field is quoted - extract between first quote and comma - name=$(echo "$line" | sed 's/^"\([^"]*\)".*/\1/') - else - # First field is not quoted - extract up to first comma - name=$(echo "$line" | cut -d',' -f1) - fi - - name=$(echo "$name" | xargs) - [ -z "$name" ] && continue - - # Check if module exists in filesystem - if ! grep -Fxq "$name" "$FILESYSTEM_MODULES"; then - # Module not found in filesystem - output the full CSV line - echo "$line" >> "$MISSING_MODULES" - fi -done - -# Output missing modules if any were found -if [ -s "$MISSING_MODULES" ]; then - echo "Missing modules:" - echo "$DB_MODULES_CSV" | head -n 1 # Output CSV header - cat "$MISSING_MODULES" -fi diff --git a/app/sbin/ODOO_19/restore b/app/sbin/ODOO_19/restore deleted file mode 100755 index 5e55637..0000000 --- a/app/sbin/ODOO_19/restore +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/bash -export PATH=/4PROJECTS/bin:$PATH -if [ ! -n "$2" ]; then - echo "Missing Parameters " - exit 0 -fi - -UUID=$1 -echo "UUID: $UUID" - -source /4server/sbin/helpers - -get_contract_info - - -#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') - -echo "PASSWORD $ODOO_DB_PASSWORD" - -echo "Restoring $FILENAME to $UUID" -echo "status of container" -doas docker ps -a --filter "id=$UUID" -echo "POSTGRES HOST: $POSTGRES_HOST" -BACKUP="/mnt/backup/$2" -TEMPLATE="/mnt/db_images/$2" -doas docker exec "$UUID" /bin/bash -c "[ -f $TEMPLATE ]" - -if doas docker exec "$UUID" /bin/bash -c "[ -f $BACKUP ]"; then - FILENAME="$BACKUP" -elif doas docker exec "$UUID" /bin/bash -c "[ -f $TEMPLATE ]"; then - FILENAME="$TEMPLATE" -else - echo "File not exists" - exit 0 -fi - - -### DELETE AND CREATE DATABASE -PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -d postgres -c " -SELECT pg_terminate_backend(pid) FROM pg_stat_activity -WHERE datname = '$UUID' AND pid <> pg_backend_pid(); -" -PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -d postgres -c " -DROP DATABASE IF EXISTS \"$UUID\"; -" - -PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \ - -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \ - -c "ALTER ROLE \"$UUID\" CREATEDB;" - - -doas docker exec "$UUID" rm -rf /home/odoo/.local/share/Odoo/filestore -doas docker exec "$UUID" rm -rf /root/.local/share/Odoo/filestore - -doas docker exec "$UUID" odoo db --db_host beedb -w "$ODOO_DB_PASSWORD" -r "$UUID" load "$UUID" $FILENAME -f - -PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \ - -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \ - -c "ALTER ROLE \"$UUID\" NOCREATEDB;" - - -doas docker exec "$UUID" cp -r /root/.local/share/Odoo/filestore /home/odoo/.local/share/Odoo/filestore -doas docker exec "$UUID" chown -R odoo:odoo /home/odoo/.local - -doas docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo/ -doas docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore - -docker restart "$UUID" diff --git a/app/sbin/ODOO_19/shell b/app/sbin/ODOO_19/shell deleted file mode 100755 index 340a83b..0000000 --- a/app/sbin/ODOO_19/shell +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -export PATH=/4PROJECTS/bin:$PATH -if [ ! -n "$1" ]; then - echo "Missing Parameters " - exit 0 -fi - -UUID=$1 -echo "UUID: $UUID" - -source /4server/sbin/helpers - -get_contract_info - - -#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') - -echo "PASSWORD $ODOO_DB_PASSWORD" - -echo "POSTGRES HOST: $POSTGRES_HOST" - -doas docker exec -it "$UUID" odoo shell --db_host beedb --db_password="$ODOO_DB_PASSWORD" -d "$UUID" --db_user="$UUID" diff --git a/app/sbin/ODOO_19/updateModules b/app/sbin/ODOO_19/updateModules deleted file mode 100755 index 7e0b575..0000000 --- a/app/sbin/ODOO_19/updateModules +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash -export PATH=/4PROJECTS/bin:$PATH - -# -------------------------------------------------- -# Validate parameter -# -------------------------------------------------- -if [ -z "$1" ]; then - echo "Missing Parameters " - exit 1 -fi - -UUID=$1 - -# -------------------------------------------------- -# Load environment helpers -# -------------------------------------------------- -source /4server/sbin/helpers - -if ! get_contract_info "$UUID"; then - echo "Error: Failed to load contract info for $UUID" >&2 - exit 1 -fi - -# -------------------------------------------------- -# Verify container exists -# -------------------------------------------------- -if ! doas docker ps -a --format "{{.Names}}" | grep -q "^${UUID}$"; then - echo "Error: Container $UUID does not exist" >&2 - exit 1 -fi - -echo "Updating all Odoo modules for database: $UUID" -echo "Using DB host: $POSTGRES_HOST" - -# -------------------------------------------------- -# Run Odoo update inside container -# -------------------------------------------------- -if ! doas docker exec -u odoo "$UUID" odoo \ - -u all \ - -d "$UUID" \ - --stop-after-init \ - --db_host="$POSTGRES_HOST" \ - --db_port="$POSTGRES_PORT" \ - --db_user="$POSTGRES_ADMIN_USER" \ - --db_password="$POSTGRES_ADMIN_PASSWORD" -then - echo "Error: Odoo module update failed for $UUID" >&2 - exit 1 -fi - -echo "Module update completed successfully for $UUID" - -# -------------------------------------------------- -# Restart container -# -------------------------------------------------- -echo "Starting container $UUID" -/4server/sbin/startContainer "$UUID" - -echo "Done." -exit 0 diff --git a/app/sbin/api b/app/sbin/api index fc2c015..cc6d8b7 100755 --- a/app/sbin/api +++ b/app/sbin/api @@ -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)]) diff --git a/app/sbin/api.orig b/app/sbin/api.orig deleted file mode 100755 index 43f5506..0000000 --- a/app/sbin/api.orig +++ /dev/null @@ -1,591 +0,0 @@ -#!/usr/bin/env python3 -import json -import os -import re -import sqlite3 -import subprocess -import time -from collections import deque -from datetime import datetime -from pathlib import Path -from typing import Any, Dict, Optional - -import psutil -import uvicorn -from fastapi import Depends, FastAPI, HTTPException, Response -from fastapi.responses import PlainTextResponse, RedirectResponse -from fastapi.security.api_key import APIKeyHeader -from pydantic import BaseModel - -# ---------------------- 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.9" - -# ---------------------- 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: - error_msg = ( - f"Command failed\n" - f"Command: {' '.join(cmd)}\n" - f"Return code: {result.returncode}\n" - f"Stdout: {result.stdout.strip()}\n" - f"Stderr: {result.stderr.strip() or 'None'}" - ) - raise HTTPException(status_code=500, detail=error_msg) - 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, - contract TEXT, - git integer, - backup_slots integer, - hdd integer, - workers integer, - utm_source text, - utm_medium text, - utm_campaign text, - domains_slots integer, - secret_list TEXT, - env_list 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 ContractItem(BaseModel): - quantity: int - name: str - product_id: int - features: Dict[str, Any] - - -class ContainerModel(BaseModel): - UUID: str - email: Optional[str] = None - expires: Optional[str] = None - tags: 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 - git: Optional[int] = None - backup_slots: Optional[int] = None - hdd: Optional[int] = None - workers: Optional[int] = None - utm_source: Optional[str] = None - utm_medium: Optional[str] = None - utm_campaign: Optional[str] = None - domains_slots: Optional[int] = None -<<<<<<< HEAD - secret_list: Optional[str] = None -======= ->>>>>>> c583649 (hmm) - env_list: Optional[str] = None - - -class UUIDRequest(BaseModel): - UUID: str - - -class CommandRequest(BaseModel): - uuid: str - method: int - - -class ImportRequest(BaseModel): - filename: str - - -class MoveRequest(BaseModel): - source: str - destination: str - - -class AddKeyRequest(BaseModel): - key: str - uuid: str - - -class ImageRequest(BaseModel): - image: str - - -# ---------------------- Routes ---------------------- -@app.get("/", include_in_schema=False) -def redirect_to_odoo(): - return RedirectResponse(url="https://ODOO4PROJECTS.com") - - -import json -from typing import Any, Dict, Optional - -from fastapi import Depends, FastAPI -from fastapi.responses import RedirectResponse -from pydantic import BaseModel - -app = FastAPI() - - -# ---------------------- 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): - # Fetch existing record - existing = execute_db( - "SELECT * FROM containers WHERE UUID = ?", (request.UUID,), 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, - comment, domains, status, created, bump, git, backup_slots, hdd, workers, -<<<<<<< HEAD - utm_source, utm_medium, utm_campaign, domains_slots, secret_list, env_list) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) -======= - utm_source, utm_medium, utm_campaign, domains_slots, env_list) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ->>>>>>> c583649 (hmm) - """, - ( - request.UUID, - request.email, - request.expires, - request.tags, - request.image, - request.history, - request.comment, - request.domains, - request.status, - request.created, - request.bump, - request.git, - request.backup_slots, - request.hdd, - request.workers, - request.utm_source, - request.utm_medium, - request.utm_campaign, - request.domains_slots, -<<<<<<< HEAD - request.secret_list, -======= ->>>>>>> c583649 (hmm) - request.env_list, - ), - ) - return {"UUID": request.UUID, "status": "created"} - - # Existing record found, do partial update - existing = existing[0] # Assuming fetch returns list of dicts - updates = {} - params = [] - - # Only add fields that are not None in the request - for field in ContainerModel.__fields__: - if field == "UUID": - continue - value = getattr(request, field) - if value is not None: - updates[field] = value - params.append(value) - - if updates: - # Build SQL dynamically - set_clause = ", ".join(f"{k}=?" for k in updates.keys()) - params.append(request.UUID) # UUID for WHERE clause - execute_db(f"UPDATE containers SET {set_clause} WHERE UUID=?", tuple(params)) - return { - "UUID": request.UUID, - "status": "updated", - "fields_updated": list(updates.keys()), - } - - return {"UUID": request.UUID, "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])} - - -@app.post("/container/restartAllContainers", dependencies=[Depends(verify_api_key)]) -def start_container(request: ImageRequest): - return {"message": run_command([f"{BIN_PATH}/restartContainers", request.image])} - - -@app.post("/container/stop", dependencies=[Depends(verify_api_key)]) -def stop_container(request: UUIDRequest): - try: - return {"message": run_command([f"{BIN_PATH}/stopContainer", request.UUID])} - 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): - 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 to select - fields = [ - "ID", - "UUID", - "email", - "expires", - "tags", - "image", - "history", - "comment", - "domains", - "status", - "created", - "git", - "backup_slots", - "hdd", - "workers", - "utm_source", - "utm_medium", - "utm_campaign", - "domains_slots", - "env_list", - ] - field_str = ", ".join(fields) - - # Execute query - if request: - rows = execute_db( - f"SELECT {field_str} FROM containers WHERE UUID=?", - (str(request.UUID),), - fetch=True, - ) - else: - rows = execute_db(f"SELECT {field_str} FROM containers", fetch=True) - - # Map rows to dicts safely - containers = [] - for row in rows: - if isinstance(row, dict): - # Already a dict (e.g., some DB wrappers) - containers.append(row) - else: - # Tuple/list -> map with fields - containers.append(dict(zip(fields, row))) - - # 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/cpu", dependencies=[Depends(verify_api_key)]) -def get_cpu_log(): - CPU_LOG_PATH = Path("/4server/data/log/cpu.log") - if not CPU_LOG_PATH.exists(): - raise HTTPException(status_code=404, detail="CPU log file not found") - - try: - with CPU_LOG_PATH.open("r") as f: - content = f.read() - return PlainTextResponse(content) - except Exception as e: - raise HTTPException(status_code=500, detail=f"Error reading CPU log: {e}") - - -@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"])} - - -@app.post("/system/restartAllContainers", dependencies=[Depends(verify_api_key)]) -def restart_all_containers(request: ImageRequest): - if not request.image: - raise HTTPException(status_code=400, detail="Image name is required") - return {"message": run_command([f"{BIN_PATH}/restartContainers", request.image])} - - -@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] - elif request.method == 2: - command = [f"{BIN_PATH}/gitRevert", request.uuid] - else: - raise HTTPException(status_code=400, detail="Invalid method") - - output = run_command(command) - return {"message": output} - - -@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") - - file_path = f"/4server/data/{request.uuid}/git-server/keys/id_rsa.pub" - - try: - # Ensure the directory exists - os.makedirs(os.path.dirname(file_path), exist_ok=True) - # Write the key to the file - with open(file_path, "w", encoding="utf-8") as f: - f.write(request.key) - 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} - - -@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/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): - raise HTTPException( - status_code=400, - detail="Invalid UUID format. Only numbers, letters a-f, and '-' are allowed.", - ) - - BASE_LOG_DIR = "/4server/data" - - # Build file paths as strings - project_dir = os.path.join(BASE_LOG_DIR, uuid) - odoo_log_file = os.path.join(project_dir, "logs", "odoo.log") - git_log_file = os.path.join(project_dir, "logs", "git.log") - - if not os.path.isfile(odoo_log_file): - raise HTTPException(status_code=404, detail="Odoo log file not found") - - # --- Helper variables and functions --- - IMPORTANT_PATTERNS = [ - re.compile(r"odoo\.addons\."), # Any Odoo addon log - re.compile(r"Job '.*' starting"), # Cron job start - re.compile(r"Job '.*' fully done"), # Cron job end - re.compile(r"ERROR"), # Errors - re.compile(r"WARNING"), # Warnings - re.compile(r"Traceback"), # Tracebacks - re.compile(r"Error"), - ] - - def is_important_line(line: str) -> bool: - return any(p.search(line) for p in IMPORTANT_PATTERNS) - - def read_last_lines(file_path: str, max_lines: int) -> list[str]: - """ - Read the last `max_lines` from the file efficiently. - """ - if not os.path.isfile(file_path): - return [] - last_lines = deque(maxlen=max_lines) - with open(file_path, "r", encoding="utf-8", errors="ignore") as f: - for line in f: - last_lines.append(line.strip()) - return list(last_lines) - - # --- Main logic --- - try: - # Last 500 lines from Odoo log - last_500_lines = read_last_lines(odoo_log_file, 500) - important_odoo_lines = [ - line for line in last_500_lines if is_important_line(line) - ] - - # Last 50 lines from git.log - last_50_git_lines = read_last_lines(git_log_file, 50) - - return { - "uuid": str(uuid), - "important_odoo_log_lines": important_odoo_lines, - "last_git_log_lines": last_50_git_lines, - } - except Exception as e: - raise HTTPException(status_code=500, detail=f"Error reading log files: {e}") - - -# ------------------------ BACKUP HANDLING ------------------------------------- -@app.post("/backup/import", dependencies=[Depends(verify_api_key)]) -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] - output = run_command(command) - return {"message": output} - - -@app.post("/backup/move", dependencies=[Depends(verify_api_key)]) -def backup_move(request: MoveRequest): - if not request.source or not request.destination: - raise HTTPException( - status_code=400, detail="Source and destination are required" - ) - - if not os.path.exists(request.source): - raise HTTPException(status_code=404, detail="Source file does not exist") - - # Use shell command to move the file - command = ["mv", request.source, request.destination] # Linux/macOS - # For Windows, use: command = ["move", request.source, request.destination] - - output = run_command(command) - return { - "message": f"Moved {request.source} to {request.destination}", - "output": output, - } - - -@app.get("/backup/borgpush", dependencies=[Depends(verify_api_key)]) -def backup_borgpush(): - return {"message": run_command(["doas", "-u", "4server", f"{BIN_PATH}/borgpush"])} - - -# ---------------------- Entry Point ---------------------- -if __name__ == "__main__": - print(VERSION) - init_db() - time.sleep(25) - uvicorn.run(app, host="10.5.0.1", port=8888) diff --git a/app/sbin/backup/N8N b/app/sbin/backup/N8N index ce203b4..642d2cb 100755 --- a/app/sbin/backup/N8N +++ b/app/sbin/backup/N8N @@ -4,10 +4,10 @@ set -euo pipefail # Fail on error, undefined variables, and pipe errors # Create backup filename FILENAME="$(date +"%Y%m%d_%H%M").zip" -BACKUP_DIR="/BACKUP/$UUID" +BACKUP_DIR="/BACKUP/$CONTAINER_ID" mkdir -p "$BACKUP_DIR" -tar -czvf "$BACKUP_DIR/$FILENAME" -C "/4server/data" "$UUID" +tar -czvf "$BACKUP_DIR/$FILENAME" -C "/4server/data" "$CONTAINER_ID" # Remove old backups beyond the configured slots ls -t "$BACKUP_DIR"/[0-9]*.zip 2>/dev/null | tail -n +$((BACKUP_SLOTS + 1)) | while read -r file; do diff --git a/app/sbin/backup/ODOO b/app/sbin/backup/ODOO index 16c1742..a3d5e80 100755 --- a/app/sbin/backup/ODOO +++ b/app/sbin/backup/ODOO @@ -4,21 +4,27 @@ set -euo pipefail # Fail on error, undefined variables, and pipe errors -log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } +# Load helper functions (provides log/warn/die) +_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of backup/ +source "$SBIN_ROOT/helpers" -# Load helper functions -source /4server/sbin/helpers +# Load SOFTWARE and DB_NAME from container.env +load_container_config "$CONTAINER_ID" # Get contract info (also exports ODOO_DB_PASSWORD via helpers) get_contract_info -log "UUID: $UUID" +# DB_NAME is the postgres database name (may differ from CONTAINER_ID for migrated containers) +DB_NAME="${DB_NAME:-$CONTAINER_ID}" + +log "CONTAINER_ID: $CONTAINER_ID" +log "DB_NAME: $DB_NAME" log "Retention: daily 14d, weekly 12w" log "DB host: $POSTGRES_HOST" # Build paths FILENAME="$(date +"%Y%m%d_%H%M").zip" -BACKUP_DIR="/BACKUP/$UUID" +BACKUP_DIR="/BACKUP/$CONTAINER_ID" BB_DIR="$BACKUP_DIR/bb" # Ensure backup directories exist (requires elevated permissions like other ops) @@ -27,12 +33,12 @@ doas mkdir -p "$BB_DIR" # Perform database dump inside the container log "Starting dump -> $BACKUP_DIR/$FILENAME" -doas docker exec "$UUID" odoo db \ +doas docker exec "$CONTAINER_ID" odoo db \ --db_host "$POSTGRES_HOST" \ - -r "$UUID" \ + -r "$DB_NAME" \ -w "$ODOO_DB_PASSWORD" \ --data-dir /home/odoo/.local/share/Odoo/ \ - dump "$UUID" "/mnt/backup/$FILENAME" + dump "$DB_NAME" "/mnt/backup/$FILENAME" log "Dump complete: $(doas du -sh "$BACKUP_DIR/$FILENAME" | cut -f1)" @@ -40,7 +46,7 @@ log "Dump complete: $(doas du -sh "$BACKUP_DIR/$FILENAME" | cut -f1)" doas chmod 666 "$BACKUP_DIR"/[0-9]*.zip # Hand ownership back to odoo inside the container -doas docker exec "$UUID" chown odoo:odoo -R /mnt/backup +doas docker exec "$CONTAINER_ID" chown odoo:odoo -R /mnt/backup # Update the current.zip symlink inside bb/ pointing at the backup just created. # Target is relative (../$FILENAME) because the symlink lives one level deeper @@ -97,10 +103,10 @@ done log "Backup finished successfully ." # --- Custom modules snapshot --- -# Archive /4server/data/$UUID/git-server/repos/local/custom/ into .current.tar.gz. +# Archive /4server/data/$CONTAINER_ID/git-server/repos/local/custom/ into .current.tar.gz. # Rebuilt only when at least one file inside is newer than the existing archive, # so borg backup dedup sees an identical blob on unchanged runs. -CUSTOM_SRC="/4server/data/$UUID/git-server/repos/local/custom" +CUSTOM_SRC="/4server/data/$CONTAINER_ID/git-server/repos/local/custom" CUSTOM_ARCHIVE="$BB_DIR/current_modules.tar.gz" if [ ! -d "$CUSTOM_SRC" ]; then @@ -110,7 +116,7 @@ elif [ ! -f "$CUSTOM_ARCHIVE" ] || \ log "Changes detected in custom modules — rebuilding current.tar.gz" # Write to a temp file first; mv is atomic, so borg never sees a partial archive doas tar -czf "${CUSTOM_ARCHIVE}.tmp" \ - -C "/4server/data/$UUID/git-server/repos/local" custom + -C "/4server/data/$CONTAINER_ID/git-server/repos/local" custom doas mv "${CUSTOM_ARCHIVE}.tmp" "$CUSTOM_ARCHIVE" log ".current.tar.gz updated: $(doas du -sh "$CUSTOM_ARCHIVE" | cut -f1)" else diff --git a/app/sbin/backupAll b/app/sbin/backupAll index 485a43c..456b888 100755 --- a/app/sbin/backupAll +++ b/app/sbin/backupAll @@ -1,18 +1,20 @@ #!/bin/bash exec >> /4server/data/log/backupAll.log 2>&1 -echo "$(date '+%Y-%m-%d %H:%M') Starting backupAll" +_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")" +source "$SBIN/helpers" +log "Starting backupAll" -BIN_PATH="/4server/sbin" +BIN_PATH="$SBIN" -containers=$(docker ps --format '{{.Names}}' | grep -E '^[0-9]{3}-[0-9]{3}') +containers=$(docker ps --format '{{.Names}}' | grep -v '^traefik$' | grep -v '^beedb$') -if [[ -z "$containers" ]]; then - echo "No containers matching pattern NNN-NNN found." +if [ -z "$containers" ]; then + log "No managed container_ids found." exit 0 fi -echo "Found containers:" +log "Triggering backup for:" while IFS= read -r container; do - echo " $container" + log " $container" touch "/4server/data/$container/cc/backup" done <<< "$containers" diff --git a/app/sbin/backupContainer b/app/sbin/backupContainer index dbcb578..1cb69b8 100755 --- a/app/sbin/backupContainer +++ b/app/sbin/backupContainer @@ -1,42 +1,29 @@ #!/bin/bash exec >> /4server/data/log/backupContainer.log 2>&1 -echo "$(date '+%Y-%m-%d %H:%M') Backup container $1" -source /4server/sbin/helpers +_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")" +source "$SBIN/helpers" -BIN_PATH="/4server/sbin" +BIN_PATH="$SBIN" -UUID="$1" +CONTAINER_ID="$1" -if [[ -z "$UUID" ]]; then - echo "Usage: $0 " - exit 1 -fi +[ -z "$CONTAINER_ID" ] && die "Usage: $0 " +load_container_config "$CONTAINER_ID" get_contract_info -# Extract the second part of UUID (split by "-") -SECOND_PART=$(echo "$UUID" | cut -d'-' -f2) +log "Backup container $CONTAINER_ID (SOFTWARE=$SOFTWARE)" -echo "Conatiner Type $UUID" - -# Decide which script to run -case "$SECOND_PART" in - 001) +# Decide which script to run based on SOFTWARE from container.env +case "$SOFTWARE" in + n8n) "$BIN_PATH/backup/N8N" ;; - 002) + ODOO_17|ODOO_18|ODOO_19) "$BIN_PATH/backup/ODOO" ;; - 003) - "$BIN_PATH/backup/ODOO" - ;; - 004) - "$BIN_PATH/backup/ODOO" - ;; - - *) - echo "Unknown UUID type: $SECOND_PART" - exit 2 + *) + die "Unknown SOFTWARE type '${SOFTWARE}' for container_id $CONTAINER_ID — run: initContainer $CONTAINER_ID " ;; esac diff --git a/app/sbin/checkBackup b/app/sbin/checkBackup index 7ed8571..dc5f369 100755 --- a/app/sbin/checkBackup +++ b/app/sbin/checkBackup @@ -6,14 +6,14 @@ from pathlib import Path BACKUP_ROOT = Path("/BACKUP") if len(sys.argv) < 2: - print("Usage: checkBackup ") + print("Usage: checkBackup ") sys.exit(1) -uuid = sys.argv[1] -directory = BACKUP_ROOT / uuid +container_id = sys.argv[1] +directory = BACKUP_ROOT / container_id if not directory.is_dir(): - print(f"ERROR: directory not found for UUID {uuid}") + print(f"ERROR: directory not found for container_id {container_id}") sys.exit(1) latest_date = None diff --git a/app/sbin/checkCalls b/app/sbin/checkCalls index 89adfca..9a68444 100755 --- a/app/sbin/checkCalls +++ b/app/sbin/checkCalls @@ -1,36 +1,43 @@ #!/bin/bash +_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")" +source "$SBIN/helpers" + cd /4server/data/ while : do -for dir in ???-???-*; do +# nullglob: if no subdirectories exist the loop body is skipped entirely +shopt -s nullglob +for dir in */; do + dir="${dir%/}" + [ -z "$dir" ] && continue if [ -d "${dir}/cc" ]; then if [ -f "${dir}/cc/backup" ]; then - echo "$(date '+%Y-%m-%d %H:%M:%S') BACKUP for: ${dir%/}" - /4server/sbin/backupContainer ${dir%/} 2 + log "BACKUP for: $dir" + "$SBIN/backupContainer" "$dir" rm "${dir}/cc/backup" fi if [ -f "${dir}/cc/restart" ]; then - echo "$(date '+%Y-%m-%d %H:%M:%S') Restart for: ${dir%/}" - /4server/sbin/startContainer ${dir%/} + log "Restart for: $dir" + "$SBIN/startContainer" "$dir" rm "${dir}/cc/restart" fi if [ -f "${dir}/cc/restore" ]; then FILENAME=$(head -n 1 "${dir}/cc/restore") - echo "$(date '+%Y-%m-%d %H:%M:%S') Restore for: ${dir%/} - $FILENAME" - /4server/sbin/ODOO_19/restore ${dir%/} $FILENAME + log "Restore for: $dir - $FILENAME" + "$SBIN/odoo/restore" "$dir" "$FILENAME" rm "${dir}/cc/restore" fi fi if [ -d "${dir}/n8n" ]; then if [ -f "${dir}/n8n/database.sqlite-journal" ]; then - echo "$(date '+%Y-%m-%d %H:%M:%S') Restart N8N (journal) for: ${dir%/}" - /4server/sbin/startContainer ${dir%/} + log "Restart N8N (journal) for: $dir" + "$SBIN/startContainer" "$dir" fi fi diff --git a/app/sbin/checkFilestore b/app/sbin/checkFilestore index 8f30883..8fdea7e 100755 --- a/app/sbin/checkFilestore +++ b/app/sbin/checkFilestore @@ -1,23 +1,23 @@ #!/bin/bash -# checkFilestore — compare container filestore vs host disk via md5 +# checkFilestore — compare container filestore vs host disk via md5 check_one() { -local UUID="$1" +local CONTAINER_ID="$1" -FILESTORE_HOST="/4server/data/$UUID/odoo/odoo-web-data/filestore/$UUID" +FILESTORE_HOST="/4server/data/$CONTAINER_ID/odoo/odoo-web-data/filestore/$CONTAINER_ID" [[ -d "$FILESTORE_HOST" ]] || { echo "ERROR: host filestore not found: $FILESTORE_HOST"; exit 2; } -doas docker ps --format "{{.Names}}" | grep -q "^${UUID}$" \ - || { echo "ERROR: container $UUID is not running"; exit 2; } +doas docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_ID}$" \ + || { echo "ERROR: container $CONTAINER_ID is not running"; exit 2; } # Read data_dir from odoo.conf inside the container, fall back to default -DATA_DIR=$(doas docker exec "$UUID" sh -c \ +DATA_DIR=$(doas docker exec "$CONTAINER_ID" sh -c \ 'for f in /etc/odoo.conf /etc/odoo/odoo.conf /opt/odoo/etc/odoo.conf; do grep -E "^data_dir" "$f" 2>/dev/null | cut -d= -f2- | xargs && break done') DATA_DIR="${DATA_DIR:-/home/odoo/.local/share/Odoo}" -FILESTORE_CONTAINER="$DATA_DIR/filestore/$UUID" -MOUNT_HOST=$(doas docker inspect "$UUID" --format '{{json .Mounts}}' \ +FILESTORE_CONTAINER="$DATA_DIR/filestore/$CONTAINER_ID" +MOUNT_HOST=$(doas docker inspect "$CONTAINER_ID" --format '{{json .Mounts}}' \ | jq -r --arg dest "$DATA_DIR" '.[] | select(.Type=="bind") | select(.Destination==$dest) | .Source') echo "ODOO USES : $FILESTORE_CONTAINER" echo "Docker maps to : $DATA_DIR" @@ -25,7 +25,7 @@ echo "" echo "Docker mapped to : ${MOUNT_HOST:-}" echo "Hashing container filestore..." -MD5_CONTAINER=$(doas docker exec "$UUID" sh -c \ +MD5_CONTAINER=$(doas docker exec "$CONTAINER_ID" sh -c \ "find $FILESTORE_CONTAINER -type f | sort | xargs md5sum 2>/dev/null | awk '{print \$1}'" | md5sum | cut -d' ' -f1) echo "Hashing host filestore..." @@ -41,18 +41,18 @@ if [[ -n "${1-}" ]]; then check_one "$1" else for dir in /4server/data/00*/; do - UUID=$(basename "$dir") - result=$(check_one "$UUID" 2>&1) + CONTAINER_ID=$(basename "$dir") + result=$(check_one "$CONTAINER_ID" 2>&1) if echo "$result" | grep -q "OK: filestores match"; then - echo "$UUID OK" + echo "$CONTAINER_ID OK" elif echo "$result" | grep -q "not running"; then - echo "$UUID NOT RUNNING" + echo "$CONTAINER_ID NOT RUNNING" elif echo "$result" | grep -q "host filestore not found"; then - echo "$UUID NO FILESTORE" + echo "$CONTAINER_ID NO FILESTORE" elif echo "$result" | grep -q "cannot query DB"; then - echo "$UUID DB ERROR" + echo "$CONTAINER_ID DB ERROR" else - echo "$UUID MISMATCH" + echo "$CONTAINER_ID MISMATCH" fi done fi diff --git a/app/sbin/contractInfo b/app/sbin/contractInfo index 0913191..21edb67 100755 --- a/app/sbin/contractInfo +++ b/app/sbin/contractInfo @@ -1,13 +1,14 @@ export PATH=/4PROJECTS/bin:$PATH if [ ! -n "$1" ]; then - echo "Missing Parameters " + echo "Missing Parameters " exit 0 fi -UUID=$1 -echo "UUID: $UUID" +CONTAINER_ID=$1 +echo "CONTAINER_ID: $CONTAINER_ID" -source /4server/sbin/helpers +_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")" +source "$SBIN/helpers" get_contract_info diff --git a/app/sbin/helpers b/app/sbin/helpers index 3e7b56a..98a9549 100755 --- a/app/sbin/helpers +++ b/app/sbin/helpers @@ -1,5 +1,48 @@ #!/bin/bash +# --------------------------------------------------------------------------- +# Logging — use these everywhere instead of bare echo +# --------------------------------------------------------------------------- +log() { echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] $*"; } +warn() { echo "$(date '+%Y-%m-%d %H:%M:%S') [WARN] $*" >&2; } +die() { echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] $*" >&2; exit 1; } + +# Resolve the absolute directory of a script. +# Always pass ${BASH_SOURCE[0]} — it holds the real path even when the +# script is invoked via PATH (unlike $0 which may be just a bare name). +# Usage in top-level scripts: SBIN="$(sbin_dir "${BASH_SOURCE[0]}")" +# Sub-scripts one level deep: SBIN_ROOT="$(dirname "$(sbin_dir "${BASH_SOURCE[0]}")")" +sbin_dir() { dirname "$(realpath "$1")"; } + +# --------------------------------------------------------------------------- +# load_container_config +# Sources /4server/data//config/container.env and exports: +# SOFTWARE — n8n | ODOO_17 | ODOO_18 | ODOO_19 +# DB_NAME — postgres database/user (may differ from CONTAINER_ID for migrations) +# --------------------------------------------------------------------------- +load_container_config() { + local container_id="${1:-$CONTAINER_ID}" + local cfg="/4server/data/$container_id/config/container.env" + if [ -f "$cfg" ]; then + while IFS= read -r line; do + case "$line" in + ''|'#'*) continue ;; + esac + local _k="${line%%=*}" + local _v="${line#*=}" + case "$_k" in + *[!A-Z_]*) continue ;; + esac + export "$_k=$_v" + done < "$cfg" + else + warn "no container.env found at $cfg" + fi +} + +# --------------------------------------------------------------------------- +# Postgres defaults (overridable via environment) +# --------------------------------------------------------------------------- POSTGRES_HOST="${POSTGRES_HOST:-beedb}" POSTGRES_PORT="${POSTGRES_PORT:-5432}" POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}" @@ -7,19 +50,19 @@ POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}" DB_PATH="/4server/data/contracts.db" - +# --------------------------------------------------------------------------- +# get_contract_info +# Reads the contracts.db row for $CONTAINER_ID and exports all columns as env vars. +# Also parses SECRET_LIST and exports ODOO_DB_PASSWORD from the psql key. +# --------------------------------------------------------------------------- get_contract_info() { + log "get_contract_info $CONTAINER_ID" - echo "get_contract_info $UUID" - - # Single CTE: the table is scanned once and all projections come from it while IFS="=" read -r key value; do - if [ -n "$key" ]; then - export "$key=$value" - fi + [ -n "$key" ] && export "$key=$value" done < <(sqlite3 "$DB_PATH" " - WITH c AS (SELECT * FROM containers WHERE UUID='$UUID' LIMIT 1) - SELECT 'UUID=' || COALESCE(UUID, '') FROM c UNION ALL + WITH c AS (SELECT * FROM containers WHERE UUID='$CONTAINER_ID' LIMIT 1) + SELECT 'CONTAINER_ID=' || COALESCE(UUID, '') FROM c UNION ALL SELECT 'EXPIRES=' || COALESCE(expires, '') FROM c UNION ALL SELECT 'IMAGE=' || COALESCE(image, '') FROM c UNION ALL SELECT 'STATUS=' || COALESCE(status, '') FROM c UNION ALL @@ -33,18 +76,13 @@ get_contract_info() { SELECT 'DOMAINS_SLOTS=' || COALESCE(domains_slots, '') FROM c; ") - # Parse SECRET_LIST: backslash-separated key=value pairs (e.g. worex1=1\pswl=22) - # - printf avoids echo interpreting escape sequences - # - tr -d '\r\n' strips any embedded newlines the DB value may contain - # - tr '\\' '\n' turns each backslash separator into a real newline - # - %%=* / #*= split on the FIRST = only, so base64 values like tok=ab== are safe + # Parse SECRET_LIST: backslash-separated key=value pairs + # %%=* / #*= split on the FIRST = only, so base64 values like tok=ab== are safe if [ -n "$SECRET_LIST" ]; then while IFS= read -r pair || [ -n "$pair" ]; do _key="${pair%%=*}" _value="${pair#*=}" - if [ -n "$_key" ]; then - export "$_key=$_value" - fi + [ -n "$_key" ] && export "$_key=$_value" done < <(printf '%s' "$SECRET_LIST" | tr -d '\r\n' | tr '\\' '\n') fi export ODOO_DB_PASSWORD=$psql diff --git a/app/sbin/helpers.orig b/app/sbin/helpers.orig deleted file mode 100755 index 6f3712d..0000000 --- a/app/sbin/helpers.orig +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash - -POSTGRES_HOST="${POSTGRES_HOST:-beedb}" -POSTGRES_PORT="${POSTGRES_PORT:-5432}" -POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}" -POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}" - -DB_PATH="/4server/data/contracts.db" - - -get_contract_info() { - - echo "get_contract_info $UUID" - - # Single CTE: the table is scanned once and all projections come from it - while IFS="=" read -r key value; do - if [ -n "$key" ]; then - export "$key=$value" - fi - done < <(sqlite3 "$DB_PATH" " - WITH c AS (SELECT * FROM containers WHERE UUID='$UUID' LIMIT 1) - SELECT 'UUID=' || COALESCE(UUID, '') FROM c UNION ALL - SELECT 'EXPIRES=' || COALESCE(expires, '') FROM c UNION ALL - SELECT 'IMAGE=' || COALESCE(image, '') FROM c UNION ALL - SELECT 'STATUS=' || COALESCE(status, '') FROM c UNION ALL - SELECT 'CREATED=' || COALESCE(created, '') FROM c UNION ALL - SELECT 'SECRET_LIST=' || COALESCE(secret_list, '') FROM c UNION ALL - SELECT 'CONTAINERDBID=' || COALESCE(id, '') FROM c UNION ALL - SELECT 'GIT=' || COALESCE(git, '') FROM c UNION ALL - SELECT 'BACKUP_SLOTS=' || COALESCE(backup_slots, '') FROM c UNION ALL - SELECT 'HDD=' || COALESCE(hdd, '') FROM c UNION ALL - SELECT 'WORKERS=' || COALESCE(workers, '') FROM c UNION ALL - SELECT 'DOMAINS_SLOTS=' || COALESCE(domains_slots, '') FROM c; - ") - - # Parse SECRET_LIST: backslash-separated key=value pairs (e.g. worex1=1\pswl=22) - # - printf avoids echo interpreting escape sequences - # - tr -d '\r\n' strips any embedded newlines the DB value may contain - # - tr '\\' '\n' turns each backslash separator into a real newline - # - %%=* / #*= split on the FIRST = only, so base64 values like tok=ab== are safe - if [ -n "$SECRET_LIST" ]; then - while IFS= read -r pair || [ -n "$pair" ]; do - _key="${pair%%=*}" - _value="${pair#*=}" - if [ -n "$_key" ]; then - export "$_key=$_value" - fi - done < <(printf '%s' "$SECRET_LIST" | tr -d '\r\n' | tr '\\' '\n') - fi -<<<<<<< HEAD - export ODOO_DB_PASSWORD=$psql -======= -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=' || replace(replace(tags, char(10), ''), char(13), '') FROM containers WHERE UUID='$UUID' - UNION ALL SELECT 'ENV=' || replace(replace(env, char(10), ''), char(13), '') FROM containers WHERE UUID='$UUID' - UNION ALL SELECT 'IMAGE=' || image 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_LIST=' || COALESCE(secret_list, '') FROM containers WHERE UUID='$UUID' - UNION ALL SELECT 'CONTAINERDBID=' || id FROM containers WHERE UUID='$UUID' - UNION ALL SELECT 'BUMP=' || bump FROM containers WHERE UUID='$UUID' - UNION ALL SELECT 'GIT=' || COALESCE(git, '') FROM containers WHERE UUID='$UUID' - UNION ALL SELECT 'BACKUP_SLOTS=' || COALESCE(backup_slots, '') FROM containers WHERE UUID='$UUID' - UNION ALL SELECT 'HDD=' || COALESCE(hdd, '') FROM containers WHERE UUID='$UUID' - UNION ALL SELECT 'WORKERS=' || COALESCE(workers, '') FROM containers WHERE UUID='$UUID' - UNION ALL SELECT 'DOMAINS_SLOTS=' || COALESCE(domains_slots, '') FROM containers WHERE UUID='$UUID'; -") - - -# Parse ENV JSON into individual env vars -eval $(echo "$ENV" | jq -r 'to_entries | .[] | "export \(.key | ascii_upcase)=\(.value)"') - -# Parse SECRET_LIST (backslash-separated key=value pairs, e.g. worex1=1\pswl=22) -if [ -n "$SECRET_LIST" ]; then - while IFS='=' read -r key value; do - if [ -n "$key" ]; then - export "$key=$value" - fi - done < <(echo "$SECRET_LIST" | tr '\\' '\n') -fi - ->>>>>>> c583649 (hmm) -} diff --git a/app/sbin/migrate b/app/sbin/migrate index f75e4cf..74445a0 100755 --- a/app/sbin/migrate +++ b/app/sbin/migrate @@ -1,16 +1,126 @@ #!/bin/bash +# Migration script — run once per server after deploying updated code. +# Safe to re-run: each step checks whether it is needed before acting. -sqlite3 /4server/data/contracts.db </dev/null <<'EOF' || true ALTER TABLE containers ADD COLUMN env_list TEXT; -UPDATE containers -SET env_list = ( - SELECT group_concat(key || '=' || value, ', ') - FROM json_each(containers.env) -); -ALTER TABLE containers ADD COLUMN secret_list TEXT; -UPDATE containers -SET secret_list = ( - SELECT group_concat(key || '=' || value, ', ') - FROM json_each(containers.secret) -); EOF +sqlite3 "$DB" 2>/dev/null <<'EOF' || true +ALTER TABLE containers ADD COLUMN secret_list TEXT; +EOF +sqlite3 "$DB" 2>/dev/null <<'EOF' || true +ALTER TABLE containers ADD COLUMN utm_source text; +EOF +sqlite3 "$DB" 2>/dev/null <<'EOF' || true +ALTER TABLE containers ADD COLUMN utm_medium text; +EOF +sqlite3 "$DB" 2>/dev/null <<'EOF' || true +ALTER TABLE containers ADD COLUMN utm_campaign text; +EOF +sqlite3 "$DB" 2>/dev/null <<'EOF' || true +ALTER TABLE containers ADD COLUMN container_id CHAR(50); +EOF + +# ----------------------------------------------------------------------- +# Step 2 — Backfill container_id from UUID +# ----------------------------------------------------------------------- +echo "Step 2: backfilling container_id from UUID..." +sqlite3 "$DB" <<'EOF' +UPDATE containers +SET container_id = UUID +WHERE container_id IS NULL OR container_id = ''; +EOF + +# ----------------------------------------------------------------------- +# Step 3 — Backfill utm_* from old affiliate JSON (if column still exists) +# ----------------------------------------------------------------------- +if [ "$(has_column affiliate)" = "1" ]; then + echo "Step 3: backfilling utm fields from affiliate JSON..." + sqlite3 "$DB" <<'EOF' +UPDATE containers +SET utm_source = COALESCE(utm_source, json_extract(affiliate, '$.utm_source')), + utm_medium = COALESCE(utm_medium, json_extract(affiliate, '$.utm_medium')), + utm_campaign = COALESCE(utm_campaign, json_extract(affiliate, '$.utm_campaign')) +WHERE affiliate IS NOT NULL AND affiliate != ''; +EOF +else + echo "Step 3: affiliate column not present — skipping utm backfill." +fi + +# ----------------------------------------------------------------------- +# Step 4 — Remove affiliate column via full table rebuild. +# Also adds container_id UNIQUE constraint and locks in the canonical +# column order to match init_db(). +# Only runs if affiliate still exists OR container_id lacks its UNIQUE index. +# ----------------------------------------------------------------------- +NEEDS_REBUILD=0 +[ "$(has_column affiliate)" = "1" ] && NEEDS_REBUILD=1 + +# If affiliate is already gone, the rebuild already ran — no need to repeat +# (container_id UNIQUE is guaranteed by the rebuild itself) + +if [ "$NEEDS_REBUILD" = "1" ]; then + echo "Step 4: rebuilding table (remove affiliate, add container_id UNIQUE)..." + sqlite3 "$DB" <<'EOF' +BEGIN; + +CREATE TABLE containers_new ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + UUID CHAR(50) UNIQUE, + container_id CHAR(50) UNIQUE, + email CHAR(100), + expires DATE, + tags TEXT, + env TEXT, + image CHAR(50), + history TEXT, + comment TEXT, + domains TEXT, + status CHAR(20), + created DATE, + bump DATE, + secret TEXT, + contract TEXT, + git integer, + backup_slots integer, + hdd integer, + workers integer, + utm_source text, + utm_medium text, + utm_campaign text, + domains_slots integer, + secret_list TEXT, + env_list TEXT +); + +INSERT INTO containers_new + SELECT ID, UUID, container_id, email, expires, tags, env, image, history, + comment, domains, status, created, bump, secret, contract, git, + backup_slots, hdd, workers, utm_source, utm_medium, utm_campaign, + domains_slots, secret_list, env_list + FROM containers; + +DROP TABLE containers; +ALTER TABLE containers_new RENAME TO containers; + +COMMIT; +EOF + echo "Step 4: table rebuilt." +else + echo "Step 4: table already up to date — skipping rebuild." +fi + +echo "Migration complete." diff --git a/app/sbin/nuke/ODOO_19 b/app/sbin/nuke/ODOO_19 deleted file mode 100755 index 34fb8fc..0000000 --- a/app/sbin/nuke/ODOO_19 +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -# Load functions -source /4server/sbin/ODOO_19/ODOO_19.lib -if [[ -z "$UUID" ]]; then - echo "Error: UUID not set. Aborting." - exit 1 -fi - -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}" -BACKUPURL="/4backup/$UUID" - -doas docker stop "$UUID" -doas docker rm "$UUID" - -if [ -n "${UUID:-}" ]; then - echo "Removing directory: $BASEURL" - #doas rm -rf "$BASEURL" - echo "Removing backup directory $BACKUPURL" - #doas rm -rf $BACKUPURL -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 < -# 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 +_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")" +source "$SBIN/helpers" -BIN_PATH="/4server/sbin" +BIN_PATH="$SBIN" +CONTAINER_ID="$1" -UUID="$1" - -if [[ -z "$UUID" ]]; then - echo "Usage: $0 " - exit 1 -fi +[ -z "$CONTAINER_ID" ] && die "Usage: $0 " +log "Nuke container $CONTAINER_ID" get_container_status() { - local uuid="$1" - # Get the container ID or name matching the UUID - CONTAINER_ID=$(docker ps -a --filter "name=$uuid" --format "{{.ID}}") - if [[ -z "$CONTAINER_ID" ]]; then + local container_id="$1" + # Get the Docker container ID or name matching the container_id + DOCKER_ID=$(docker ps -a --filter "name=$container_id" --format "{{.ID}}") + if [[ -z "$DOCKER_ID" ]]; then echo "not_found" return fi - STATUS=$(docker inspect -f '{{.State.Status}}' "$CONTAINER_ID") + STATUS=$(docker inspect -f '{{.State.Status}}' "$DOCKER_ID") echo "$STATUS" } -# Check if container exists -STATUS=$(get_container_status "$UUID") -if [[ "$STATUS" == "running" ]]; then - echo "Container $UUID is still running. Aborting deletion." - exit 2 -fi +STATUS=$(get_container_status "$CONTAINER_ID") +[ "$STATUS" = "running" ] && die "Container $CONTAINER_ID is still running — stop it first" +load_container_config "$CONTAINER_ID" 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) +case "$SOFTWARE" in + n8n) "$BIN_PATH/nuke/n8n" ;; - 002) - "$BIN_PATH/nuke/ODOO_19" + ODOO_17|ODOO_18|ODOO_19) + "$BIN_PATH/nuke/odoo" ;; *) - echo "Unknown UUID type: $SECOND_PART" - exit 2 + die "Unknown SOFTWARE type '${SOFTWARE}' for container_id $CONTAINER_ID — run: initContainer $CONTAINER_ID " ;; esac -#sqlite3 "/4server/data/contracts.db" < $(basename "${ARCHIVE}.gz")" + echo "$CONTAINER_ID rotated -> $(basename "${ARCHIVE}.gz")" # Delete rotated logs older than 4 weeks find "$DIR" -name 'odoo.log.*.gz' -mtime +$RETENTION_DAYS -exec rm -f {} \; \ - -exec echo "$UUID deleted: {}" \; + -exec echo "$CONTAINER_ID deleted: {}" \; done done diff --git a/app/sbin/start/ODOO_17 b/app/sbin/start/ODOO_17 deleted file mode 100755 index d5995bd..0000000 --- a/app/sbin/start/ODOO_17 +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/bash -echo "START ODOO 17" -# Load functions -source /4server/sbin/ODOO_19/ODOO_19.lib -source /4server/sbin/helpers -get_contract_info - -# Config variables -UUID="${UUID:-default}" -BRANCH="${BRANCH:-release}" - -ODOO_DB_USER="${UUID}" -#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') - - -echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID" -echo "DBID: $CONTAINERDBID" - -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/" -INSTALL_DIR="$BASEURL/install/" -SSH_DIR="$BASEURL/.ssh/" -ETC_DIR="$BASEURL/etc/" - -SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1) - -LABEL_DOMAINS=("$UUID.odoo4projects.com") -if [ -f "$BASEURL/etc/domain" ]; then - while IFS= read -r domain || [[ -n $domain ]]; do - [ -z "$domain" ] && continue - LABEL_DOMAINS+=("$domain") - done < "$BASEURL/etc/domain" -else - echo "[DEBUG] No additional domain file found at $BASEURL/etc/domain" -fi - -RULE="" -for d in "${LABEL_DOMAINS[@]}"; do - RESOLVED_IP=$(nslookup "$d" 2>/dev/null | awk '/^Address: / { print $2 }' | head -n1 || true) - if [ -z "$RESOLVED_IP" ]; then - echo "[DEBUG] Could not resolve $d, skipping" - continue - fi - if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then - RULE+=" || Host(\`$d\`)" - else - echo "[DEBUG] Skipping $d (does not match SERVER_IP $SERVER_IP)" - fi -done -RULE="${RULE# || }" -DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE" -echo "[DEBUG] Final Traefik label: $DOMAIN_LABEL" - -docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo/ -docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore - - -find "$BASEURL" -type d -exec chmod 777 {} \; -PORT=$((CONTAINERDBID + 2200)) -echo "PORT $PORT" -mkdir -p ${ETC_DIR} -echo "GITPATH ${ETC_DIR}gitpath" -touch ${ETC_DIR}gitpath - -mkdir -p ${GIT_DIR}keys -touch ${GIT_DIR}keys/id_rsa.pub - - -echo "git clone \"ssh://git@${UUID}.odoo4projects.com:${PORT}/git-server/repos/odoo.git\"" > "${ETC_DIR}/gitpath" - - -docker stop "$UUID" 2>/dev/null -docker rm "$UUID" 2>/dev/null - -EXTRA_DOCKER_PARAMETER="" - -docker run -d --name "$UUID" \ - --network 4server_4projects \ - --restart=always \ - $EXTRA_DOCKER_PARAMETER \ - -v "$DATA_DIR/odoo-web-data:/home/odoo/.local/share/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 "$GIT_DIR:/git-server" \ - -v "$INSTALL_DIR:/mnt/install" \ - -v "$SSH_DIR:/etc/sshkey" \ - -v "$ETC_DIR:/mnt/etc" \ - -p "$PORT:22" \ - -e HOST="beedb" \ - -e USER="$ODOO_DB_USER" \ - -e PASSWORD="$ODOO_DB_PASSWORD" \ - -e UUID="$UUID" \ - -e HDD="$HDD" \ - -e DOMAIN_SLOTS="$DOMAIN_SLOTS" \ - -e BACKUP_SLOTS="$BACKUP_SLOTS" \ - -e WORKER="$WORKER" \ - -e GIT="$GIT" \ - --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_17:$BRANCH - -docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo -docker exec "$UUID" rm -rf /var/lib/odoo/.local/share/Odoo/filestore -docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore - - -docker exec $UUID chown -R odoo:odoo /home/odoo/.local -docker exec $UUID chown -R odoo:odoo /var/lib/odoo/.local/share/Odoo -docker exec $UUID chown -R odoo:odoo /mnt/* -docker exec $UUID chown odoo:odoo /git-server/keys/id_rsa.pub - - -check_and_create_db diff --git a/app/sbin/start/ODOO_18 b/app/sbin/start/ODOO_18 deleted file mode 100755 index f76575b..0000000 --- a/app/sbin/start/ODOO_18 +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/bash - -# Load functions -source /4server/sbin/ODOO_19/ODOO_19.lib -source /4server/sbin/helpers -get_contract_info - -# Config variables -UUID="${UUID:-default}" -BRANCH="${BRANCH:-release}" - -ODOO_DB_USER="${UUID}" -#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') - - -echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID" -echo "DBID: $CONTAINERDBID" - -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/" -INSTALL_DIR="$BASEURL/install/" -SSH_DIR="$BASEURL/.ssh/" -ETC_DIR="$BASEURL/etc/" - -SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1) - -LABEL_DOMAINS=("$UUID.odoo4projects.com") -if [ -f "$BASEURL/etc/domain" ]; then - while IFS= read -r domain || [[ -n $domain ]]; do - [ -z "$domain" ] && continue - LABEL_DOMAINS+=("$domain") - done < "$BASEURL/etc/domain" -else - echo "[DEBUG] No additional domain file found at $BASEURL/etc/domain" -fi - -RULE="" -for d in "${LABEL_DOMAINS[@]}"; do - RESOLVED_IP=$(nslookup "$d" 2>/dev/null | awk '/^Address: / { print $2 }' | head -n1 || true) - if [ -z "$RESOLVED_IP" ]; then - echo "[DEBUG] Could not resolve $d, skipping" - continue - fi - if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then - RULE+=" || Host(\`$d\`)" - else - echo "[DEBUG] Skipping $d (does not match SERVER_IP $SERVER_IP)" - fi -done -RULE="${RULE# || }" -DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE" -echo "[DEBUG] Final Traefik label: $DOMAIN_LABEL" - -docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo/ -docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore - - -find "$BASEURL" -type d -exec chmod 777 {} \; -PORT=$((CONTAINERDBID + 2200)) -echo "PORT $PORT" -mkdir -p ${ETC_DIR} -echo "GITPATH ${ETC_DIR}gitpath" -touch ${ETC_DIR}gitpath - -mkdir -p ${GIT_DIR}keys -touch ${GIT_DIR}keys/id_rsa.pub - - -echo "git clone \"ssh://git@${UUID}.odoo4projects.com:${PORT}/git-server/repos/odoo.git\"" > "${ETC_DIR}/gitpath" - - -docker stop "$UUID" 2>/dev/null -docker rm "$UUID" 2>/dev/null - -EXTRA_DOCKER_PARAMETER="" - -docker run -d --name "$UUID" \ - --network 4server_4projects \ - --restart=always \ - $EXTRA_DOCKER_PARAMETER \ - -v "$DATA_DIR/odoo-web-data:/home/odoo/.local/share/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 "$GIT_DIR:/git-server" \ - -v "$INSTALL_DIR:/mnt/install" \ - -v "$SSH_DIR:/etc/sshkey" \ - -v "$ETC_DIR:/mnt/etc" \ - -p "$PORT:22" \ - -e HOST="beedb" \ - -e USER="$ODOO_DB_USER" \ - -e PASSWORD="$ODOO_DB_PASSWORD" \ - -e UUID="$UUID" \ - -e HDD="$HDD" \ - -e DOMAIN_SLOTS="$DOMAIN_SLOTS" \ - -e BACKUP_SLOTS="$BACKUP_SLOTS" \ - -e WORKER="$WORKER" \ - -e GIT="$GIT" \ - --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_18:$BRANCH - -docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo -docker exec "$UUID" rm -rf /var/lib/odoo/.local/share/Odoo/filestore -docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore - - -docker exec $UUID chown -R odoo:odoo /home/odoo/.local -docker exec $UUID chown -R odoo:odoo /var/lib/odoo/.local/share/Odoo -docker exec $UUID chown -R odoo:odoo /mnt/* -docker exec $UUID chown odoo:odoo /git-server/keys/id_rsa.pub - - -check_and_create_db diff --git a/app/sbin/start/ODOO_19 b/app/sbin/start/ODOO_19 deleted file mode 100755 index 33db1b7..0000000 --- a/app/sbin/start/ODOO_19 +++ /dev/null @@ -1,125 +0,0 @@ -#!/bin/bash - -# Load functions -source /4server/sbin/ODOO_19/ODOO_19.lib -source /4server/sbin/helpers -get_contract_info - -# Config variables -UUID="${UUID:-default}" -BRANCH="${BRANCH:-release}" - -ODOO_DB_USER="${UUID}" -#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') -echo "***** $ODOO_DB_PASSWORD" - -echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID" -echo "DBID: $CONTAINERDBID" - -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/" -INSTALL_DIR="$BASEURL/install/" -SSH_DIR="$BASEURL/.ssh/" -ETC_DIR="$BASEURL/etc/" - -SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1) - -LABEL_DOMAINS=("$UUID.odoo4projects.com") -if [ -f "$BASEURL/etc/domain" ]; then - while IFS= read -r domain || [[ -n $domain ]]; do - [ -z "$domain" ] && continue - LABEL_DOMAINS+=("$domain") - done < "$BASEURL/etc/domain" -else - echo "[DEBUG] No additional domain file found at $BASEURL/etc/domain" -fi - -RULE="" -for d in "${LABEL_DOMAINS[@]}"; do - RESOLVED_IP=$(nslookup "$d" 2>/dev/null | awk '/^Address: / { print $2 }' | head -n1 || true) - if [ -z "$RESOLVED_IP" ]; then - echo "[DEBUG] Could not resolve $d, skipping" - continue - fi - if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then - RULE+=" || Host(\`$d\`)" - else - echo "[DEBUG] Skipping $d (does not match SERVER_IP $SERVER_IP)" - fi -done -RULE="${RULE# || }" -DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE" -echo "[DEBUG] Final Traefik label: $DOMAIN_LABEL" - - - -find "$BASEURL" -type d -exec chmod 777 {} \; -PORT=$((CONTAINERDBID + 2200)) -echo "PORT $PORT" -mkdir -p ${ETC_DIR} -chmod 777 ${ETC_DIR} -echo "GITPATH ${ETC_DIR}gitpath" -touch ${ETC_DIR}gitpath - -mkdir -p ${BACKUP_DIR} -chmod 777 ${BACKUP_DIR} - -mkdir -p ${GIT_DIR}keys -chmod 777 ${GIT_DIR} -touch ${GIT_DIR}keys/id_rsa.pub - - -echo "git clone \"ssh://git@${UUID}.odoo4projects.com:${PORT}/git-server/repos/odoo.git\"" > "${ETC_DIR}/gitpath" - - -docker stop "$UUID" 2>/dev/null -docker rm "$UUID" 2>/dev/null - -EXTRA_DOCKER_PARAMETER="" - -docker run -d --name "$UUID" \ - --network 4server_4projects \ - --restart=always \ - $EXTRA_DOCKER_PARAMETER \ - -v "$DATA_DIR/odoo-web-data:/home/odoo/.local/share/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 "$GIT_DIR:/git-server" \ - -v "$INSTALL_DIR:/mnt/install" \ - -v "$SSH_DIR:/etc/sshkey" \ - -v "$ETC_DIR:/mnt/etc" \ - -p "$PORT:22" \ - -e HOST="beedb" \ - -e USER="$ODOO_DB_USER" \ - -e PASSWORD="$ODOO_DB_PASSWORD" \ - -e UUID="$UUID" \ - -e HDD="$HDD" \ - -e DOMAIN_SLOTS="$DOMAIN_SLOTS" \ - -e BACKUP_SLOTS="$BACKUP_SLOTS" \ - -e WORKER="$WORKER" \ - -e GIT="$GIT" \ - --label "$DOMAIN_LABEL" \ - --label "traefik.http.services.$UUID.loadbalancer.server.port=8069" \ - --label "traefik.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 - - -docker exec $UUID chown -R odoo:odoo /home/odoo/.local -docker exec $UUID chown -R odoo:odoo /mnt/* -docker exec $UUID chown -R git:git /git-server -chmod 777 /4server/data/$UUID/cc - -check_and_create_db diff --git a/app/sbin/start/n8n b/app/sbin/start/n8n index acec4b1..5530830 100755 --- a/app/sbin/start/n8n +++ b/app/sbin/start/n8n @@ -1,29 +1,29 @@ #!/usr/bin/env bash -#--label "traefik.http.routers.${UUID}.middlewares=cors-headers@file" \ -echo "Start N8N container ${UUID}" +#--label "traefik.http.routers.${CONTAINER_ID}.middlewares=cors-headers@file" \ +echo "Start N8N container ${CONTAINER_ID}" # Get the hostname of the machine HOSTNAME=$(hostname) -mkdir -p /4server/data/${UUID}/n8n -mkdir -p /4server/data/${UUID}/data -mkdir -p /4server/data/${UUID}/backup +mkdir -p /4server/data/${CONTAINER_ID}/n8n +mkdir -p /4server/data/${CONTAINER_ID}/data +mkdir -p /4server/data/${CONTAINER_ID}/backup -chmod 777 /4server/data/${UUID}/n8n -chmod 777 /4server/data/${UUID}/data -chmod 777 /4server/data/${UUID}/backup +chmod 777 /4server/data/${CONTAINER_ID}/n8n +chmod 777 /4server/data/${CONTAINER_ID}/data +chmod 777 /4server/data/${CONTAINER_ID}/backup # 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" +if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_ID}$"; then + echo "$(date '+%Y-%m-%d %H:%M') - stopping existing container $CONTAINER_ID" + docker stop "$CONTAINER_ID" + docker rm "$CONTAINER_ID" fi docker run -d \ - --name "$UUID" \ + --name "$CONTAINER_ID" \ -p 5678 \ --cap-add=SYS_ADMIN \ --security-opt seccomp=unconfined \ @@ -35,7 +35,7 @@ docker run -d \ -e N8N_SMTP_PASS="Airbus12N@N" \ -e N8N_SMTP_SENDER="n8n " \ -e N8N_SMTP_SSL=false \ - -e N8N_HOST="${UUID}.odoo4projects.com" \ + -e N8N_HOST="${CONTAINER_ID}.odoo4projects.com" \ -e N8N_PORT=5678 \ -e N8N_PROTOCOL=https \ -e N8N_FORMDATA_FILE_SIZE_MAX=3000 \ @@ -43,19 +43,19 @@ docker run -d \ -e NODE_ENV=production \ -e EXECUTIONS_PRUNE=true \ -e EXECUTIONS_PRUNE_MAX_AGE=48 \ - -e WEBHOOK_URL="https://${UUID}.odoo4projects.com/" \ + -e WEBHOOK_URL="https://${CONTAINER_ID}.odoo4projects.com/" \ -e GENERIC_TIMEZONE="UTC-3" \ -e N8N_BLOCK_ENV_ACCESS_AND_PROCESS_INFORMATION=true \ -e N8N_CUSTOM_EXTENSIONS="/usr/local/share/n8n/custom" \ - -v "/4server/data/${UUID}/n8n:/home/node/.n8n" \ - -v "/4server/data/${UUID}/data:/data" \ - -v "/4server/data/${UUID}/backup:/backup" \ + -v "/4server/data/${CONTAINER_ID}/n8n:/home/node/.n8n" \ + -v "/4server/data/${CONTAINER_ID}/data:/data" \ + -v "/4server/data/${CONTAINER_ID}/backup:/backup" \ --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" \ + --label "traefik.http.routers.${CONTAINER_ID}.rule=Host(\`${CONTAINER_ID}.odoo4projects.com\`)" \ + --label "traefik.http.routers.${CONTAINER_ID}.entrypoints=web,websecure" \ + --label "traefik.http.routers.${CONTAINER_ID}.tls=true" \ + --label "traefik.http.routers.${CONTAINER_ID}.tls.certresolver=production" \ + --label "traefik.http.services.${CONTAINER_ID}.loadbalancer.server.port=5678" \ --network 4server_4projects \ docker.odoo4projects.com/4projects/n8n:release diff --git a/app/sbin/startContainer b/app/sbin/startContainer index a2687fb..8aa5cd7 100755 --- a/app/sbin/startContainer +++ b/app/sbin/startContainer @@ -1,65 +1,46 @@ #!/bin/bash -# Usage: ./start_by_uuid.sh -# 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:%S') Start container $1" -source /4server/sbin/helpers +_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")" +source "$SBIN/helpers" -BIN_PATH="/4server/sbin" +BIN_PATH="$SBIN" +CONTAINER_ID="$1" -UUID="$1" - -if [[ -z "$UUID" ]]; then - echo "$(date '+%Y-%m-%d %H:%M:%S') Usage: $0 " - exit 1 -fi - -if [[ ! "$UUID" =~ ^[0-9a-fA-F-]+$ ]]; then - echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: Invalid UUID format: $UUID" - exit 1 -fi +[ -z "$CONTAINER_ID" ] && die "Usage: $0 " +log "Start container $CONTAINER_ID" -DOMAIN_FILE="/4server/data/$UUID/etc/domain" +DOMAIN_FILE="/4server/data/$CONTAINER_ID/etc/domain" DB_FILE="/4server/data/contracts.db" if [ -f "$DOMAIN_FILE" ]; then DOMAINS=$(paste -sd "," "$DOMAIN_FILE") # Escape single quotes for SQLite ('' is the standard SQLite escape for ') DOMAINS_SAFE="${DOMAINS//\'/\'\'}" - UUID_SAFE="${UUID//\'/\'\'}" - sqlite3 "$DB_FILE" "UPDATE containers SET domains='$DOMAINS_SAFE' WHERE UUID='$UUID_SAFE';" + CONTAINER_ID_SAFE="${CONTAINER_ID//\'/\'\'}" + sqlite3 "$DB_FILE" "UPDATE containers SET domains='$DOMAINS_SAFE' WHERE UUID='$CONTAINER_ID_SAFE';" fi +load_container_config "$CONTAINER_ID" 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) +# Decide which script to run based on SOFTWARE from container.env +case "$SOFTWARE" in + n8n) "$BIN_PATH/start/n8n" ;; - 002) - "$BIN_PATH/start/ODOO_18" + ODOO_17) + IMAGE_TAG=odoo_17 "$BIN_PATH/start/odoo" ;; - - - 003) - "$BIN_PATH/start/ODOO_19" + ODOO_18) + IMAGE_TAG=odoo_18 "$BIN_PATH/start/odoo" ;; - - 004) - "$BIN_PATH/start/ODOO_17" + ODOO_19) + IMAGE_TAG=odoo_19 "$BIN_PATH/start/odoo" ;; - - - *) - echo "$(date '+%Y-%m-%d %H:%M:%S') Unknown UUID type: $SECOND_PART" - exit 2 + die "Unknown SOFTWARE type '${SOFTWARE}' for container_id $CONTAINER_ID — run: initContainer $CONTAINER_ID " ;; esac diff --git a/app/templates/traefik.yaml b/app/templates/traefik.yaml index 7519104..3495377 100644 --- a/app/templates/traefik.yaml +++ b/app/templates/traefik.yaml @@ -87,7 +87,7 @@ http: routers: api-router: - rule: "Host(`{{HOSTNAME}}.odoo4projects.com`)" + rule: "Host(`{{HOSTNAME}}.odoo4projects.com`) || Host(`api.{{HOSTNAME}}.odoo4projects.com`)" service: api-service entryPoints: - websecure diff --git a/app/update b/app/update index f29d46e..2b541ad 100755 --- a/app/update +++ b/app/update @@ -31,7 +31,7 @@ rex doas pip install --root-user-action ignore --break-system-packages --no-cach ### own bins echo "Running prsync ./sbin" -prsync -h "/app/host_vars/hosts" -avz ./sbin/ /4server/sbin/ +prsync -h "/app/host_vars/hosts" -avz ./sbin/ /usr/sbin/ ### POSTGRESS rex mkdir -p /4server/data/postgres/etc diff --git a/app/update_sbin b/app/update_sbin index ae0b36c..60e54df 100755 --- a/app/update_sbin +++ b/app/update_sbin @@ -1,7 +1,7 @@ #!/bin/bash echo "Running prsync ./sbin" -prsync -h "/app/host_vars/hosts" -avz ./sbin/ /4server/sbin/ +prsync -h "/app/host_vars/hosts" -avz ./sbin/ /usr/sbin/ template templates/.profile /home/4server/.profile template templates/postgresql.conf /4server/data/postgres/data/postgresql.conf diff --git a/app/vault/host_vars.img b/app/vault/host_vars.img index 08baff2..09a58da 100644 Binary files a/app/vault/host_vars.img and b/app/vault/host_vars.img differ