This commit is contained in:
Your Name
2026-04-11 18:12:42 -03:00
parent b14d2f4fc1
commit 73090ddaa1
12 changed files with 346 additions and 128 deletions
Regular → Executable
View File
+175 -75
View File
@@ -1,30 +1,27 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from fastapi import FastAPI, HTTPException, Depends, Response import json
from fastapi.security.api_key import APIKeyHeader import os
from fastapi.responses import RedirectResponse, PlainTextResponse import re
from pydantic import BaseModel
import psutil
import sqlite3 import sqlite3
import subprocess import subprocess
import os
import uvicorn
from typing import Dict, Any, Optional
from datetime import datetime
import json
import re
from collections import deque
import time import time
from collections import deque
from datetime import datetime
from pathlib import Path from pathlib import Path
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 ---------------------- # ---------------------- Constants ----------------------
DB_PATH = "/4server/data/contracts.db" DB_PATH = "/4server/data/contracts.db"
BIN_PATH = "/4server/sbin" BIN_PATH = "/4server/sbin"
API_KEY = os.getenv("API_KEY", "your-secret-api-key") API_KEY = os.getenv("API_KEY", "your-secret-api-key")
VERSION = "API: 0.0.8" VERSION = "API: 0.0.9"
# ---------------------- FastAPI App ---------------------- # ---------------------- FastAPI App ----------------------
app = FastAPI() app = FastAPI()
@@ -57,7 +54,7 @@ def init_db():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(''' cursor.execute("""
CREATE TABLE IF NOT EXISTS containers ( CREATE TABLE IF NOT EXISTS containers (
ID INTEGER PRIMARY KEY AUTOINCREMENT, ID INTEGER PRIMARY KEY AUTOINCREMENT,
UUID CHAR(50) UNIQUE, UUID CHAR(50) UNIQUE,
@@ -74,9 +71,19 @@ def init_db():
created DATE, created DATE,
bump DATE, bump DATE,
secret TEXT, secret TEXT,
contract 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.commit()
conn.close() conn.close()
@@ -101,13 +108,12 @@ class ContractItem(BaseModel):
product_id: int product_id: int
features: Dict[str, Any] features: Dict[str, Any]
class ContainerModel(BaseModel): class ContainerModel(BaseModel):
UUID: str UUID: str
email: Optional[str] = None email: Optional[str] = None
expires: Optional[str] = None expires: Optional[str] = None
tags: Optional[str] = None tags: Optional[str] = None
env: Optional[Dict[str, Any]] = None
affiliate: Optional[str] = None
image: Optional[str] = None image: Optional[str] = None
history: Optional[str] = None history: Optional[str] = None
comment: Optional[str] = None comment: Optional[str] = None
@@ -115,37 +121,56 @@ class ContainerModel(BaseModel):
status: Optional[str] = None status: Optional[str] = None
created: Optional[str] = None created: Optional[str] = None
bump: Optional[str] = None bump: Optional[str] = None
secret: Optional[Dict[str, Any]] = None git: Optional[int] = None
contract: Optional[str] = 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
env_list: Optional[str] = None
class UUIDRequest(BaseModel): class UUIDRequest(BaseModel):
UUID: str UUID: str
class CommandRequest(BaseModel): class CommandRequest(BaseModel):
uuid: str uuid: str
method: int method: int
class ImportRequest(BaseModel): class ImportRequest(BaseModel):
filename: str filename: str
class MoveRequest(BaseModel): class MoveRequest(BaseModel):
source: str source: str
destination: str destination: str
class AddKeyRequest(BaseModel): class AddKeyRequest(BaseModel):
key: str key: str
uuid: str uuid: str
class ImageRequest(BaseModel):
image: str
# ---------------------- Routes ---------------------- # ---------------------- Routes ----------------------
@app.get("/", include_in_schema=False) @app.get("/", include_in_schema=False)
def redirect_to_odoo(): def redirect_to_odoo():
return RedirectResponse(url="https://ODOO4PROJECTS.com") return RedirectResponse(url="https://ODOO4PROJECTS.com")
from fastapi import FastAPI, Depends
import json
from typing import Any, Dict, Optional
from fastapi import Depends, FastAPI
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional, Dict, Any
import json
app = FastAPI() app = FastAPI()
@@ -158,25 +183,42 @@ def redirect_to_odoo():
@app.post("/container/update", dependencies=[Depends(verify_api_key)]) @app.post("/container/update", dependencies=[Depends(verify_api_key)])
def update_container(request: ContainerModel): def update_container(request: ContainerModel):
# Convert dict fields to JSON strings
env_str = json.dumps(request.env) if isinstance(request.env, dict) else None
secret_str = json.dumps(request.secret) if isinstance(request.secret, dict) else None
contract_str = json.dumps(request.contract) if isinstance(request.contract, dict) else None
# Fetch existing record # Fetch existing record
existing = execute_db("SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True) existing = execute_db(
"SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True
)
if not existing: if not existing:
# If record does not exist, insert a new one with all given fields # If record does not exist, insert a new one with all given fields
execute_db(""" execute_db(
INSERT INTO containers (UUID, email, expires, tags, env, affiliate, image, history, """
comment, domains, status, created, bump, secret, contract) INSERT INTO containers (UUID, email, expires, tags, image, history,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) comment, domains, status, created, bump, git, backup_slots, hdd, workers,
""", ( utm_source, utm_medium, utm_campaign, domains_slots, env_list)
request.UUID, request.email, request.expires, request.tags, env_str, VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
request.affiliate, request.image, request.history, request.comment, """,
request.domains, request.status, request.created, request.bump, secret_str, contract_str (
)) 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,
request.env_list,
),
)
return {"UUID": request.UUID, "status": "created"} return {"UUID": request.UUID, "status": "created"}
# Existing record found, do partial update # Existing record found, do partial update
@@ -190,9 +232,6 @@ def update_container(request: ContainerModel):
continue continue
value = getattr(request, field) value = getattr(request, field)
if value is not None: if value is not None:
if field in ["env", "secret", "contract"]:
value = json.dumps(value)
updates[field] = value updates[field] = value
params.append(value) params.append(value)
@@ -201,7 +240,11 @@ def update_container(request: ContainerModel):
set_clause = ", ".join(f"{k}=?" for k in updates.keys()) set_clause = ", ".join(f"{k}=?" for k in updates.keys())
params.append(request.UUID) # UUID for WHERE clause params.append(request.UUID) # UUID for WHERE clause
execute_db(f"UPDATE containers SET {set_clause} WHERE UUID=?", tuple(params)) 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": "updated",
"fields_updated": list(updates.keys()),
}
return {"UUID": request.UUID, "status": "no_change"} return {"UUID": request.UUID, "status": "no_change"}
@@ -211,6 +254,11 @@ def start_container(request: UUIDRequest):
return {"message": run_command([f"{BIN_PATH}/startContainer", request.UUID])} 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)]) @app.post("/container/stop", dependencies=[Depends(verify_api_key)])
def stop_container(request: UUIDRequest): def stop_container(request: UUIDRequest):
try: try:
@@ -222,7 +270,9 @@ def stop_container(request: UUIDRequest):
@app.post("/container/nuke", dependencies=[Depends(verify_api_key)]) @app.post("/container/nuke", dependencies=[Depends(verify_api_key)])
def nuke_container(request: UUIDRequest): def nuke_container(request: UUIDRequest):
status = execute_db("SELECT status FROM containers WHERE UUID=?", (request.UUID,), fetch=True) status = execute_db(
"SELECT status FROM containers WHERE UUID=?", (request.UUID,), fetch=True
)
if not status or status[0]["status"] != "nuke": if not status or status[0]["status"] != "nuke":
raise HTTPException(400, "Container status is not '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.UUID])}
@@ -232,8 +282,26 @@ def nuke_container(request: UUIDRequest):
def info_container(request: Optional[UUIDRequest] = None): def info_container(request: Optional[UUIDRequest] = None):
# Fields to select # Fields to select
fields = [ fields = [
"ID", "UUID", "email", "expires", "tags", "env", "affiliate", "ID",
"image", "history", "comment", "domains", "status", "created", "contract" "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) field_str = ", ".join(fields)
@@ -242,13 +310,10 @@ def info_container(request: Optional[UUIDRequest] = None):
rows = execute_db( rows = execute_db(
f"SELECT {field_str} FROM containers WHERE UUID=?", f"SELECT {field_str} FROM containers WHERE UUID=?",
(str(request.UUID),), (str(request.UUID),),
fetch=True fetch=True,
) )
else: else:
rows = execute_db( rows = execute_db(f"SELECT {field_str} FROM containers", fetch=True)
f"SELECT {field_str} FROM containers",
fetch=True
)
# Map rows to dicts safely # Map rows to dicts safely
containers = [] containers = []
@@ -265,6 +330,7 @@ def info_container(request: Optional[UUIDRequest] = None):
return n8n_items return n8n_items
@app.post("/container/bump", dependencies=[Depends(verify_api_key)]) @app.post("/container/bump", dependencies=[Depends(verify_api_key)])
def bump_container(request: UUIDRequest): def bump_container(request: UUIDRequest):
today = datetime.utcnow().strftime("%Y-%m-%d") today = datetime.utcnow().strftime("%Y-%m-%d")
@@ -275,10 +341,16 @@ def bump_container(request: UUIDRequest):
@app.post("/container/quota", dependencies=[Depends(verify_api_key)]) @app.post("/container/quota", dependencies=[Depends(verify_api_key)])
def container_quota(request: UUIDRequest): def container_quota(request: UUIDRequest):
output = run_command([ output = run_command(
"docker", "stats", request.UUID, "--no-stream", [
"--format", "{{.MemUsage}},{{.BlockIO}}" "docker",
]) "stats",
request.UUID,
"--no-stream",
"--format",
"{{.MemUsage}},{{.BlockIO}}",
]
)
mem_usage, disk_usage = output.split(",") mem_usage, disk_usage = output.split(",")
return {"memory_usage": mem_usage, "disk_io": disk_usage} return {"memory_usage": mem_usage, "disk_io": disk_usage}
@@ -286,7 +358,10 @@ def container_quota(request: UUIDRequest):
# ---------------------- SYSTEM ---------------------- # ---------------------- SYSTEM ----------------------
@app.get("/system/containers", dependencies=[Depends(verify_api_key)]) @app.get("/system/containers", dependencies=[Depends(verify_api_key)])
def get_containers(): def get_containers():
return Response(content=run_command([f"{BIN_PATH}/getContainers"]), media_type="application/json") return Response(
content=run_command([f"{BIN_PATH}/getContainers"]),
media_type="application/json",
)
@app.get("/system/images", dependencies=[Depends(verify_api_key)]) @app.get("/system/images", dependencies=[Depends(verify_api_key)])
@@ -294,6 +369,7 @@ def list_images():
images = run_command(["docker", "images", "--format", "{{.Repository}}:{{.Tag}}"]) images = run_command(["docker", "images", "--format", "{{.Repository}}:{{.Tag}}"])
return {"images": images.split("\n")} return {"images": images.split("\n")}
@app.get("/system/cpu", dependencies=[Depends(verify_api_key)]) @app.get("/system/cpu", dependencies=[Depends(verify_api_key)])
def get_cpu_log(): def get_cpu_log():
CPU_LOG_PATH = Path("/4server/data/log/cpu.log") CPU_LOG_PATH = Path("/4server/data/log/cpu.log")
@@ -307,12 +383,15 @@ def get_cpu_log():
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Error reading CPU log: {e}") raise HTTPException(status_code=500, detail=f"Error reading CPU log: {e}")
@app.get("/system/info", dependencies=[Depends(verify_api_key)]) @app.get("/system/info", dependencies=[Depends(verify_api_key)])
def get_system_info(): def get_system_info():
try: try:
alpine_version = None alpine_version = None
last_update = None last_update = None
bump_dates = execute_db("SELECT MAX(bump) AS latest_bump FROM containers", fetch=True)[0]["latest_bump"] bump_dates = execute_db(
"SELECT MAX(bump) AS latest_bump FROM containers", fetch=True
)[0]["latest_bump"]
if os.path.exists("/4server/data/update"): if os.path.exists("/4server/data/update"):
with open("/4server/data/update") as f: with open("/4server/data/update") as f:
last_update = f.read().strip() last_update = f.read().strip()
@@ -328,10 +407,14 @@ def get_system_info():
"latest_bump": bump_dates, "latest_bump": bump_dates,
"version": VERSION, "version": VERSION,
"resources": { "resources": {
"memory": {"total": mem.total, "available": mem.available, "used": mem.used}, "memory": {
"total": mem.total,
"available": mem.available,
"used": mem.used,
},
"disk": {"total": disk.total, "used": disk.used, "free": disk.free}, "disk": {"total": disk.total, "used": disk.used, "free": disk.free},
"cpu_count": cpu_count "cpu_count": cpu_count,
} },
} }
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@@ -342,6 +425,13 @@ def pull_all_images():
return {"message": run_command([f"{BIN_PATH}/pullAllContainers"])} 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)]) @app.post("/client/git", dependencies=[Depends(verify_api_key)])
def git_tool(request: CommandRequest): def git_tool(request: CommandRequest):
if request.method == 1: if request.method == 1:
@@ -382,7 +472,10 @@ def audit_odoo(uuid: str):
@app.get("/client/logs/{uuid}", dependencies=[Depends(verify_api_key)]) @app.get("/client/logs/{uuid}", dependencies=[Depends(verify_api_key)])
async def get_odoo_log_summary(uuid: str): async def get_odoo_log_summary(uuid: str):
if not re.fullmatch(r"[0-9a-fA-F\-]+", uuid): 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.") raise HTTPException(
status_code=400,
detail="Invalid UUID format. Only numbers, letters a-f, and '-' are allowed.",
)
BASE_LOG_DIR = "/4server/data" BASE_LOG_DIR = "/4server/data"
@@ -396,12 +489,12 @@ async def get_odoo_log_summary(uuid: str):
# --- Helper variables and functions --- # --- Helper variables and functions ---
IMPORTANT_PATTERNS = [ IMPORTANT_PATTERNS = [
re.compile(r"odoo\.addons\."), # Any Odoo addon log re.compile(r"odoo\.addons\."), # Any Odoo addon log
re.compile(r"Job '.*' starting"), # Cron job start re.compile(r"Job '.*' starting"), # Cron job start
re.compile(r"Job '.*' fully done"), # Cron job end re.compile(r"Job '.*' fully done"), # Cron job end
re.compile(r"ERROR"), # Errors re.compile(r"ERROR"), # Errors
re.compile(r"WARNING"), # Warnings re.compile(r"WARNING"), # Warnings
re.compile(r"Traceback"), # Tracebacks re.compile(r"Traceback"), # Tracebacks
re.compile(r"Error"), re.compile(r"Error"),
] ]
@@ -424,7 +517,9 @@ async def get_odoo_log_summary(uuid: str):
try: try:
# Last 500 lines from Odoo log # Last 500 lines from Odoo log
last_500_lines = read_last_lines(odoo_log_file, 500) 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)] important_odoo_lines = [
line for line in last_500_lines if is_important_line(line)
]
# Last 50 lines from git.log # Last 50 lines from git.log
last_50_git_lines = read_last_lines(git_log_file, 50) last_50_git_lines = read_last_lines(git_log_file, 50)
@@ -437,6 +532,7 @@ async def get_odoo_log_summary(uuid: str):
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Error reading log files: {e}") raise HTTPException(status_code=500, detail=f"Error reading log files: {e}")
# ------------------------ BACKUP HANDLING ------------------------------------- # ------------------------ BACKUP HANDLING -------------------------------------
@app.post("/backup/import", dependencies=[Depends(verify_api_key)]) @app.post("/backup/import", dependencies=[Depends(verify_api_key)])
def backup_import(request: ImportRequest): def backup_import(request: ImportRequest):
@@ -447,10 +543,13 @@ def backup_import(request: ImportRequest):
output = run_command(command) output = run_command(command)
return {"message": output} return {"message": output}
@app.post("/backup/move", dependencies=[Depends(verify_api_key)]) @app.post("/backup/move", dependencies=[Depends(verify_api_key)])
def backup_move(request: MoveRequest): def backup_move(request: MoveRequest):
if not request.source or not request.destination: if not request.source or not request.destination:
raise HTTPException(status_code=400, detail="Source and destination are required") raise HTTPException(
status_code=400, detail="Source and destination are required"
)
if not os.path.exists(request.source): if not os.path.exists(request.source):
raise HTTPException(status_code=404, detail="Source file does not exist") raise HTTPException(status_code=404, detail="Source file does not exist")
@@ -460,7 +559,10 @@ def backup_move(request: MoveRequest):
# For Windows, use: command = ["move", request.source, request.destination] # For Windows, use: command = ["move", request.source, request.destination]
output = run_command(command) output = run_command(command)
return {"message": f"Moved {request.source} to {request.destination}", "output": output} return {
"message": f"Moved {request.source} to {request.destination}",
"output": output,
}
# ---------------------- Entry Point ---------------------- # ---------------------- Entry Point ----------------------
@@ -469,5 +571,3 @@ if __name__ == "__main__":
init_db() init_db()
time.sleep(25) time.sleep(25)
uvicorn.run(app, host="10.5.0.1", port=8888) uvicorn.run(app, host="10.5.0.1", port=8888)
+36 -23
View File
@@ -5,34 +5,47 @@ POSTGRES_PORT="${POSTGRES_PORT:-5432}"
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}" POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}" POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
DB_PATH="/4server/data/contracts.db"
get_contract_info() { get_contract_info() {
DB_PATH="/4server/data/contracts.db" echo "get_contract_info $UUID"
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;
")
while IFS="=" read -r key value; do # Parse SECRET_LIST: backslash-separated key=value pairs (e.g. worex1=1\pswl=22)
if [ -n "$key" ]; then # - printf avoids echo interpreting escape sequences
export "$key=$value" # - 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 fi
done < <(sqlite3 "$DB_PATH" " export ODOO_DB_PASSWORD=$psql
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=' || secret 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';
")
eval $(echo "$ENV" | jq -r 'to_entries | .[] | "export \(.key | ascii_upcase)=\(.value)"')
} }
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
sqlite3 /4server/data/contracts.db <<EOF
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
+57
View File
@@ -0,0 +1,57 @@
#!/bin/bash
# Usage: ./odoo_passwd.sh <container_name>
CONTAINER=$1
if [ -z "$CONTAINER" ]; then
echo "Usage: $0 <docker_container>"
exit 1
fi
# Step 1: Get users list
echo "Fetching users..."
USERS=$(doas docker exec -i "$CONTAINER" bash -c \
'PGPASSWORD="$PASSWORD" psql -h "$HOST" -U "$USER" -At -F"," -c "SELECT id, login FROM res_users ORDER BY id;"')
# Step 2: Display users
echo "Select a user:"
IFS=$'\n'
select USER_LINE in $USERS; do
if [ -n "$USER_LINE" ]; then
break
fi
done
USER_ID=$(echo "$USER_LINE" | cut -d',' -f1)
USER_LOGIN=$(echo "$USER_LINE" | cut -d',' -f2)
echo "Selected user: $USER_LOGIN (ID: $USER_ID)"
# Step 3: Ask for new password
read -s -p "Enter new password: " NEW_PASS
echo
read -s -p "Confirm password: " CONFIRM_PASS
echo
if [ "$NEW_PASS" != "$CONFIRM_PASS" ]; then
echo "Passwords do not match!"
exit 1
fi
# Step 4: Generate Odoo-compatible hash using Python inside container
HASH=$(doas docker exec -i "$CONTAINER" python3 - <<EOF
from passlib.context import CryptContext
ctx = CryptContext(schemes=['pbkdf2_sha512'])
print(ctx.hash("$NEW_PASS"))
EOF
)
echo "Updating password..."
# Step 5: Update DB
doas docker exec -i "$CONTAINER" bash -c \
"PGPASSWORD=\"$PASSWORD\" psql -h \"$HOST\" -U \"$USER\" -c \"UPDATE res_users SET password='$HASH' WHERE id=$USER_ID;\""
echo "Password updated successfully for $USER_LOGIN"
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
doas docker exec -it $1 bash -c 'PGPASSWORD="$PASSWORD" psql -h "$HOST" -U "$USER"'
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# Check that a search string was provided
if [ -z "$1" ]; then
echo "Usage: $0 <search_string>"
exit 1
fi
SEARCH="$1"
DB_PATH="/4server/data/contracts.db"
# Get all UUIDs where tags include the search string
uuids=$(sqlite3 "$DB_PATH" "SELECT UUID FROM containers WHERE tags LIKE '%$SEARCH%';")
# Loop through each UUID
for uuid in $uuids; do
# Check if a container with this name is running
if doas docker ps --format '{{.Names}}' | grep -qx "$uuid"; then
echo "Restarting $uuid"
/4server/sbin/startContainer $uuid
fi
done
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
doas docker exec -it $1 bash -c 'PGPASSWORD="$PASSWORD" psql -h "$HOST" -U "$USER"'
+3 -3
View File
@@ -10,10 +10,10 @@ UUID="${UUID:-default}"
BRANCH="${BRANCH:-release}" BRANCH="${BRANCH:-release}"
ODOO_DB_USER="${UUID}" ODOO_DB_USER="${UUID}"
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') #export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
echo "ENV: $HDD $DOMAIN_COUNT $BACKUP_SLOTS $CONTAINERDBID" echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID"
echo "DBID: $CONTAINERDBID" echo "DBID: $CONTAINERDBID"
BASEURL="${BASEURL:-/4server/data/$UUID}" BASEURL="${BASEURL:-/4server/data/$UUID}"
@@ -101,7 +101,7 @@ docker run -d --name "$UUID" \
-e PASSWORD="$ODOO_DB_PASSWORD" \ -e PASSWORD="$ODOO_DB_PASSWORD" \
-e UUID="$UUID" \ -e UUID="$UUID" \
-e HDD="$HDD" \ -e HDD="$HDD" \
-e DOMAIN_COUNT="$DOMAIN_COUNT" \ -e DOMAIN_SLOTS="$DOMAIN_SLOTS" \
-e BACKUP_SLOTS="$BACKUP_SLOTS" \ -e BACKUP_SLOTS="$BACKUP_SLOTS" \
-e WORKER="$WORKER" \ -e WORKER="$WORKER" \
-e GIT="$GIT" \ -e GIT="$GIT" \
+3 -3
View File
@@ -10,10 +10,10 @@ UUID="${UUID:-default}"
BRANCH="${BRANCH:-release}" BRANCH="${BRANCH:-release}"
ODOO_DB_USER="${UUID}" ODOO_DB_USER="${UUID}"
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') #export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
echo "ENV: $HDD $DOMAIN_COUNT $BACKUP_SLOTS $CONTAINERDBID" echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID"
echo "DBID: $CONTAINERDBID" echo "DBID: $CONTAINERDBID"
BASEURL="${BASEURL:-/4server/data/$UUID}" BASEURL="${BASEURL:-/4server/data/$UUID}"
@@ -101,7 +101,7 @@ docker run -d --name "$UUID" \
-e PASSWORD="$ODOO_DB_PASSWORD" \ -e PASSWORD="$ODOO_DB_PASSWORD" \
-e UUID="$UUID" \ -e UUID="$UUID" \
-e HDD="$HDD" \ -e HDD="$HDD" \
-e DOMAIN_COUNT="$DOMAIN_COUNT" \ -e DOMAIN_SLOTS="$DOMAIN_SLOTS" \
-e BACKUP_SLOTS="$BACKUP_SLOTS" \ -e BACKUP_SLOTS="$BACKUP_SLOTS" \
-e WORKER="$WORKER" \ -e WORKER="$WORKER" \
-e GIT="$GIT" \ -e GIT="$GIT" \
+4 -4
View File
@@ -10,10 +10,10 @@ UUID="${UUID:-default}"
BRANCH="${BRANCH:-release}" BRANCH="${BRANCH:-release}"
ODOO_DB_USER="${UUID}" ODOO_DB_USER="${UUID}"
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') #export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
echo "***** $ODOO_DB_PASSWORD"
echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID"
echo "ENV: $HDD $DOMAIN_COUNT $BACKUP_SLOTS $CONTAINERDBID"
echo "DBID: $CONTAINERDBID" echo "DBID: $CONTAINERDBID"
BASEURL="${BASEURL:-/4server/data/$UUID}" BASEURL="${BASEURL:-/4server/data/$UUID}"
@@ -104,7 +104,7 @@ docker run -d --name "$UUID" \
-e PASSWORD="$ODOO_DB_PASSWORD" \ -e PASSWORD="$ODOO_DB_PASSWORD" \
-e UUID="$UUID" \ -e UUID="$UUID" \
-e HDD="$HDD" \ -e HDD="$HDD" \
-e DOMAIN_COUNT="$DOMAIN_COUNT" \ -e DOMAIN_SLOTS="$DOMAIN_SLOTS" \
-e BACKUP_SLOTS="$BACKUP_SLOTS" \ -e BACKUP_SLOTS="$BACKUP_SLOTS" \
-e WORKER="$WORKER" \ -e WORKER="$WORKER" \
-e GIT="$GIT" \ -e GIT="$GIT" \
+9 -6
View File
@@ -15,16 +15,20 @@ if [[ -z "$UUID" ]]; then
exit 1 exit 1
fi fi
if [[ ! "$UUID" =~ ^[0-9a-fA-F-]+$ ]]; then
echo "ERROR: Invalid UUID format: $UUID"
exit 1
fi
DOMAIN_FILE="/4server/data/$UUID/etc/domain" DOMAIN_FILE="/4server/data/$UUID/etc/domain"
DB_FILE="/4server/data/contracts.db" DB_FILE="/4server/data/contracts.db"
if [ -f "$DOMAIN_FILE" ]; then if [ -f "$DOMAIN_FILE" ]; then
DOMAINS=$(paste -sd "," "$DOMAIN_FILE") DOMAINS=$(paste -sd "," "$DOMAIN_FILE")
sqlite3 "$DB_FILE" <<SQL # Escape single quotes for SQLite ('' is the standard SQLite escape for ')
UPDATE containers DOMAINS_SAFE="${DOMAINS//\'/\'\'}"
SET domains='$DOMAINS' UUID_SAFE="${UUID//\'/\'\'}"
WHERE UUID='$UUID'; sqlite3 "$DB_FILE" "UPDATE containers SET domains='$DOMAINS_SAFE' WHERE UUID='$UUID_SAFE';"
SQL
fi fi
@@ -59,4 +63,3 @@ case "$SECOND_PART" in
exit 2 exit 2
;; ;;
esac esac