From 9a59b2932afd5cb28d7dcc0e3e9d3954e00f6068 Mon Sep 17 00:00:00 2001 From: oliver Date: Sun, 27 Sep 2026 10:39:39 -0300 Subject: [PATCH] c --- app/sbin/ODOO/audit | 3 + app/sbin/ODOO/dbVersion | 47 ++++++++++++++++ app/sbin/ODOO/import | 31 +++++++++++ app/sbin/ODOO/shell | 11 ++++ app/sbin/ODOO/updateModules | 31 +++++++++++ app/sbin/initContainer | 49 +++++++++++++++++ app/sbin/nuke/odoo | 54 ++++++++++++++++++ app/sbin/odoo.lib | 107 ++++++++++++++++++++++++++++++++++++ app/sbin/odoo/restore | 74 +++++++++++++++++++++++++ app/sbin/start/odoo | 92 +++++++++++++++++++++++++++++++ 10 files changed, 499 insertions(+) create mode 100755 app/sbin/ODOO/audit create mode 100755 app/sbin/ODOO/dbVersion create mode 100755 app/sbin/ODOO/import create mode 100755 app/sbin/ODOO/shell create mode 100755 app/sbin/ODOO/updateModules create mode 100755 app/sbin/initContainer create mode 100755 app/sbin/nuke/odoo create mode 100755 app/sbin/odoo.lib create mode 100755 app/sbin/odoo/restore create mode 100755 app/sbin/start/odoo diff --git a/app/sbin/ODOO/audit b/app/sbin/ODOO/audit new file mode 100755 index 0000000..cb4b3e5 --- /dev/null +++ b/app/sbin/ODOO/audit @@ -0,0 +1,3 @@ +#!/bin/bash + +"$(dirname "$(realpath "${BASH_SOURCE[0]:-$0}")")/listModules" "$1" diff --git a/app/sbin/ODOO/dbVersion b/app/sbin/ODOO/dbVersion new file mode 100755 index 0000000..ca12723 --- /dev/null +++ b/app/sbin/ODOO/dbVersion @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +import csv +import sys +import zipfile + +if len(sys.argv) < 2: + print("Usage: python3 check_odoo_version.py ") + sys.exit(1) + +zip_path = sys.argv[1] +base_version = None + +with zipfile.ZipFile(zip_path, 'r') as z: + # Assume there is only one .sql file in the zip + sql_files = [f for f in z.namelist() if f.endswith('.sql')] + if not sql_files: + print("No .sql file found in the zip.") + sys.exit(1) + + sql_file_name = sql_files[0] + + with z.open(sql_file_name, 'r') as f: + # Decode bytes to string + lines = (line.decode('utf-8') for line in f) + + # Skip lines until COPY command + for line in lines: + if line.startswith("COPY public.ir_module_module"): + break + + # Read the COPY data until the terminator '\.' + reader = csv.reader(lines, delimiter='\t', quotechar='"', escapechar='\\') + for row in reader: + if row == ['\\.']: # End of COPY + break + if len(row) < 12: + continue + module_name = row[7].strip() # 8th column = name + if module_name == "base": + base_version = row[11].strip() # 12th column = latest_version + break + +if base_version: + print(base_version.split(".")[0]) +else: + print("Base module not found in dump.") + diff --git a/app/sbin/ODOO/import b/app/sbin/ODOO/import new file mode 100755 index 0000000..3169d94 --- /dev/null +++ b/app/sbin/ODOO/import @@ -0,0 +1,31 @@ +#!/bin/bash + +# Create the tmp directory if it doesn't exist +mkdir -p /4server/tmp/ + +# Save original stdout +exec 3>&1 + +# Redirect all other output to log +exec > /4server/data/log/importDb.log 2>&1 +echo "$(date '+%Y-%m-%d %H:%M') Import file $1" + +# Generate random 8-digit filename +RANDOM_FILE="/4server/tmp/import_$(date '+%Y%m%d_%H%M').zip" + +# Download file from Google Drive using gdown +gdown "$1" -O "$RANDOM_FILE" + +# Execute dbVersion on the downloaded file and capture output +VERSION=$("$(dirname "$(realpath "${BASH_SOURCE[0]:-$0}")")/dbVersion" "$RANDOM_FILE") + +# Output JSON to original stdout +cat <&3 +{ + "version":"$VERSION", + "file":"$RANDOM_FILE" +} +EOF + +# Close saved stdout +exec 3>&- diff --git a/app/sbin/ODOO/shell b/app/sbin/ODOO/shell new file mode 100755 index 0000000..af8659e --- /dev/null +++ b/app/sbin/ODOO/shell @@ -0,0 +1,11 @@ +#!/bin/bash +export PATH=/4PROJECTS/bin:$PATH +CONTAINER_ID="$1" +_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of ODOO/ +source "$SBIN_ROOT/helpers" +[ -z "$CONTAINER_ID" ] && die "Usage: $0 " +load_container_config "$CONTAINER_ID" +get_contract_info +DB_NAME="${DB_NAME:-$CONTAINER_ID}" +log "shell: CONTAINER_ID=$CONTAINER_ID DB_NAME=$DB_NAME" +doas docker exec -it "$CONTAINER_ID" odoo shell --db_host beedb --db_password="$ODOO_DB_PASSWORD" -d "$DB_NAME" --db_user="$DB_NAME" diff --git a/app/sbin/ODOO/updateModules b/app/sbin/ODOO/updateModules new file mode 100755 index 0000000..be0a6f1 --- /dev/null +++ b/app/sbin/ODOO/updateModules @@ -0,0 +1,31 @@ +#!/bin/bash +export PATH=/4PROJECTS/bin:$PATH + +# -------------------------------------------------- +# Validate parameter +# -------------------------------------------------- +CONTAINER_ID="$1" +_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of ODOO/ +source "$SBIN_ROOT/helpers" +[ -z "$CONTAINER_ID" ] && die "Usage: $0 " +load_container_config "$CONTAINER_ID" +get_contract_info +DB_NAME="${DB_NAME:-$CONTAINER_ID}" + +doas docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_ID}$" \ + || die "Container $CONTAINER_ID does not exist" + +log "Updating all Odoo modules: DB=$DB_NAME host=$POSTGRES_HOST" + +doas docker exec -u odoo "$CONTAINER_ID" odoo \ + -u all \ + -d "$DB_NAME" \ + --stop-after-init \ + --db_host="$POSTGRES_HOST" \ + --db_port="$POSTGRES_PORT" \ + --db_user="$POSTGRES_ADMIN_USER" \ + --db_password="$POSTGRES_ADMIN_PASSWORD" \ + || die "Odoo module update failed for $CONTAINER_ID" + +log "Module update complete — restarting $CONTAINER_ID" +"$SBIN_ROOT/startContainer" "$CONTAINER_ID" diff --git a/app/sbin/initContainer b/app/sbin/initContainer new file mode 100755 index 0000000..70cf425 --- /dev/null +++ b/app/sbin/initContainer @@ -0,0 +1,49 @@ +#!/bin/bash +# Usage: initContainer [db_name] +# +# Creates /4server/data//config/container.env +# +# : new codename (e.g. "banana") +# : n8n | ODOO_17 | ODOO_18 | ODOO_19 +# [db_name] : optional — postgres DB/user name to use. +# Defaults to . +# Pass the OLD container_id here when migrating an existing container +# so the existing postgres database is reused. +# +# Examples: +# New container: +# initContainer banana ODOO_19 +# +# Migrating existing container (old container_id kept as DB name): +# initContainer banana ODOO_19 005-003-91f3922c-8cc2-4950-aa57-808775c341ff + +CONTAINER_ID="$1" +SOFTWARE="$2" +DB_NAME="${3:-$1}" + +if [[ -z "$CONTAINER_ID" || -z "$SOFTWARE" ]]; then + echo "Usage: $0 [db_name]" + echo " software: n8n | ODOO_17 | ODOO_18 | ODOO_19" + exit 1 +fi + +case "$SOFTWARE" in + n8n|ODOO_17|ODOO_18|ODOO_19) ;; + *) + echo "ERROR: Unknown software type: $SOFTWARE" + echo " Valid values: n8n | ODOO_17 | ODOO_18 | ODOO_19" + exit 1 + ;; +esac + +CONFIG_DIR="/4server/data/$CONTAINER_ID/config" +mkdir -p "$CONFIG_DIR" + +cat > "$CONFIG_DIR/container.env" </dev/null | awk '/^Address: / { print $2 }' | head -n1 || true) + if [ -z "$resolved_ip" ]; then + warn "build_traefik_rule: could not resolve $d, skipping" + continue + fi + if [ "$resolved_ip" = "$server_ip" ]; then + rule+=" || Host(\`$d\`)" + else + warn "build_traefik_rule: $d resolves to $resolved_ip, not $server_ip — skipping" + fi + done + + rule="${rule# || }" + if [ -z "$rule" ]; then + die "build_traefik_rule: no valid domains resolved for $CONTAINER_ID — cannot start container" + fi + + DOMAIN_LABEL="traefik.http.routers.$CONTAINER_ID.rule=$rule" + log "Traefik rule: $DOMAIN_LABEL" +} + +# --------------------------------------------------------------------------- +# check_and_create_db +# Creates the postgres user and database if they don't exist yet. +# On a brand-new container this triggers a restore from default.zip. +# Requires: DB_NAME, ODOO_DB_USER, ODOO_DB_PASSWORD, POSTGRES_* from env. +# --------------------------------------------------------------------------- +check_and_create_db() { + local _dbname="${DB_NAME:-$CONTAINER_ID}" + local _dbuser="${ODOO_DB_USER:-$_dbname}" + + log "check_and_create_db: user=$_dbuser db=$_dbname host=$POSTGRES_HOST" + + # --- user --- + local _user_result + _user_result=$(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='$_dbuser';" 2>&1) \ + || die "check_and_create_db: psql connection failed: $_user_result" + + if ! echo "$_user_result" | grep -q 1; then + log "Creating PostgreSQL user $_dbuser" + PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \ + -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" \ + -d postgres -c "CREATE USER \"$_dbuser\" WITH PASSWORD '$ODOO_DB_PASSWORD';" \ + || die "check_and_create_db: failed to create user $_dbuser" + fi + + # --- database --- + local _db_result + _db_result=$(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='$_dbname';" 2>&1) \ + || die "check_and_create_db: psql connection failed: $_db_result" + + if ! echo "$_db_result" | grep -q 1; then + log "Database $_dbname not found — restoring from default.zip" + "$SBIN_ROOT/odoo/restore" "$CONTAINER_ID" default.zip \ + || die "check_and_create_db: restore failed for $_dbname" + fi +} diff --git a/app/sbin/odoo/restore b/app/sbin/odoo/restore new file mode 100755 index 0000000..8d9d426 --- /dev/null +++ b/app/sbin/odoo/restore @@ -0,0 +1,74 @@ +#!/bin/bash +# Usage: odoo/restore +# Restores an Odoo database from a backup zip inside the container. + +if [ -z "$2" ]; then + echo "Usage: $0 " + exit 1 +fi + +CONTAINER_ID="$1" + +_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of odoo/ +source "$SBIN_ROOT/helpers" + +load_container_config "$CONTAINER_ID" +get_contract_info + +DB_NAME="${DB_NAME:-$CONTAINER_ID}" + +log "Restoring $2 into CONTAINER_ID=$CONTAINER_ID DB_NAME=$DB_NAME" +log "Postgres host: $POSTGRES_HOST" + +BACKUP="/mnt/backup/$2" +TEMPLATE="/mnt/db_images/$2" + +if doas docker exec "$CONTAINER_ID" /bin/bash -c "[ -f $BACKUP ]"; then + FILENAME="$BACKUP" +elif doas docker exec "$CONTAINER_ID" /bin/bash -c "[ -f $TEMPLATE ]"; then + FILENAME="$TEMPLATE" +else + die "restore: file $2 not found at $BACKUP or $TEMPLATE inside container $CONTAINER_ID" +fi + +log "Using file: $FILENAME" + +# Terminate active connections then drop/recreate +PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \ + -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -d postgres -c " + SELECT pg_terminate_backend(pid) FROM pg_stat_activity + WHERE datname = '$DB_NAME' AND pid <> pg_backend_pid();" \ + || die "restore: failed to terminate connections on $DB_NAME" + +PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \ + -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -d postgres \ + -c "DROP DATABASE IF EXISTS \"$DB_NAME\";" \ + || die "restore: failed to drop database $DB_NAME" + +PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \ + -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \ + -c "ALTER ROLE \"$DB_NAME\" CREATEDB;" \ + || die "restore: failed to grant CREATEDB on $DB_NAME" + +doas docker exec "$CONTAINER_ID" rm -rf /home/odoo/.local/share/Odoo/filestore +doas docker exec "$CONTAINER_ID" rm -rf /root/.local/share/Odoo/filestore + +doas docker exec "$CONTAINER_ID" odoo db \ + --db_host beedb \ + -w "$ODOO_DB_PASSWORD" \ + -r "$DB_NAME" \ + load "$DB_NAME" "$FILENAME" -f \ + || die "restore: odoo db load failed for $DB_NAME" + +PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \ + -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \ + -c "ALTER ROLE \"$DB_NAME\" NOCREATEDB;" \ + || die "restore: failed to revoke CREATEDB on $DB_NAME" + +doas docker exec "$CONTAINER_ID" cp -r /root/.local/share/Odoo/filestore /home/odoo/.local/share/Odoo/filestore +doas docker exec "$CONTAINER_ID" chown -R odoo:odoo /home/odoo/.local +doas docker exec "$CONTAINER_ID" mkdir -p /var/lib/odoo/.local/share/Odoo/ +doas docker exec "$CONTAINER_ID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore + +log "Restore complete, restarting container $CONTAINER_ID" +docker restart "$CONTAINER_ID" diff --git a/app/sbin/start/odoo b/app/sbin/start/odoo new file mode 100755 index 0000000..8d73a13 --- /dev/null +++ b/app/sbin/start/odoo @@ -0,0 +1,92 @@ +#!/bin/bash +# Unified Odoo start script for all versions (17 / 18 / 19). +# Called by startContainer with IMAGE_TAG exported, e.g.: +# IMAGE_TAG=odoo_19 /4server/sbin/start/odoo +# +# Requires (exported by startContainer before this is called): +# CONTAINER_ID, IMAGE_TAG, SOFTWARE, DB_NAME + +_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of start/ +source "$SBIN_ROOT/helpers" +source "$SBIN_ROOT/odoo.lib" +load_container_config "$CONTAINER_ID" +get_contract_info + +BRANCH="${BRANCH:-release}" +DB_NAME="${DB_NAME:-$CONTAINER_ID}" +ODOO_DB_USER="$DB_NAME" + +log "Starting Odoo container: CONTAINER_ID=$CONTAINER_ID IMAGE_TAG=$IMAGE_TAG DB_NAME=$DB_NAME" + +BASEURL="/4server/data/$CONTAINER_ID" +DATA_DIR="$BASEURL/odoo" +CUSTOM_DIR="$BASEURL/git/$CONTAINER_ID/custom" +ENTERPRISE_DIR="$BASEURL/git/$CONTAINER_ID/enterprise" +LOGS_DIR="$BASEURL/logs" +CONFIG_DIR="$BASEURL/config" +CC_DIR="$BASEURL/cc" +BACKUP_DIR="/BACKUP/$CONTAINER_ID" +GIT_DIR="$BASEURL/git-server" +INSTALL_DIR="$BASEURL/install" +SSH_DIR="$BASEURL/.ssh" +ETC_DIR="$BASEURL/etc" + +PORT=$((CONTAINERDBID + 2200)) +log "SSH port: $PORT" + +# --- prepare directories --- +find "$BASEURL" -type d -exec chmod 777 {} \; +mkdir -p "$ETC_DIR" "$BACKUP_DIR" "$GIT_DIR/keys" "$INSTALL_DIR" "$SSH_DIR" +chmod 777 "$ETC_DIR" "$BACKUP_DIR" "$GIT_DIR" +touch "$GIT_DIR/keys/id_rsa.pub" +echo "git clone \"ssh://git@${CONTAINER_ID}.odoo4projects.com:${PORT}/git-server/repos/odoo.git\"" > "$ETC_DIR/gitpath" + +# --- build Traefik domain rule (exports DOMAIN_LABEL) --- +build_traefik_rule + +# --- stop and remove any existing container --- +docker stop "$CONTAINER_ID" 2>/dev/null || true +docker rm "$CONTAINER_ID" 2>/dev/null || true + +# --- run --- +docker run -d --name "$CONTAINER_ID" \ + --network 4server_4projects \ + --restart=always \ + -v "$DATA_DIR/odoo-web-data:/home/odoo/.local/share/Odoo" \ + -v "$CUSTOM_DIR:/mnt/addons/custom" \ + -v "$ENTERPRISE_DIR:/mnt/addons/enterprise" \ + -v "$LOGS_DIR:/mnt/logs" \ + -v "$CC_DIR:/mnt/cc" \ + -v "$BACKUP_DIR:/mnt/backup" \ + -v "$GIT_DIR:/git-server" \ + -v "$INSTALL_DIR:/mnt/install" \ + -v "$SSH_DIR:/etc/sshkey" \ + -v "$ETC_DIR:/mnt/etc" \ + -p "$PORT:22" \ + -e HOST="beedb" \ + -e USER="$ODOO_DB_USER" \ + -e PASSWORD="$ODOO_DB_PASSWORD" \ + -e CONTAINER_ID="$CONTAINER_ID" \ + -e DB_NAME="$DB_NAME" \ + -e HDD="$HDD" \ + -e DOMAINS_SLOTS="$DOMAINS_SLOTS" \ + -e BACKUP_SLOTS="$BACKUP_SLOTS" \ + -e WORKER="$WORKER" \ + -e GIT="$GIT" \ + --label "$DOMAIN_LABEL" \ + --label "traefik.http.services.$CONTAINER_ID.loadbalancer.server.port=8069" \ + --label "traefik.http.routers.$CONTAINER_ID.entrypoints=web,websecure" \ + --label "traefik.http.routers.$CONTAINER_ID.tls=true" \ + --label "traefik.http.routers.$CONTAINER_ID.tls.certresolver=production" \ + --label "traefik.http.routers.$CONTAINER_ID.service=$CONTAINER_ID" \ + "docker.odoo4projects.com/4projects/${IMAGE_TAG}:${BRANCH}" \ + || die "docker run failed for $CONTAINER_ID" + +# --- fix ownership inside container --- +docker exec "$CONTAINER_ID" chown -R odoo:odoo /home/odoo/.local +docker exec "$CONTAINER_ID" chown -R odoo:odoo /mnt +docker exec "$CONTAINER_ID" chown -R git:git /git-server +chmod 777 "$CC_DIR" + +# --- ensure DB/user exist, restore from default.zip if brand new --- +check_and_create_db