diff --git a/app/download_sbin b/app/download_sbin new file mode 100755 index 0000000..f2907f5 --- /dev/null +++ b/app/download_sbin @@ -0,0 +1,6 @@ +#!/bin/bash + +rsync -avz --progress -e ssh dev:/4server/sbin /app/sbin + + + diff --git a/app/sbin/ODOO_19/ODOO_19.lib b/app/sbin/ODOO_19/ODOO_19.lib new file mode 100644 index 0000000..3b61ac2 --- /dev/null +++ b/app/sbin/ODOO_19/ODOO_19.lib @@ -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" +} diff --git a/app/sbin/ODOO_19/restore b/app/sbin/ODOO_19/restore new file mode 100755 index 0000000..ec1ccb1 --- /dev/null +++ b/app/sbin/ODOO_19/restore @@ -0,0 +1,59 @@ +#!/bin/bash +export PATH=/4PROJECTS/bin:$PATH + +if [ ! -n "$2" ]; then + echo "Missing Parameters " + 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%}" diff --git a/app/sbin/nuke/ODOO_19 b/app/sbin/nuke/ODOO_19 new file mode 100755 index 0000000..6a91876 --- /dev/null +++ b/app/sbin/nuke/ODOO_19 @@ -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 < +# 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 " + 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 + diff --git a/app/sbin/pullAllContainers b/app/sbin/pullAllContainers new file mode 100755 index 0000000..eacc698 --- /dev/null +++ b/app/sbin/pullAllContainers @@ -0,0 +1,7 @@ +#!/bin/bash + +docker ps -a --format '{{.Image}}' | sort -u | xargs -n1 docker pull + + + + diff --git a/app/sbin/restoreODOO b/app/sbin/restoreODOO deleted file mode 100644 index 6b675b7..0000000 --- a/app/sbin/restoreODOO +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -export PATH=/4PROJECTS/bin:$PATH - -if [ ! -n "$2" ]; then - echo "Missing Parameters " - 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 - diff --git a/app/sbin/sbin/ODOO_19/ODOO_19.lib b/app/sbin/sbin/ODOO_19/ODOO_19.lib new file mode 100644 index 0000000..3b61ac2 --- /dev/null +++ b/app/sbin/sbin/ODOO_19/ODOO_19.lib @@ -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" +} diff --git a/app/sbin/sbin/ODOO_19/restore b/app/sbin/sbin/ODOO_19/restore new file mode 100755 index 0000000..491c9e9 --- /dev/null +++ b/app/sbin/sbin/ODOO_19/restore @@ -0,0 +1,59 @@ +#!/bin/bash +export PATH=/4PROJECTS/bin:$PATH + +if [ ! -n "$2" ]; then + echo "Missing Parameters " + 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%}" diff --git a/app/sbin/sbin/api b/app/sbin/sbin/api new file mode 100755 index 0000000..5b25ef0 --- /dev/null +++ b/app/sbin/sbin/api @@ -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) + + diff --git a/app/sbin/sbin/getContainers b/app/sbin/sbin/getContainers new file mode 100755 index 0000000..544032a --- /dev/null +++ b/app/sbin/sbin/getContainers @@ -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 "]" + diff --git a/app/sbin/sbin/helpers b/app/sbin/sbin/helpers new file mode 100755 index 0000000..8ebbd9f --- /dev/null +++ b/app/sbin/sbin/helpers @@ -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)"') +} diff --git a/app/sbin/sbin/nebula b/app/sbin/sbin/nebula new file mode 100755 index 0000000..615b717 Binary files /dev/null and b/app/sbin/sbin/nebula differ diff --git a/app/sbin/sbin/nebula-cert b/app/sbin/sbin/nebula-cert new file mode 100755 index 0000000..d3f698c Binary files /dev/null and b/app/sbin/sbin/nebula-cert differ diff --git a/app/sbin/sbin/nuke/ODOO_19 b/app/sbin/sbin/nuke/ODOO_19 new file mode 100755 index 0000000..6a91876 --- /dev/null +++ b/app/sbin/sbin/nuke/ODOO_19 @@ -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 < +# 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 " + 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 + diff --git a/app/sbin/start/ODOO_18 b/app/sbin/sbin/start/ODOO_18 similarity index 100% rename from app/sbin/start/ODOO_18 rename to app/sbin/sbin/start/ODOO_18 diff --git a/app/sbin/start/ODOO_18.lib b/app/sbin/sbin/start/ODOO_18.lib similarity index 100% rename from app/sbin/start/ODOO_18.lib rename to app/sbin/sbin/start/ODOO_18.lib diff --git a/app/sbin/sbin/start/ODOO_19 b/app/sbin/sbin/start/ODOO_19 new file mode 100755 index 0000000..6c50ee3 --- /dev/null +++ b/app/sbin/sbin/start/ODOO_19 @@ -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 diff --git a/app/sbin/sbin/start/n8n b/app/sbin/sbin/start/n8n new file mode 100755 index 0000000..91c8eb6 --- /dev/null +++ b/app/sbin/sbin/start/n8n @@ -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" diff --git a/app/sbin/sbin/startContainer b/app/sbin/sbin/startContainer new file mode 100755 index 0000000..82eeae6 --- /dev/null +++ b/app/sbin/sbin/startContainer @@ -0,0 +1,36 @@ +#!/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') Start container $1" + +source /4server/sbin/helpers + +BIN_PATH="/4server/sbin" + +UUID="$1" + +if [[ -z "$UUID" ]]; then + echo "Usage: $0 " + 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 + diff --git a/app/sbin/start/ODOO_19 b/app/sbin/start/ODOO_19 new file mode 100755 index 0000000..3e6f97c --- /dev/null +++ b/app/sbin/start/ODOO_19 @@ -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 diff --git a/app/sbin/startContainer b/app/sbin/startContainer index fd9617a..82eeae6 100755 --- a/app/sbin/startContainer +++ b/app/sbin/startContainer @@ -26,9 +26,6 @@ case "$SECOND_PART" in "$BIN_PATH/start/n8n" ;; 002) - "$BIN_PATH/start/ODOO_18" - ;; - 003) "$BIN_PATH/start/ODOO_19" ;; *)