Compare commits

..

27 Commits

Author SHA1 Message Date
Your Name eb4d83e9e9 hmm 2026-05-27 16:28:02 -03:00
Your Name 5c6dbe416a n 2026-04-21 17:09:04 -03:00
oliver 72243d8b18 pruning 2026-04-21 17:07:07 -03:00
oliver 11a1c2635f comment 2026-04-21 16:32:37 -03:00
oliver 835165289b Update api 2026-04-21 16:28:49 -03:00
oliver 08b9c38d0f get 2026-04-21 16:18:27 -03:00
oliver eb8c770506 Update api 2026-04-21 16:07:22 -03:00
oliver 3c33bd86e5 works 2026-04-21 16:04:12 -03:00
oliver 04f665a20d Update borgpush 2026-04-21 15:55:54 -03:00
oliver 1d2ddd85e1 borgpush 2026-04-21 15:55:03 -03:00
oliver 0251f06861 backup 2026-04-21 13:59:34 -03:00
oliver 4328750ac8 h 2026-04-21 13:53:37 -03:00
oliver 22e49faf46 m 2026-04-21 13:49:52 -03:00
oliver c94bac2db3 n 2026-04-21 13:49:29 -03:00
oliver 1f08c66c78 c 2026-04-21 13:08:18 -03:00
oliver ba5ad005a6 Backup 2026-04-21 12:45:18 -03:00
oliver 1b64bdc08d b 2026-04-21 12:17:42 -03:00
oliver 7e3f4be906 Create backup 2026-04-21 12:17:33 -03:00
oliver d354d989e7 Backup 17, 18 changed 2026-04-21 12:03:46 -03:00
oliver 902c43ec89 Update ODOO_19 2026-04-21 12:02:39 -03:00
oliver 717045baa6 added modules 2026-04-21 11:51:35 -03:00
oliver e672b1d2c6 ln, update 2026-04-21 11:44:30 -03:00
oliver ad59e8f0f1 NEW 2026-04-21 11:39:34 -03:00
Your Name 943d67045e secret list 2026-04-11 19:30:00 -03:00
Your Name b525fdaeaf changes 2026-04-11 19:26:23 -03:00
Your Name 2549419ef1 fix 2026-04-11 18:30:19 -03:00
Your Name 73090ddaa1 changes 2026-04-11 18:12:42 -03:00
30 changed files with 427 additions and 293 deletions
+2
View File
@@ -1,2 +1,4 @@
alpine.qcow2
tmp/ tmp/
exchange/ exchange/
backup/
+3 -1
View File
@@ -15,7 +15,9 @@ RUN apk add --no-cache \
e2fsprogs \ e2fsprogs \
screen \ screen \
rsync \ rsync \
device-mapper sshfs \
device-mapper \
borgbackup
WORKDIR /root WORKDIR /root
+5
View File
@@ -29,6 +29,11 @@ Host meppel
User 4server User 4server
IdentityFile /app/host_vars/meppel/meppel IdentityFile /app/host_vars/meppel/meppel
Host sydney
Hostname 192.168.9.22
User 4server
IdentityFile /app/host_vars/sydney/sydney
Host saopaulo Host saopaulo
Hostname 192.168.9.11 Hostname 192.168.9.11
User 4server User 4server
+1
View File
@@ -13,4 +13,5 @@
192.168.9.11 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDc6J024++J25z2ZMXQ3Vim+mLVIVs+cZngv8DmtEDfrT/Ptl7+F2IqXRNcZuq1YTjEZO8eQg27iIemPoPT3xa8= 192.168.9.11 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDc6J024++J25z2ZMXQ3Vim+mLVIVs+cZngv8DmtEDfrT/Ptl7+F2IqXRNcZuq1YTjEZO8eQg27iIemPoPT3xa8=
192.168.9.21 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMFuf87ngDaF8D0kzVzzB953ji6ptg/V9t+WPac+DAjO 192.168.9.21 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMFuf87ngDaF8D0kzVzzB953ji6ptg/V9t+WPac+DAjO
192.168.9.21 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtexcutExMflSRPJOwGPuZx49c+ZUMoElNPNntXONOuVZ8jM+zzA/4s+HCj+RWdHMnarLcNJjwRZRq7JWruqmr1EN9eNmvu13cLCKZr6Ape1gt8VmMhvQ9nA7eMx3UdvnWgXjLCfJy+BKh2XVAneM5lOSPMcyQlcACrLQVkse9EKhxhV5nEjXKYxSNetIPK9PxglXznpC8IpuWlXnDH2R8vDhdvBmBFTKOjN5Wa+GrMfElWeQOjyCpNcMVYohvV87VN6FxHQTADFTm2CKy/W7pnM8rI55ZbBIMfvLyhpM+vtFh1sJlIZSFnn+ytMUImNEAgBDI3fCpwZ99zeLViGhQJlBkdCUI+JJK9XJjpcOMNydeWh142RGU6juPuSLOZPqKNc3BpZYgPY0vMDgDWgT9AcYup3rAJ/UnUnhsxiT2h/gx7+t/j9xg01BQF5S6mezaueuavHCh9MvAzXiJXR8zanhl/Hh9BKgIAFrZh71CzP/PYG33BKCe9DXA09aJrf8= 192.168.9.21 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtexcutExMflSRPJOwGPuZx49c+ZUMoElNPNntXONOuVZ8jM+zzA/4s+HCj+RWdHMnarLcNJjwRZRq7JWruqmr1EN9eNmvu13cLCKZr6Ape1gt8VmMhvQ9nA7eMx3UdvnWgXjLCfJy+BKh2XVAneM5lOSPMcyQlcACrLQVkse9EKhxhV5nEjXKYxSNetIPK9PxglXznpC8IpuWlXnDH2R8vDhdvBmBFTKOjN5Wa+GrMfElWeQOjyCpNcMVYohvV87VN6FxHQTADFTm2CKy/W7pnM8rI55ZbBIMfvLyhpM+vtFh1sJlIZSFnn+ytMUImNEAgBDI3fCpwZ99zeLViGhQJlBkdCUI+JJK9XJjpcOMNydeWh142RGU6juPuSLOZPqKNc3BpZYgPY0vMDgDWgT9AcYup3rAJ/UnUnhsxiT2h/gx7+t/j9xg01BQF5S6mezaueuavHCh9MvAzXiJXR8zanhl/Hh9BKgIAFrZh71CzP/PYG33BKCe9DXA09aJrf8=
192.168.9.22 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMO5VhveQe76kEOdVqrdoTwW1fNzmgDzZNBznFEC4ohtXes0VMtJR02lhc+55XTIUX+EDxBtAh8U+kJpFeLetMU=
192.168.9.21 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGlzLZWNjSaDuDd4e66LoBizY5j+QCsifIIxkvX4CrzP/AqAgWDEEgT+pAXBFkNJlBR6TDFJ0bIdwZTbcq8/72E= 192.168.9.21 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGlzLZWNjSaDuDd4e66LoBizY5j+QCsifIIxkvX4CrzP/AqAgWDEEgT+pAXBFkNJlBR6TDFJ0bIdwZTbcq8/72E=
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
set -e
export BORG_REPO="ssh://e7e9h45y@e7e9h45y.repo.borgbase.com/./repo"
export BORG_RSH="ssh -i ~/.ssh/borg-backup"
echo "Applying retention policy..."
borg prune \
--keep-daily=7 \
--keep-weekly=8 \
--keep-monthly=12 \
--stats
echo "Done: pruning complete"
Regular → Executable
View File
+5 -5
View File
@@ -13,7 +13,7 @@ source /4server/sbin/helpers
get_contract_info get_contract_info
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') #export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
echo "PASSWORD $ODOO_DB_PASSWORD" echo "PASSWORD $ODOO_DB_PASSWORD"
@@ -49,7 +49,7 @@ PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-c "ALTER ROLE \"$UUID\" CREATEDB;" -c "ALTER ROLE \"$UUID\" CREATEDB;"
doas docker exec "$UUID" rm -rf /home/odoo/.local/share/Odoo/filestore 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" 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 doas docker exec "$UUID" odoo db --db_host beedb -w "$ODOO_DB_PASSWORD" -r "$UUID" load "$UUID" $FILENAME -f
@@ -59,10 +59,10 @@ PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-c "ALTER ROLE \"$UUID\" NOCREATEDB;" -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" 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" chown -R odoo:odoo /home/odoo/.local
doas docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo/ 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 doas docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore
docker restart "$UUID" docker restart "$UUID"
+1 -2
View File
@@ -13,11 +13,10 @@ source /4server/sbin/helpers
get_contract_info get_contract_info
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') #export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
echo "PASSWORD $ODOO_DB_PASSWORD" echo "PASSWORD $ODOO_DB_PASSWORD"
echo "POSTGRES HOST: $POSTGRES_HOST" 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" doas docker exec -it "$UUID" odoo shell --db_host beedb --db_password="$ODOO_DB_PASSWORD" -d "$UUID" --db_user="$UUID"
+190 -83
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,57 @@ 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
secret_list: Optional[str] = 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 +184,43 @@ 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, secret_list, 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.secret_list,
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 +234,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 +242,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 +256,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 +272,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 +284,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 +312,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 +332,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 +343,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 +360,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,12 +371,13 @@ 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")
if not CPU_LOG_PATH.exists(): if not CPU_LOG_PATH.exists():
raise HTTPException(status_code=404, detail="CPU log file not found") raise HTTPException(status_code=404, detail="CPU log file not found")
try: try:
with CPU_LOG_PATH.open("r") as f: with CPU_LOG_PATH.open("r") as f:
content = f.read() content = f.read()
@@ -307,12 +385,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 +409,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 +427,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:
@@ -359,7 +451,7 @@ def git_tool(request: CommandRequest):
def add_git_key(request: AddKeyRequest): def add_git_key(request: AddKeyRequest):
if not request.key or not request.uuid: if not request.key or not request.uuid:
raise HTTPException(status_code=400, detail="Key and uuid are required") raise HTTPException(status_code=400, detail="Key and uuid are required")
file_path = f"/4server/data/{request.uuid}/git-server/keys/id_rsa.pub" file_path = f"/4server/data/{request.uuid}/git-server/keys/id_rsa.pub"
try: try:
@@ -370,7 +462,7 @@ def add_git_key(request: AddKeyRequest):
f.write(request.key) f.write(request.key)
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to save key: {str(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.uuid}", "file": file_path}
@@ -382,7 +474,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 +491,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 +519,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,30 +534,42 @@ 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):
if not request.filename: if not request.filename:
raise HTTPException(status_code=400, detail="Filename is required") raise HTTPException(status_code=400, detail="Filename is required")
command = [f"{BIN_PATH}/ODOO_19/import", request.filename] command = [f"{BIN_PATH}/ODOO_19/import", request.filename]
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")
# Use shell command to move the file # Use shell command to move the file
command = ["mv", request.source, request.destination] # Linux/macOS command = ["mv", request.source, request.destination] # Linux/macOS
# 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,
}
@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 ---------------------- # ---------------------- Entry Point ----------------------
@@ -469,5 +578,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)
-48
View File
@@ -1,48 +0,0 @@
#!/bin/bash
# Backup Odoo database script
# Author: Your Name
# Description: Dumps Odoo DB, manages backups, and sets permissions
set -euo pipefail # Fail on error, undefined variables, and pipe errors
# Load helper functions
source /4server/sbin/helpers
# Get contract info
get_contract_info
# Export Odoo database password
export ODOO_DB_PASSWORD
ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
# Display basic info
echo "UUID: $UUID"
echo "Backup slots: $BACKUP_SLOTS"
# Create backup filename
FILENAME="$(date +"%Y%m%d_%H%M").zip"
BACKUP_DIR="/BACKUP/$UUID"
# Ensure backup directory exists
mkdir -p "$BACKUP_DIR"
# Perform database dump using docker
doas docker exec "$UUID" odoo db \
--db_host beedb \
-r "$UUID" \
-w "$ODOO_DB_PASSWORD" \
--data-dir /home/odoo/.local/share/Odoo/ \
dump "$UUID" "/mnt/backup/$FILENAME"
# Set permissions for backup files
doas chmod 600 "$BACKUP_DIR"/*
doas docker exec "$UUID" chown odoo:odoo -R /mnt/backup
# 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
echo "Deleting old backup: $file"
doas rm -f "$file"
done
-48
View File
@@ -1,48 +0,0 @@
#!/bin/bash
# Backup Odoo database script
# Author: Your Name
# Description: Dumps Odoo DB, manages backups, and sets permissions
set -euo pipefail # Fail on error, undefined variables, and pipe errors
# Load helper functions
source /4server/sbin/helpers
# Get contract info
get_contract_info
# Export Odoo database password
export ODOO_DB_PASSWORD
ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
# Display basic info
echo "UUID: $UUID"
echo "Backup slots: $BACKUP_SLOTS"
# Create backup filename
FILENAME="$(date +"%Y%m%d_%H%M").zip"
BACKUP_DIR="/BACKUP/$UUID"
# Ensure backup directory exists
mkdir -p "$BACKUP_DIR"
# Perform database dump using docker
doas docker exec "$UUID" odoo db \
--db_host beedb \
-r "$UUID" \
-w "$ODOO_DB_PASSWORD" \
--data-dir /home/odoo/.local/share/Odoo/ \
dump "$UUID" "/mnt/backup/$FILENAME"
# Set permissions for backup files
doas chmod 600 "$BACKUP_DIR"/*
doas docker exec "$UUID" chown odoo:odoo -R /mnt/backup
# 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
echo "Deleting old backup: $file"
doas rm -f "$file"
done
-48
View File
@@ -1,48 +0,0 @@
#!/bin/bash
# Backup Odoo database script
# Author: Your Name
# Description: Dumps Odoo DB, manages backups, and sets permissions
set -euo pipefail # Fail on error, undefined variables, and pipe errors
# Load helper functions
source /4server/sbin/helpers
# Get contract info
get_contract_info
# Export Odoo database password
export ODOO_DB_PASSWORD
ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
# Display basic info
echo "UUID: $UUID"
echo "Backup slots: $BACKUP_SLOTS"
# Create backup filename
FILENAME="$(date +"%Y%m%d_%H%M").zip"
BACKUP_DIR="/BACKUP/$UUID"
# Ensure backup directory exists
mkdir -p "$BACKUP_DIR"
# Perform database dump using docker
doas docker exec "$UUID" odoo db \
--db_host beedb \
-r "$UUID" \
-w "$ODOO_DB_PASSWORD" \
--data-dir /home/odoo/.local/share/Odoo/ \
dump "$UUID" "/mnt/backup/$FILENAME"
# Set permissions for backup files
doas chmod 600 "$BACKUP_DIR"/*
doas docker exec "$UUID" chown odoo:odoo -R /mnt/backup
# 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
echo "Deleting old backup: $file"
doas rm -f "$file"
done
+3 -3
View File
@@ -26,13 +26,13 @@ case "$SECOND_PART" in
"$BIN_PATH/backup/N8N" "$BIN_PATH/backup/N8N"
;; ;;
002) 002)
"$BIN_PATH/backup/ODOO_18" "$BIN_PATH/backup/ODOO"
;; ;;
003) 003)
"$BIN_PATH/backup/ODOO_19" "$BIN_PATH/backup/ODOO"
;; ;;
004) 004)
"$BIN_PATH/backup/ODOO_17" "$BIN_PATH/backup/ODOO"
;; ;;
*) *)
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
# ssh -i ~/.ssh/borg-backup e7e9h45y@e7e9h45y.repo.borgbase.com
set -e
export BORG_REPO="ssh://e7e9h45y@e7e9h45y.repo.borgbase.com/./repo"
export BORG_RSH="ssh -i ~/.ssh/borg-backup"
export BORG_PASSPHRASE="Airbus12"
HOSTNAME=$(hostname)
DATE=$(date +%Y-%m-%d_%H-%M-%S)
ARCHIVE_NAME="${HOSTNAME}-${DATE}"
# Resolve symlinks externally and feed real paths to borg via --paths-from-stdin.
# - First find: locates all current* symlinks under /BACKUP and resolves them with realpath.
# - Second find: locates all regular current* files under /BACKUP (non-symlinks).
# - sort -u: deduplicates in case a resolved symlink target overlaps with a regular file.
{
find /BACKUP -name 'current*' -type l -print0 | xargs -0 -I {} realpath {}
find /BACKUP -name 'current*' ! -type l
} | sort -u | borg create \
--verbose \
--stats \
--compression lz4 \
--paths-from-stdin \
::$ARCHIVE_NAME
echo "Backup completed: $ARCHIVE_NAME"
+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
+2 -4
View File
@@ -12,7 +12,7 @@ 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}"
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')
BASEURL="${BASEURL:-/4server/data/$UUID}" BASEURL="${BASEURL:-/4server/data/$UUID}"
BACKUPURL="/4backup/$UUID" BACKUPURL="/4backup/$UUID"
@@ -24,7 +24,7 @@ if [ -n "${UUID:-}" ]; then
echo "Removing directory: $BASEURL" echo "Removing directory: $BASEURL"
#doas rm -rf "$BASEURL" #doas rm -rf "$BASEURL"
echo "Removing backup directory $BACKUPURL" echo "Removing backup directory $BACKUPURL"
#doas rm -rf $BACKUPURL #doas rm -rf $BACKUPURL
fi fi
@@ -52,5 +52,3 @@ END
EOF EOF
echo "✅ Database '$UUID' and user '$ODOO_DB_USER' removed (if they existed)." echo "✅ Database '$UUID' and user '$ODOO_DB_USER' removed (if they existed)."
+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"'
+6 -6
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" \
@@ -114,12 +114,12 @@ docker run -d --name "$UUID" \
docker.odoo4projects.com/4projects/odoo_17:$BRANCH docker.odoo4projects.com/4projects/odoo_17:$BRANCH
docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo 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" 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" 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 /home/odoo/.local
docker exec $UUID chown -R odoo:odoo /var/lib/odoo/.local/share/Odoo 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 -R odoo:odoo /mnt/*
docker exec $UUID chown odoo:odoo /git-server/keys/id_rsa.pub docker exec $UUID chown odoo:odoo /git-server/keys/id_rsa.pub
+6 -6
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" \
@@ -114,12 +114,12 @@ docker run -d --name "$UUID" \
docker.odoo4projects.com/4projects/odoo_18:$BRANCH docker.odoo4projects.com/4projects/odoo_18:$BRANCH
docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo 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" 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" 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 /home/odoo/.local
docker exec $UUID chown -R odoo:odoo /var/lib/odoo/.local/share/Odoo 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 -R odoo:odoo /mnt/*
docker exec $UUID chown odoo:odoo /git-server/keys/id_rsa.pub docker exec $UUID chown odoo:odoo /git-server/keys/id_rsa.pub
+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
+1 -2
View File
@@ -1,7 +1,6 @@
# ~/.bashrc # ~/.bashrc
clear clear
echo "Server {{HOSTNAME}}" echo "Server {{HOSTNAME}} IP: $(ip route get 1.1.1.1 | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}')"
export PS1="\[\e[32m\]\h:\w\$\[\e[0m\] " export PS1="\[\e[32m\]\h:\w\$\[\e[0m\] "
df -h . df -h .
+3 -4
View File
@@ -23,9 +23,9 @@ rex doas chown 4server:4server /4server/tmp
template templates/.profile /home/4server/.profile template templates/.profile /home/4server/.profile
### PACKAGES ### PACKAGES
template templates/repositories /etc/apk/repositories template templates/repositories /etc/apk/repositories
rex "doas apk update && doas apk upgrade" rex "doas apk update && doas apk upgrade"
rex doas apk add iperf linux-lts openssh ufw python3 build-base python3-dev linux-headers py3-pip gcc g++ musl-dev libffi-dev make jq rsync mc vim docker docker-compose htop linux-lts sqlite bash postgresql16-client rex doas apk add sshfs borgbackup iperf linux-lts openssh ufw python3 build-base python3-dev linux-headers py3-pip gcc g++ musl-dev libffi-dev make jq rsync mc vim docker docker-compose htop linux-lts sqlite bash postgresql16-client
rex doas pip install --root-user-action ignore --break-system-packages --no-cache-dir "uvicorn[standard]" fastapi pydantic psutil gdown rex doas pip install --root-user-action ignore --break-system-packages --no-cache-dir "uvicorn[standard]" fastapi pydantic psutil gdown
@@ -92,6 +92,5 @@ rex mkdir -p /4server/data/traefik/etc/certs
echo "prsync traefik certs" echo "prsync traefik certs"
prsync -h "/app/host_vars/hosts" -avz ./etc/traefik/certs/* /4server/data/traefik/etc/certs/ prsync -h "/app/host_vars/hosts" -avz ./etc/traefik/certs/* /4server/data/traefik/etc/certs/
template templates/docker-compose.yml /4server/docker-compose.yml template templates/docker-compose.yml /4server/docker-compose.yml
rex doas docker-compose -f /4server/docker-compose.yml up -d --force-recreate rex doas docker-compose -f /4server/docker-compose.yml up -d --force-recreate
+4
View File
@@ -2,8 +2,12 @@
echo "Running prsync ./sbin" echo "Running prsync ./sbin"
prsync -h "/app/host_vars/hosts" -avz ./sbin/ /4server/sbin/ prsync -h "/app/host_vars/hosts" -avz ./sbin/ /4server/sbin/
template templates/.profile /home/4server/.profile
rex doas rc-service api restart rex doas rc-service api restart
rex doas rc-service checkCalls restart rex doas rc-service checkCalls restart
prsync -h "/app/host_vars/hosts" -avz ./host_vars/borg-backup/* /home/4server/.ssh
rex doas chown 4server:4server /home/4server/.ssh/borg*
rex doas chmod 600 /home/4server/.ssh/borg*
Binary file not shown.
Executable
+2
View File
@@ -0,0 +1,2 @@
sudo borg mount ssh://e7e9h45y@e7e9h45y.repo.borgbase.com/./repo /mnt