#!/bin/bash cd "$(dirname "$0")" || exit # Commands such as git pull return exit code if there are no changes # set -e if [[ -a ".env" ]]; then source .env fi function help-table() { # Define column widths cmd_width=25 opt_width=20 desc_width=90 # Print table header printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "Command" "Option" "Description" echo "|$(printf '%*s' $((cmd_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((opt_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((desc_width + 2)) '' | tr ' ' '-')|" # Print table rows printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "activate-venv" "" "Activate virtualenv." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "build" "[platform]" "Build Odoo Conainer image. Optionally define the target platform." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "change-uuid" "[env]" "Change database uuid via xmlrpc." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "checkout" "[branch]" "Checkout Odoo version / branch." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "checkout-config-files" "[branch]" "Checkout config files from another branch." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "checkout-revision" "[revision]" "Run Odoo revision script or checkout revision set by env var." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "clear-assets" "[db]" "Clear all assets of Odoo database." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "clear-views" "[db]" "Clear all views of Odoo database." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "clear-filestore" "[db]" "Clear local filestore folder. No param will clear all filestores. Default is branch name." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "convert-database" "[db]" "Convert database backup file from PostgreSQL 9.5 to latest. Default is branch name." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "create-module" "[path]" "Create new Odoo module from template." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "create-module-repo" "[path]" "Initialize Odoo module repo from template." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "create-revision" "[revision]" "Create new Odoo revision." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "create-nextcloud-env" "[env]" "Create env file for Nextcloud instance." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "create-odoo-env" "[env]" "Create env file for Odoo Instance." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "create-snippet" "[id]" "Create snippet from template." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "disable-mailserver" "[env]" "Disable mail server settings via xmlrpc." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "disable-snippet" "[env][path]" "Disable snippet definition." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "container-login" "" "Setup container hub login credentials." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "drop-db" "[db]" "Drop target Odoo database. Default is branch name." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "edit-env" "[env]" "Open env file in default editor." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "generate-admin-passwd" "[pass]" "Generate hash for Odoo master password." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "translate-module" "[env][path][lang]" "Generate translation for Odoo module." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "generate-help-doc" "" "Write help table to task.md file." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "generate-module-overview" "" "Update module overview doc file." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "generate-module-security" "[path][model]" "Generate model access file for a selected model of a module." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "generate-module-model" "[path][model]" "Generate model files from templates." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "generate-module-snippet" "[path][ref]" "Generate view snippet from template." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "generate-snippets-doc" "" "Update snippets doc file." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "generate-revisions-doc" "" "Update revisions doc file." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "get-addons-path" "" "Output addons path." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "get-module-version" "[path]" "Get module version from manifest." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "get-modules" "[path][option]" "Get list of modules in path. Option is 'basename' or 'dirname'." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-feature-branch" "[path]" "Create feature branch for Odoo module." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-fetch" "" "Fetch all Odoo version branches." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-mig-branch" "[path]" "Create migration branch for Odoo module." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-submodule-add" "[url] [path]" "Add git submodule." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-submodule-checkout" "" "Checkout submodule commits." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-submodule-ls" "" "List submodule path space separated." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-submodule-list" "" "List path and url of submodules." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-submodule-init" "" "Init submodules listed in the .gitmodules file." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-submodule-pull" "" "Pull all submodules listed in the .gitmodules file." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-submodule-remove" "[path]" "Remove a git submodule." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-submodule-delete" "" "Delete all submodule folders." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-submodule-switch" "[branch]" "Switch branch for all submodules listed in the .gitmodules file." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "git-submodule-sync" "" "Switch, stash and pull all submodules." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "import-csv" "[db][path]" "Import data from csv. Filename must match PostgreSQL table name." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "info" "" "Show values of project env vars." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "init-db" "[db]" "Initialize the Odoo database. Default is branch name." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "init-venv" "" "Initialize python virtual env." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "init-module" "[db][path,module]" "Initialize Odoo module. Default database is branch name." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "load-language" "[db][lang]" "Install language package in Odoo db." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "install-native" "" "Install Odoo requirements in source folder." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "install-odoo-scripts" "" "Install Odoo scripts." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "install-requirements" "[db][path]" "Install python packages from requirements.txt." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "install-snippet" "[env][path]" "Install snippet definition." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "k8s-apply" "" "Apply the Odoo K8s manifests." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "lint-module" "[path]" "Run pylint odoo for module." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "lint-module-repo" "[path]" "Run pylint odoo for modules in repo folder." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "lint-snippets" "" "Run checks for all snippets." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "list-modules" "[path]" "Get modules in path as list." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "list-env" "" "List env files." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "list-revision" "" "List available Odoo revisions." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "llm-update" "[path]" "Feed module files with prompt to LLM and apply updates with git path." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "logs" "" "Tail Odoo container logs." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "node-install" "" "Install build dependencies." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "node-dev" "" "Start vuepress development server." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "node-build" "" "Create vuepress build." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "node-serve-build" "" "Serve vuepress build." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "odoo-cloc" "[db]" "Count custom line of codes. Default is branch name." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "pass-store-dotenv" "" "Store content of .env in pass entry." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "pass-restore-dotenv" "" "Restore content of .env from pass entry." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "patch-database" "[db][path]" "Apply sql file to database. Default database is branch name." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "publish" "" "Publish Odoo container image." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "ps" "" "List container processes." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "pytest-module" "[db][name,path]" "Run module tests with pytest." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "release-module" "[path]" "Create GitHub release for a module." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "remove" "[name]" "Remove containers and volumes. Options: none, db, admin, odoo, mail." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "remove-env" "[env]" "Remove environment config." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "remove-module" "[db][name]" "Remove target Odoo module." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "remove-snippet" "[env][path]" "Remove snippet definition." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "reset-views" "[db][key]" "Execute hard reset on views matching keys." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "restart" "[name]" "Restart container." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "save-config" "[name]" "Save Odoo database config." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "setup-mail" "[db]" "Setup mail config in Odoo db." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "setup-mail-sql" "[db]" "Setup mail config in Odoo db with SQL." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "set-admin" "[db]" "Sets the password for the first user in database. Default is branch name." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "set-addons-path" "" "Set Odoo addons path env variable." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "source" "" "Source the Python virtual env." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "show-env" "[env]" "Output content of the env file." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "show-revision" "[revision]" "Show references of Odoo revision." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "start" "[name][db]" "Start container. Options: none, db, admin, odoo, native, mail." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "start-kind" "" "Start kind Kubernetes cluster." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "start-minikube" "" "Start minikube Kubernetes cluster." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "start-psql" "[db]" "Start PSQL shell in container container. Default is branch name." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "start-shell" "[db]" "Start Odoo shell from source folder. Default is branch name." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "stop" "[name]" "Stop containers." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "template-odoo-rc" "" "Template the Odoo config file." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "template-docker-compose" "" "Template the Docker compose file." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "test-container" "" "Test container environment." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "test-xmlrpc" "[env]" "Test json rpc connection." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "test-k8s" "" "Test Kubernetes environment." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "test-module" "[db][name,path]" "Test target Odoo module." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "test-module-repo" "[db][path]" "Test target Odoo modules in repo folder." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "update-app-list" "[db]" "Update app list." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "update-docs" "" "Update project docs." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "upgrade-module" "[path][version]" "Upgrade code of module from target Odoo version." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "upgrade-odoo" "[db][version][mode]" "Run the Odoo upgrade scripts. Default mode ist 'test'." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "update-module" "[db][name,path]" "Update target Odoo module." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "update-readme" "[path]" "Update Readme file of Odoo module collection." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "update-snippet" "[env][path]" "Update snippet definition." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "upload-module" "[env][path]" "Zip and upload Odoo module." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "visualize-dependencies" "[path]" "Generate visualizations of module dependencies." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "version" "" "Show version of required tools." printf "| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" "zip-module" "[path]" "Create zip file for module." } function help() { echo echo "$1 [options]" echo echo "commands:" echo help-table echo } # Static env vars PASS_ENTRY=env/Odoo-Build ODOO_RC="odoo.conf" ODOO_BUILD_CONFIG_DIR="$HOME/.config/odoo-build" # Dynamic env vars GIT_BRANCH=$(git symbolic-ref --short -q HEAD) ODOO_DATABASE=${ODOO_DATABASE:="$GIT_BRANCH"} ODOO_REVISION=${ODOO_REVISION:="$GIT_BRANCH"} ODOO_LANGUAGE=${ODOO_LANGUAGE:="de_CH"} ODOO_PORT=${ODOO_PORT:="8069"} POSTGRES_PORT=${POSTGRES_PORT:="5432"} LOG_LEVEL=${LOG_LEVEL:="info"} CONTAINER_REGISTRY=${CONTAINER_REGISTRY:="mintsystem/"} CONTAINER_TAG=${CONTAINER_TAG:="odoo:${ODOO_REVISION}"} CONTAINER_CONFIG="$HOME/.docker/$CONTAINER_REGISTRY" BROWSER_OPEN=${BROWSER_OPEN:=true} ODOO_REVISION_CONTAINER_PULL=${ODOO_REVISION_CONTAINER_PULL:=true} CONTAINER_ENGINE=${CONTAINER_ENGINE:="docker"} COMPOSE_COMMAND=${COMPOSE_COMMAND:="docker compose"} LLM_MODEL=${LLM_MODEL:="deepseek-coder"} # Conditional env vars if [[ "$CONTAINER_ENGINE" == "podman" ]]; then COMPOSE_COMMAND="podman-compose" fi if [ -n "$SERVER_WIDE_MODULES" ]; then SERVER_WIDE_MODULES="base,web,$SERVER_WIDE_MODULES" else SERVER_WIDE_MODULES="base,web" fi if [[ "$(uname)" == "Darwin" ]]; then OS_RELEASE="Darwin" PYTHON_NOTIFY="" OS_OPEN="open" else OS_RELEASE=$(awk -F= '/^NAME/{print $2}' /etc/os-release | tr -d '"') PYTHON_NOTIFY="inotify" OS_OPEN="xdg-open" fi # Help Commands function info() { set-addons-path echo "OS Release: $OS_RELEASE" echo "Git Branch: $GIT_BRANCH" echo "Odoo Revision: $ODOO_REVISION" echo "Odoo Port: $ODOO_PORT" echo "Postgres Port: $POSTGRES_PORT" echo "Odoo Language: $ODOO_LANGUAGE" echo "Container Registry: $CONTAINER_REGISTRY" echo "Container Config: $CONTAINER_CONFIG" echo "Container Tag: $CONTAINER_TAG" echo "Addons Path: $ADDONS_PATH" } function version() { activate-venv uv --version wkhtmltopdf -V if [ "$CONTAINER_ENGINE" == "docker" ]; then docker -v docker compose version else podman --version podman-compose --version fi minikube version kind version } function generate-admin-passwd() { activate-venv if test -z "$1"; then echo "\$1 is empty."; exit; fi ODOO_PASSWORD="$1" scripts/hash_password } # Environment Commands function init-venv() { if [ ! -d "venv$GIT_BRANCH" ]; then echo "Init venv$GIT_BRANCH with $(uv --version)." uv venv "venv$GIT_BRANCH" fi } function activate-venv() { echo "Source virtualenv venv$GIT_BRANCH." source "venv$GIT_BRANCH/bin/activate" echo "$(python --version) is active." } function load-env() { ENV_FILE="$ODOO_BUILD_CONFIG_DIR/.env.$1" echo "Load env file $ENV_FILE" export $(cat "$ENV_FILE" | sed 's/#.*//g' | xargs) } function get-addons-path() { set-addons-path echo "$ADDONS_PATH" } function template-odoo-rc() { echo "Template $ODOO_RC" export SERVER_WIDE_MODULES export MODULE_AUTO_INSTALL_DISABLED if [ -n "$GIT_SSH_PRIVATE_KEY" ]; then GIT_SSH_PRIVATE_KEY=$(echo -e "$GIT_SSH_PRIVATE_KEY" | base64 -w0) fi export GIT_SSH_PRIVATE_KEY export GIT_SSH_PUBLIC_KEY export MAIL_DEFAULT_FROM=${MAIL_DEFAULT_FROM:="notification"} export MAIL_CATCHALL_ALIAS=${MAIL_CATCHALL_ALIAS:="catchall"} envsubst < "odoo.conf.template" > "$ODOO_RC" } function set-addons-path() { # Read addons path from git submodules SUBMODULES_PATH="$(git config --file .gitmodules --get-regexp path | awk '{ print $2 }' | tr '\n' ',' | sed 's/,*$//g')" # Remove excluded addons EXCLUDES="addons/theme_mint_system addons/company addons/web" for EXCLUDE in $EXCLUDES; do SUBMODULES_PATH=$(echo "$SUBMODULES_PATH" | sed "s|${EXCLUDE},||g") done # Check if addons dir exist for ADDON in $SUBMODULES_PATH; do if [ -d "$ADDON" ]; then echo "Path does not exist: $ADDON" exit 1 fi done # Convert to native paths ADDONS_PATH="$(echo $SUBMODULES_PATH | sed 's|odoo,|odoo/addons,|g')" # Append paths from config env var if [ -n "$ODOO_ADDONS_PATH" ]; then ADDONS_PATH="${ODOO_ADDONS_PATH},${ADDONS_PATH}" fi } function init-config-dir() { echo "Ensure $ODOO_BUILD_CONFIG_DIR exists" mkdir -p "$ODOO_BUILD_CONFIG_DIR" } function create-odoo-env() { if test -z "$1"; then echo "\$1 is empty."; exit; fi init-config-dir ENV_FILEPATH="$ODOO_BUILD_CONFIG_DIR/.env.$1" if [ -f "$ENV_FILEPATH" ]; then echo "Env file already exists: $ENV_FILEPATH" exit 1 fi echo "Init env file: $ENV_FILEPATH" echo "ODOO_URL=$ODOO_URL" > "$ENV_FILEPATH" echo "ODOO_DATABASE=$ODOO_DATABASE" >> "$ENV_FILEPATH" echo "ODOO_USERNAME=$ODOO_USERNAME" >> "$ENV_FILEPATH" echo "ODOO_PASSWORD=$ODOO_PASSWORD" >> "$ENV_FILEPATH" echo "Edit with: task edit-env $1" } function create-nextcloud-env() { if test -z "$1"; then echo "\$1 is empty."; exit; fi init-config-dir ENV_FILEPATH="$ODOO_BUILD_CONFIG_DIR/.env.$1" if [ -f "$ENV_FILEPATH" ]; then echo "Env file already exists: $ENV_FILEPATH" exit 1 fi echo "Init env file: $ENV_FILEPATH" echo "NEXTCLOUD_URL=" > "$ENV_FILEPATH" echo "NEXTCLOUD_USERNAME=" >> "$ENV_FILEPATH" echo "NEXTCLOUD_PASSWORD=" >> "$ENV_FILEPATH" echo "Edit with: task edit-env $1" } function list-env() { ls -a1 "$ODOO_BUILD_CONFIG_DIR" } function show-env() { if test -z "$1"; then echo "\$1 is empty."; exit; fi ENV_FILEPATH="$ODOO_BUILD_CONFIG_DIR/.env.$1" cat "$ENV_FILEPATH" } function edit-env() { if test -z "$1"; then echo "\$1 is empty."; exit; fi ENV_FILEPATH="$ODOO_BUILD_CONFIG_DIR/.env.$1" "$EDITOR" "$ENV_FILEPATH" } function remove-env() { if test -z "$1"; then echo "\$1 is empty."; exit; fi ENV_FILEPATH="$ODOO_BUILD_CONFIG_DIR/.env.$1" rm "$ENV_FILEPATH" } # Docs Commands generate-help-doc() { help-table > task.md } function update-readme() { if test -z "$1"; then echo "\$1 is empty."; exit; fi README_PATH="$1" FILE_NAME=$(basename "$README_PATH") if [[ "$FILE_NAME" != "README.md" ]]; then README_PATH="$1/README.md" FILE_NAME="README.md" fi REPO_PATH=$(dirname "$README_PATH") MANIFEST_FILES="$REPO_PATH/*/__manifest__.py" # Find marker in readme and clear content after echo "Clear modules table for $README_PATH." MARKER="## Available modules" sed -i "/$MARKER/Q" "$README_PATH" echo "$MARKER" >> "$README_PATH" echo "" >> "$README_PATH" echo "| Module | Summary |" >> "$README_PATH" echo "| --- | --- |" >> "$README_PATH" # Clear table after a marker for MANIFEST_FILE in $MANIFEST_FILES; do MODULE_DIR=$(dirname "$MANIFEST_FILE") MODULE_NAME=$(basename "$MODULE_DIR") echo "Add summary of $MODULE_NAME to readme file." SUMMARY=$(grep 'summary' "$MANIFEST_FILE" -A 1 | tail -1) echo "| [$MODULE_NAME]($MODULE_NAME) | $SUMMARY |" >> "$README_PATH" done } function generate-snippets-doc() { echo "Update snippets doc file." echo "# Snippets" > snippets.md echo "In Odoo everything is described as an XML document. \ Snippets are modifications (edits) for these XML documents. \ With a snippet you can e.g. hide a field on the contact form. \ A snippet name is unique and follows the \`\$COMPANY.\$MODULE.\$XML_ID.\$DESCRIPTION\` schema." >> snippets.md PATH_URL="https://github.com/Mint-System/Odoo-Build/tree/16.0" # Get all modules MODULES=$(ls snippets/*.xml | xargs -n 1 basename | cut -d. -f1 | sort | uniq) for MODULE in $MODULES; do TITLE="${MODULE^}" # Uppercase first letter TITLE=$(echo "$TITLE" | sed 's/_/ /g') # Replace underline with space TITLE=$(echo "$TITLE" | sed -r 's/\<./\U&/g') # Uppercase first letter after space # Create doc file for each module DOCFILE="snippets/$MODULE.md" echo -e "# $TITLE" > "$DOCFILE" echo "## [$TITLE](snippets/$MODULE.md)" >> snippets.md SIDEBAR_ENTRIES+="'/snippets/$MODULE.md',\n" # Get all xml ids XML_IDS=$(ls snippets/$MODULE.*.xml | xargs -n 1 basename | cut -d. -f2 | uniq) for XML_ID in $XML_IDS; do XML_ID_TITLE=$(echo "$XML_ID" | sed 's/_/ /g') # Replace underline with space XML_ID_TITLE=$(echo "$XML_ID_TITLE" | sed -r 's/\<./\U&/g') # Uppercase first letter after space echo "## $XML_ID_TITLE " >> "$DOCFILE" # Process each file FILES="snippets/$MODULE.$XML_ID.*.xml" for FILE in $FILES; do # Get snippet params FILENAME=$(basename "$FILE") EDIT=$(echo "$FILENAME" | cut -d. -f3) EDIT_TITLE=$(echo "$EDIT" | sed 's/_/ /g') # Replace underline with space EDIT_TITLE=$(echo "$EDIT_TITLE" | sed -r 's/\<./\U&/g') # Uppercase first letter after space # Group by modules echo "### $EDIT_TITLE " >> "$DOCFILE" echo "ID: \`mint_system.$MODULE.$XML_ID.$EDIT\` " >> "$DOCFILE" echo -e '```xml' >> "$DOCFILE" cat "$FILE" >> "$DOCFILE" echo -e '\n```' >> "$DOCFILE" echo -e "Source: [$FILE]($PATH_URL/$FILE)\n" >> "$DOCFILE" done done done echo -e "export default [$SIDEBAR_ENTRIES]" > .vuepress/sidebar.js } function generate-revisions-doc() { echo "Update revisions doc file." rm -f "revisions.md" echo -e "# Odoo Revisions \n\ A Odoo revision is a snapshot of git references of the Odoo source and modules at a specific date.\n\ For each major release there are multiple revisions.\n" > "revisions.md" declare -A REVISIONS PATH_URL="https://github.com/Mint-System/Odoo-Build/tree/16.0" for FILE in scripts/odoo_revision_*; do if [[ -f "$FILE" ]]; then REVISION=$(basename "$FILE" | sed 's/odoo_revision_//') VERSION=$(echo "$REVISION" | cut -d'.' -f1-2) if [[ -z "${REVISIONS[$VERSION]}" ]]; then REVISIONS[$VERSION]="## $VERSION" fi REVISIONS[$VERSION]+=$'\n\n' REVISIONS[$VERSION]+="#### $REVISION" REVISIONS[$VERSION]+=$'\n\n```bash\n' REVISIONS[$VERSION]+=$(cat "$FILE") REVISIONS[$VERSION]+=$'\n```' fi done for REVISION in $(echo "${!REVISIONS[@]}" | tr ' ' '\n' | sort -V); do echo -e "${REVISIONS[$REVISION]}\n" >> "revisions.md" done } function generate-module-overview() { echo "Update module overview doc file." activate-venv ./scripts/generate_module_overview } function update-docs() { generate-module-overview generate-revisions-doc generate-snippets-doc generate-help-doc } # Module Commands function get-modules() { if test -z "$1"; then echo "\$1 is empty."; exit; fi OPTION="$2" if [ -z "$OPTION" ]; then OPTION="dirname" fi if [[ "$OPTION" == "dirname" ]]; then MODULES=$(echo "$1" | tr "," "\n" | xargs -I {} find {} -type f -name "__manifest__.py" | xargs grep -l "version.*${GIT_BRANCH}" | xargs -r dirname | sort -u | tr "\n" "," | sed 's/,$//') fi if [[ "$OPTION" == "basename" ]]; then MODULES=$(echo "$1" | tr "," "\n" | xargs -I {} find {} -type f -name "__manifest__.py" | xargs grep -l "version.*${GIT_BRANCH}" | xargs -r dirname | xargs -r -I {} basename {} | sort -u | tr "\n" "," | sed 's/,$//') fi echo "$MODULES" } function get-module-version() { if test -z "$1" then echo "\$1 is empty." else # Get version of module VERSION=$(grep -m 1 \"version "$1/__manifest__.py" | sed "s;';\";g" | sed "s/,//g" | sed 's/#.*//g') VERSION=$(echo "{ $VERSION }" | jq .version | sed 's/"//g' | sed 's/null//g') # Set default version [ -z "$VERSION" ] && VERSION=0.0 COUNT_DOTS=$(echo "$VERSION" | grep -o "\." | wc -l) # Check if oca version or enterprise version if [[ $COUNT_DOTS == 2 ]]; then VERSION="$GIT_BRANCH.$VERSION" fi if [[ $COUNT_DOTS == 1 ]]; then VERSION="$GIT_BRANCH.$VERSION" fi echo "$VERSION" fi } function list-modules() { if test -z "$1"; then echo "\$1 is empty."; exit; fi echo $(get-modules "$1" basename | tr "," "\n") } # Container Commands function template-docker-compose() { echo "Template docker-compose.yml" export CONTAINER_REGISTRY export CONTAINER_TAG export POSTGRES_PORT export ODOO_PORT export MAIL_DEFAULT_FROM export MAIL_CATCHALL_DOMAIN export MAIL_CATCHALL_ALIAS export ODOO_MAIL_SMTP_HOST export ODOO_MAIL_SMTP_PORT export ODOO_MAIL_SMTP_ENCRYPTION export ODOO_MAIL_SMTP_FROM_FILTER export ODOO_MAIL_IMAP_HOST export ODOO_MAIL_IMAP_PORT export ODOO_MAIL_IMAP_SSL export ODOO_MAIL_USERNAME export ODOO_MAIL_PASSWORD export GIT_SSH_PUBLIC_KEY export GIT_SSH_PRIVATE_KEY export ADDONS_GIT_REPOS export ODOO_ADDONS_PATH export ODOO_DATABASE export ODOO_INIT export ODOO_INIT_LANG export ODOO_INIT_ADDONS export ENVIRONMENT export PYTHON_INSTALL export SERVER_WIDE_MODULES export SESSION_DB_URI export PROXY_MODE export LOG_LEVEL export LIST_DB export ADMIN_PASSWD export DBFILTER export WORKERS export LIMIT_REQUEST export LIMIT_TIME_CPU export LIMIT_TIME_REAL export MODULE_AUTO_INSTALL_DISABLED export CLICK_ODOO_UPDATE export PGADMIN_DEFAULT_EMAIL export PGADMIN_DEFAULT_PASSWORD export POSTGRES_IMAGE envsubst < "docker-compose.yml.template" > "docker-compose.yml" } function ps() { $CONTAINER_ENGINE ps } function build() { MANIFEST_FILE="image/Dockerfile" PLATFORM="linux/amd64" # linux/arm64 if [ -n "$1" ]; then PLATFORM="$1" fi ODOO_DATE="" if [[ "ODOO_REVISION" != "$GIT_BRANCH" ]]; then ODOO_DATE=$(echo "$ODOO_REVISION" | cut -d'.' -f3-) fi echo "Remove container image with tag ${CONTAINER_REGISTRY}${CONTAINER_TAG}" $CONTAINER_ENGINE image rm -f "${CONTAINER_REGISTRY}${CONTAINER_TAG}" if [ "$CONTAINER_ENGINE" == "podman" ]; then echo "Run Podman build ${CONTAINER_REGISTRY}${CONTAINER_TAG} for ${PLATFORM}" SOURCE_DATE_EPOCH=$(git -C odoo log -1 --pretty=%ct) podman build --platform "$PLATFORM" . \ --file "$MANIFEST_FILE" \ --build-arg ODOO_VERSION="$GIT_BRANCH" \ --build-arg ODOO_DATE="$ODOO_DATE" \ --tag "${CONTAINER_REGISTRY}${CONTAINER_TAG}" fi if [ "$CONTAINER_ENGINE" == "docker" ]; then echo "Run Docker build ${CONTAINER_REGISTRY}${CONTAINER_TAG} for ${PLATFORM}" SOURCE_DATE_EPOCH=$(git -C odoo log -1 --pretty=%ct) docker buildx build --platform "$PLATFORM" . \ --file "$MANIFEST_FILE" \ --build-arg ODOO_VERSION="$GIT_BRANCH" \ --build-arg ODOO_DATE="$ODOO_DATE" \ --tag "${CONTAINER_REGISTRY}${CONTAINER_TAG}" \ --load fi } function container-login() { echo "Setup container hub login credentials for $CONTAINER_REGISTRY" echo -n "Enter username: " read CONTAINER_REGISTRY_USERNAME echo -n "Enter password: " read -s CONTAINER_REGISTRY_PASSWORD if [ "$CONTAINER_ENGINE" == "podman" ]; then podman login --username "$CONTAINER_REGISTRY_USERNAME" --password "$CONTAINER_REGISTRY_PASSWORD" docker.io fi if [ "$CONTAINER_ENGINE" == "docker" ]; then docker --config "$CONTAINER_CONFIG" login --username "$CONTAINER_REGISTRY_USERNAME" --password "$CONTAINER_REGISTRY_PASSWORD" fi } function publish() { LATEST_TAG="odoo:$GIT_BRANCH" echo "Tag container image ${CONTAINER_REGISTRY}${CONTAINER_TAG} with ${CONTAINER_REGISTRY}${LATEST_TAG}" $CONTAINER_ENGINE tag "${CONTAINER_REGISTRY}${CONTAINER_TAG}" "${CONTAINER_REGISTRY}${LATEST_TAG}" if [ "$CONTAINER_ENGINE" == "docker" ]; then echo "Push container image to ${CONTAINER_REGISTRY}${CONTAINER_TAG}" docker --config "$CONTAINER_CONFIG" push "${CONTAINER_REGISTRY}${CONTAINER_TAG}" docker --config "$CONTAINER_CONFIG" push "${CONTAINER_REGISTRY}${LATEST_TAG}" fi } function upload-module() { if test -z "$1"; then echo "\$1 is empty."; exit; fi if test -z "$2"; then echo "\$2 is empty."; exit; fi # Check if path is root folder MODULES="$2" if [ ! -f "$2/__manifest__.py" ]; then echo "Identified as root folder" MODULES="$MODULES/*" fi for MODULE in $MODULES; do if [ -f "$MODULE/__manifest__.py" ]; then # Create module zip zip-module "$MODULE" # Get path to zip file and upload url FILE_PATH="tmp/$MODULE_NAME.zip" load-env "$1" NEXTCLOUD_UPLOAD_URL="$NEXTCLOUD_URL/remote.php/dav/files/$NEXTCLOUD_USERNAME/Odoo-Apps/" # Upload zip file curl -u "$NEXTCLOUD_USERNAME:$NEXTCLOUD_PASSWORD" -T "$FILE_PATH" "$NEXTCLOUD_UPLOAD_URL" echo "File $FILE_PATH uploaded to $NEXTCLOUD_UPLOAD_URL." UPLOADS+=" - name: $MODULE\n" UPLOADS+=" version: $VERSION\n" fi done echo "odoo_apps:" echo -e "$UPLOADS" } function logs() { $CONTAINER_ENGINE logs --follow odoo } # Script Commands function install-odoo-scripts() { curl -L https://raw.githubusercontent.com/mint-system/ansible-build/master/roles/odoo_scripts/files/install | bash } function upgrade-odoo() { if test -z "$1"; then echo "\$1/database is empty."; exit; fi if test -z "$2"; then echo "\$2/version is empty."; exit; fi DATABASE="$1" ODOO_TARGET_VERSION="$2" MODE="test" if [ -n "$3" ]; then # Options: test, production MODE="$3" fi NEW_DATABASE="${DATABASE}_${ODOO_TARGET_VERSION}" echo "Execute Odoo upgrade script in $MODE mode for $DATABASE with version $ODOO_TARGET_VERSION." echo "The database will be restored as $NEW_DATABASE." export PGHOST=localhost export PGUSER=odoo export PGPASSWORD=odoo python <(curl -s https://upgrade.odoo.com/upgrade) "$MODE" -d "$DATABASE" -t "$ODOO_TARGET_VERSION" -r "$NEW_DATABASE" echo "Upgrade finished successfully." } # Install Commands function install-native() { init-venv activate-venv if [[ "$OS_RELEASE" =~ ^(Ubuntu|Debian|Pop!_OS)$ ]]; then # Source: https://gist.github.com/faniska/37f896d5e9de5fee925925d7caf3cb9e INSTALLED_VERSION=$(wkhtmltopdf --version 2>&1 | grep -Po '^wkhtmltopdf \K[\d.]+') if [ "$INSTALLED_VERSION" != "0.12.4" ]; then echo "Install wkhtmltopdf" sudo apt remove wkhtmltopdf wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.4/wkhtmltox-0.12.4_linux-generic-amd64.tar.xz tar xvf wkhtmltox*.tar.xz sudo mv wkhtmltox/bin/wkhtmlto* /usr/bin rm -rf wkhtmltox wkhtmltox-0.12.4_linux-generic-amd64.tar.* else echo "wkhtmltopdf version is already 0.12.4" fi echo "Install python-ldap and psycopg2 build dependencies" sudo apt-get install -y libsasl2-dev libldap2-dev libssl-dev libpq-dev python3-dev echo "Install xmllint and gettext" sudo apt install -y libxml2-utils gettext elif [[ "$OS_RELEASE" =~ ^(Darwin)$ ]]; then echo "Welcome daring Mac user. Let's try our best to get this working." echo "Install wkhtmltopdf and some dev libraries" echo "This will give a depreciation warning and you need to enter your sudo password." brew install gettext wkhtmltopdf libsasl2 openldap openssl libxml2 echo "Install the binary version of psycopg2" uv pip install psycopg2-binary --force echo "Add LDFLAGS to .env file" if ! grep -qxF 'LDFLAGS=-L/opt/homebrew/lib/' .env; then echo '' >> .env echo 'LDFLAGS=-L/opt/homebrew/lib/' >> .env fi else echo "The operating system $OS_RELEASE is not supported." exit 1 fi echo "Install Odoo development tools" uv pip install ${PYTHON_NOTIFY} uv pip install -r requirements.txt if [ -f "odoo/requirements.txt" ]; then echo "Install Odoo dependencies" uv pip install -r odoo/requirements.txt echo "Configure Odoo as python package" uv pip install pip pip install -e odoo fi } # Process Commands function open-url-with-delay(){ if test -z "$1"; then echo "\$1 is empty."; exit; fi if $BROWSER_OPEN; then sleep 2 $OS_OPEN "$1" fi } function start() { activate-venv set-addons-path template-docker-compose # Use default database if second param is not given DATABASE="$2" if [ -z "$2" ]; then DATABASE="$ODOO_DATABASE" fi if [ -z "$1" ] ; then echo "Start odoo container ${CONTAINER_REGISTRY}${CONTAINER_TAG}" echo "Open http://localhost:8069 url in your browser." $COMPOSE_COMMAND up -d fi if [[ "$1" =~ "db" ]]; then $COMPOSE_COMMAND up -d db fi if [[ "$1" =~ "admin" ]]; then echo "Open http://localhost:8000 url in your browser." open-url-with-delay "http://localhost:8000" & $COMPOSE_COMMAND up -d admin fi if [[ "$1" =~ "odoo" ]]; then echo "Start odoo container ${CONTAINER_REGISTRY}${CONTAINER_TAG}" echo "Open http://localhost:8069 url in your browser." open-url-with-delay "http://localhost:8069" & $COMPOSE_COMMAND up -d odoo fi if [[ "$1" =~ "native" ]]; then activate-venv template-odoo-rc PORT=8069 while [ $PORT -le 8079 ]; do if ! lsof -i:$PORT >/dev/null 2>&1; then break fi PORT=$((PORT + 1)) done export ODOO_BASE_URL="http://localhost:8069" echo "Open http://localhost:$PORT url in your browser." open-url-with-delay "http://localhost:$PORT/web?debug=1&db=$DATABASE" & odoo --database="$DATABASE" --config "$ODOO_RC" --xmlrpc-port="$PORT" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL" fi if [[ "$1" =~ "queue" ]]; then activate-venv PORT=8069 echo "Open http://localhost:$PORT url in your browser." open-url-with-delay "http://localhost:$PORT/web?debug=1&db=$DATABASE" & odoo --database="$DATABASE" --config "$ODOO_RC" --xmlrpc-port="$PORT" --gevent-port=8072 --load=web,queue_job --workers=4 --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL" --log-handler=odoo.addons.queue_job:DEBUG fi if [[ "$1" =~ "mail" ]]; then echo "Open http://localhost:8025 url in your browser." open-url-with-delay "http://localhost:8025" & $COMPOSE_COMMAND up -d mail fi } function remove() { activate-venv template-docker-compose if test -z "$1"; then $COMPOSE_COMMAND down -v fi if [[ "$1" =~ "db" ]]; then $COMPOSE_COMMAND rm -f -s -v db fi if [[ "$1" =~ "admin" ]]; then $COMPOSE_COMMAND rm -f -s -v admin fi if [[ "$1" =~ "odoo" ]]; then $COMPOSE_COMMAND rm -f -s -v odoo fi if [[ "$1" =~ "mail" ]]; then $COMPOSE_COMMAND rm -f -s -v mail fi } function restart() { activate-venv template-docker-compose if test -z "$1"; then $COMPOSE_COMMAND restart fi if [[ "db|mail|odoo" =~ $1 ]]; then $COMPOSE_COMMAND restart "$1" fi } function stop() { activate-venv template-docker-compose if test -z "$1"; then $COMPOSE_COMMAND stop fi $COMPOSE_COMMAND stop "$1" } function test-container() { activate-venv version info if [ -z "$GIT_SSH_PRIVATE_KEY" ]; then echo "Generate SSH private key." mkdir -p tmp if [ ! -f "tmp/ed25519" ]; then ssh-keygen -t ed25519 -C admin@example.com -f tmp/ed25519 -N "" fi GIT_SSH_PRIVATE_KEY=$(cat tmp/ed25519 | base64 -w0) GIT_SSH_PUBLIC_KEY=$(cat tmp/ed25519.pub) fi echo "Clone submodule" git submodule update --init oca/partner-contact echo "Start container environment." export GIT_SSH_PRIVATE_KEY export GIT_SSH_PUBLIC_KEY export ADDONS_GIT_REPOS="https://github.com/OCA/server-env.git#${GIT_BRANCH},https://github.com/OCA/server-tools.git#${GIT_BRANCH},https://github.com/Mint-System/Odoo-Apps-Server-Tools.git#${GIT_BRANCH},https://github.com/Mint-System/Odoo-Apps-Connector.git#${GIT_BRANCH}" export ODOO_ADDONS_PATH="/mnt/enterprise,/mnt/oca" export ODOO_DATABASE=test export ODOO_INIT=True export ODOO_INIT_LANG=de_CH export ODOO_INIT_ADDONS=server_environment_ir_config_parameter,module_auto_update,prometheus_exporter,mail_server_filter,git_base,contacts,web_enterprise export PYTHON_INSTALL=prometheus-client,manifestoo export SERVER_WIDE_MODULES=web,session_db,module_change_auto_install export SESSION_DB_URI=postgres://odoo:odoo@db/test export MODULE_AUTO_INSTALL_DISABLED=odoo_test_xmlrunner export CLICK_ODOO_UPDATE=True export ODOO_MAIL_SMTP_HOST=mail export ODOO_MAIL_SMTP_PORT=1025 export ODOO_MAIL_SMTP_ENCRYPTION=none export MAIL_DEFAULT_FROM="test" export BROWSER_OPEN=false start db,odoo,mail while ! curl --fail -s http://localhost:8069; do sleep 1; done echo "Test auto upgrade." docker-odoo-init -d test -i partner_firstname sed -i -E 's/(firstname = fields)/secondname = fields.Char();\1/' oca/partner-contact/partner_firstname/models/res_partner.py restart odoo while ! curl --fail -s http://localhost:8069; do sleep 1; done echo "Check if auto upgrade was successful." scripts/odooctl --method "search_one" --model "ir.model.fields" --domain "[('name', '=', 'secondname')]" echo "Setup and test RPC credentials." export ODOO_URL="http://localhost:8069" export ODOO_USERNAME=admin export ODOO_PASSWORD=admin create-odoo-env test show-env test test-xmlrpc test echo "Send mail with message post." echo "Install, update, disable and remove snippet." install-snippet test snippets/base.view_partner_form.show_type.xml update-snippet test snippets/base.view_partner_form.show_type.xml disable-snippet test snippets/base.view_partner_form.show_type.xml remove-snippet test snippets/base.view_partner_form.show_type.xml echo "Test Odoo scripts." docker-odoo-backup -d test docker-odoo-drop -d test docker-odoo-restore -d test -f /var/tmp/test.zip change-uuid test echo "Cleanup test environment." remove-env test git -C oca/partner-contact checkout . } # Kubernetes Commands function start-kind() { echo "Start kind." kind create cluster } function start-minikube() { echo "Start minikube." minikube start } function k8s-apply() { echo "Deploy Postgres:" kubectl apply -f k8s/postgres-cm.yaml kubectl apply -f k8s/postgres-pvc.yaml kubectl apply -f k8s/postgres-deploy.yaml kubectl apply -f k8s/postgres-svc.yaml echo -e "\nDeploy Odoo:" kubectl apply -f k8s/odoo-cm.yaml kubectl apply -f k8s/odoo16-deploy.yaml kubectl apply -f k8s/odoo-svc.yaml echo -e "\nWait until resources are ready." sleep 8 ODOO_POD=$(kubectl get pods -o=name --all-namespaces | grep odoo) POSTGRES_POD=$(kubectl get pods -o=name --all-namespaces | grep postgres) echo -e "\nWait for $ODOO_POD and $POSTGRES_POD pods to be running:" kubectl wait --for=condition=Ready "$ODOO_POD" "$POSTGRES_POD" --timeout=60s sleep 3 echo -e "\nInit Odoo database in $ODOO_POD pod:" kubectl exec "$ODOO_POD" -- env kubectl exec "$ODOO_POD" -- /bin/bash -c "odoo -d odoo -i web --db_host \$HOST -r\$USER -w\$PASSWORD --stop-after-init --no-http" } function test-k8s() { start-kind k8s-apply } # Interactive Shell Commands function start-shell() { DATABASE="$1" if [ -z "$DATABASE" ]; then DATABASE="$ODOO_DATABASE" fi activate-venv set-addons-path odoo shell --database "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http } function start-psql() { DATABASE="$1" if [ -z "$DATABASE" ]; then DATABASE="$ODOO_DATABASE" fi $CONTAINER_ENGINE exec -it db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" } # Database Commands function init-db() { ODOO_INIT_ADDONS="${ODOO_INIT_ADDONS:-"web"}" DATABASE="$1" if [ -z "$DATABASE" ]; then DATABASE="$ODOO_DATABASE" fi activate-venv set-addons-path template-odoo-rc echo "Initialize database $DATABASE with $ODOO_INIT_ADDONS." if [[ -n "$ODOO_PARAM" ]]; then odoo -d "$DATABASE" -i "$ODOO_INIT_ADDONS" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE" "$ODOO_PARAM" else odoo -d "$DATABASE" -i "$ODOO_INIT_ADDONS" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE" fi } function import-csv() { if test -z "$1"; then echo "\$1 is empty."; exit; fi DATABASE="$1" FILE_PATH="$2" # Use default database if second param is not given if test -z "$2"; then DATABASE="$ODOO_DATABASE" FILE_PATH="$1" fi FILE_NAME=$(basename "$FILE_PATH") echo "Importing $FILE_NAME to $DATABASE ..." $CONTAINER_ENGINE cp "$FILE_PATH" db:/tmp/ $CONTAINER_ENGINE exec db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "\copy $FILE_NAME FROM '/tmp/$FILE_NAME' DELIMITER ',' CSV HEADER;" } function drop-db() { DATABASE="$1" if [ -z "$DATABASE" ]; then DATABASE="$ODOO_DATABASE" fi $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$DATABASE';" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "DROP DATABASE \"$DATABASE\";" } function setup-mail() { DATABASE="$1" if [ -z "$DATABASE" ]; then DATABASE="$ODOO_DATABASE" fi activate-venv set-addons-path PYTHON_SCRIPT=$(envsubst < scripts/setup-mail) echo "Setup mail server configuation for database $DATABASE." echo "$PYTHON_SCRIPT" | odoo shell --database "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http } function setup-mail-sql() { DATABASE="$1" if [ -z "$DATABASE" ]; then DATABASE="$ODOO_DATABASE" fi activate-venv set-addons-path if [ -n "$ODOO_MAIL_SMTP_HOST" ]; then ODOO_MAIL_SMTP_PORT=${ODOO_MAIL_SMTP_PORT:="587"} ODOO_MAIL_SMTP_ENCRYPTION=${ODOO_MAIL_SMTP_ENCRYPTION:="starttls"} ODOO_MAIL_SMTP_FROM_FILTER=${ODOO_MAIL_SMTP_FROM_FILTER:=""} IR_MAIL_SERVER_ID=$($CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -tAc "SELECT id FROM ir_mail_server WHERE name = '$ODOO_MAIL_SMTP_HOST' LIMIT 1") if [ -z "$IR_MAIL_SERVER_ID" ]; then echo "Setup outgoing mail server $ODOO_MAIL_SMTP_HOST for database $DATABASE." $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "INSERT INTO ir_mail_server (active, name, smtp_host, smtp_port, smtp_user, smtp_pass, smtp_encryption, from_filter, smtp_authentication) VALUES (TRUE, '$ODOO_MAIL_SMTP_HOST', '$ODOO_MAIL_SMTP_HOST', $ODOO_MAIL_SMTP_PORT, '$ODOO_MAIL_USERNAME', '$ODOO_MAIL_PASSWORD', '$ODOO_MAIL_SMTP_ENCRYPTION', '$ODOO_MAIL_SMTP_FROM_FILTER', 'login')" fi fi if [ -n "$ODOO_MAIL_IMAP_HOST" ]; then ODOO_MAIL_IMAP_PORT=${ODOO_MAIL_IMAP_PORT:="993"} ODOO_MAIL_IMAP_SSL=${ODOO_MAIL_IMAP_SSL:="True"} FETCHMAIL_SERVER_ID=$($CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -tAc "SELECT id FROM fetchmail_server WHERE name = '$ODOO_MAIL_SMTP_HOST' LIMIT 1") if [ -z "$FETCHMAIL_SERVER_ID" ]; then echo "Setup incoming mail server $ODOO_MAIL_IMAP_HOST for database $DATABASE." $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "INSERT INTO fetchmail_server (\"active\", \"name\", \"server\", \"port\", \"user\", \"password\", \"is_ssl\", \"server_type\", \"state\") VALUES (TRUE, '$ODOO_MAIL_SMTP_HOST', '$ODOO_MAIL_IMAP_HOST', $ODOO_MAIL_IMAP_PORT, '$ODOO_MAIL_USERNAME', '$ODOO_MAIL_PASSWORD', $ODOO_MAIL_IMAP_SSL, 'imap', 'done')" fi fi if [ -n "$MAIL_CATCHALL_ALIAS" ]; then MAIL_CATCHALL_ALIAS_VALUE=$($CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -tAc "SELECT value FROM ir_config_parameter WHERE key = 'mail.catchall.alias'") if [ "$MAIL_CATCHALL_ALIAS_VALUE" != "$MAIL_CATCHALL_ALIAS" ]; then echo "Update system parameter mail.catchall.alias with value $MAIL_CATCHALL_ALIAS." $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "UPDATE ir_config_parameter SET value = '$MAIL_CATCHALL_ALIAS' WHERE key = 'mail.catchall.alias'" fi fi if [ -n "$MAIL_DEFAULT_FROM" ]; then MAIL_DEFAULT_FROM_VALUE=$($CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -tAc "SELECT value FROM ir_config_parameter WHERE key = 'mail.default.from'") if [ "$MAIL_DEFAULT_FROM_VALUE" != "$MAIL_DEFAULT_FROM" ]; then echo "Update system parameter mail.default.from with value $MAIL_DEFAULT_FROM." $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "UPDATE ir_config_parameter SET value = '$MAIL_DEFAULT_FROM' WHERE key = 'mail.default.from'" fi fi if [ -n "$MAIL_CATCHALL_DOMAIN" ]; then MAIL_CATCHALL_DOMAIN_KEY=$($CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -tAc "SELECT id FROM ir_config_parameter WHERE key = 'mail.catchall.domain'") if [ -z "$MAIL_CATCHALL_DOMAIN_KEY" ]; then echo "Add system parameter mail.catchall.domain with value $MAIL_CATCHALL_DOMAIN." $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "INSERT INTO ir_config_parameter (key, value) VALUES ('mail.catchall.domain', '$MAIL_CATCHALL_DOMAIN')" fi MAIL_CATCHALL_DOMAIN_VALUE=$($CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -tAc "SELECT value FROM ir_config_parameter WHERE key = 'mail.catchall.domain'") if [ "$MAIL_CATCHALL_DOMAIN_VALUE" != "$MAIL_CATCHALL_DOMAIN" ]; then echo "Update system parameter mail.catchall.domain with value $MAIL_CATCHALL_DOMAIN." $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "UPDATE ir_config_parameter SET value = '$MAIL_CATCHALL_DOMAIN' WHERE key = 'mail.catchall.domain'" fi fi } function load-language() { if test -z "$1"; then echo "\$1 is empty."; exit; fi DATABASE="$1" LANGUAGE="$2" # Use default database if second param is not given if test -z "$2"; then DATABASE="$ODOO_DATABASE" LANGUAGE="$1" fi activate-venv set-addons-path echo "Setup language $LANGUAGE for database $DATABASE:" odoo -d "$DATABASE" -u all --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$LANGUAGE" } function set-admin() { DATABASE="$1" if [ -z "$DATABASE" ]; then DATABASE="$ODOO_DATABASE" fi $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "UPDATE res_users SET active=true,password='\$pbkdf2-sha512\$25000\$JuScEwIg5JzTGqNUivFeqw\$yWTOcix2Afr3XGP2NPY7w4w49e9vpsu14NRndDYXAkbtMF4zkrmx6inVsoLl0zZY30xI/0GzhwonWsK9TUmjWA' WHERE id=2;" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "SELECT login, password FROM res_users WHERE id=2;" } function clear-assets() { DATABASE="$1" if [ -z "$DATABASE" ]; then DATABASE="$ODOO_DATABASE" fi echo "Delete these assets for $DATABASE:" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "select id,name from ir_attachment where res_model='ir.ui.view' and name like '%assets_%';" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "delete from ir_attachment where res_model='ir.ui.view' and name like '%assets_%';" } function clear-views() { DATABASE="$1" if [ -z "$DATABASE" ]; then DATABASE="$ODOO_DATABASE" fi echo "Archive views with prefix 'mint_system'." $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "SELECT id,name from ir_ui_view WHERE name like '%mint_system%';" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "UPDATE ir_ui_view SET active=false WHERE name like '%mint_system%';" echo "Archive views with prefix 'Odoo Studio:'." $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "SELECT id,name from ir_ui_view WHERE name like '%Odoo Studio:%';" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "UPDATE ir_ui_view SET active=false WHERE name like '%Odoo Studio:%';" echo "Delete all active views for $DATABASE in $CONTAINER :" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "DELETE FROM report_layout WHERE view_id IN (SELECT id FROM ir_ui_view WHERE active = TRUE);" $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "WITH RECURSIVE views_to_delete AS (SELECT id, inherit_id FROM ir_ui_view WHERE active = TRUE AND inherit_id IS NULL UNION ALL SELECT v.id, v.inherit_id FROM ir_ui_view v JOIN views_to_delete p ON v.inherit_id = p.id) DELETE FROM ir_ui_view WHERE id IN (SELECT id FROM views_to_delete);" } function convert-database() { if test -z "$1"; then echo "\$1 is empty."; exit; fi unzip "tmp/$1.zip" -d "./tmp/$1" sed -i'' 's/AS integer//g' "./tmp/$1/dump.sql" grep -i 'as integer' "./tmp/$1/dump.sql" cd "tmp/$1" || exit ; zip -r "../${1}2.zip" . ; cd ../.. || exit rm -r "tmp/$1" echo "Converted database file $1 to ${1}2" } function clear-filestore() { echo "Remove directory $HOME/.local/share/Odoo/filestore/$1" rm -rf "$HOME/.local/share/Odoo/filestore/$1" } function disable-mailserver() { if test -z "$1"; then echo "\$1 is empty."; exit; fi activate-venv load-env "$1" scripts/odooctl --method 'write' --model 'ir.mail_server' --field 'active' --value False --domain "[]" scripts/odooctl --method 'write' --model 'fetchmail.server' --field 'active' --value False --domain "[]" } function change-uuid() { if test -z "$1"; then echo "\$1 is empty."; exit; fi activate-venv load-env "$1" UUID=$(uuidgen) echo "Set uuid $UUID for $1." scripts/odooctl --method 'write' --model 'ir.config_parameter' --domain "[('key', '=', 'database.uuid')]" --field 'value' --value "'$UUID'" } function update-app-list() { if test -z "$1"; then echo "\$1 is empty."; exit; fi activate-venv set-addons-path echo "env['ir.module.module'].update_list()" | odoo shell --database "$1" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http } function patch-database() { if test -z "$1"; then echo "\$1 is empty."; exit; fi DATABASE="$1" FILENAME="$2" # Use default database if second param is not given if test -z "$2"; then DATABASE="$ODOO_DATABASE" FILENAME="$1" fi echo "Apply patch $FILENAME to Database $DATABASE." cat "$FILENAME" | $CONTAINER_ENGINE exec -i db psql "postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost/$DATABASE" echo "Patching database succeeded" } function save-config() { DATABASE="$1" if [ -z "$DATABASE" ]; then DATABASE="$ODOO_DATABASE" fi activate-venv set-addons-path odoo -d "$DATABASE" -s --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http } # Report Commands function odoo-cloc() { DATABASE="$1" if [ -z "$DATABASE" ]; then DATABASE="$ODOO_DATABASE" fi activate-venv set-addons-path odoo cloc -d "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http } function visualize-dependencies() { if test -z "$1"; then echo "\$1 is empty."; exit; fi MODULE_DIR="$1" OUTPUT_FILE="tmp/module_dependencies.mmd" mkdir -p tmp echo "graph TD" > "$OUTPUT_FILE" for MODULE_PATH in "$MODULE_DIR"/*; do if [[ -d "$MODULE_PATH" ]]; then MODULE_NAME=$(basename "$MODULE_PATH") MANIFEST_FILE="$MODULE_PATH/__manifest__.py" if [[ -f $MANIFEST_FILE ]]; then DEPENDENCIES=$(grep -Po '(?<="depends": \[)[^\]]*' "$MANIFEST_FILE" | tr -d '"[] ') if [[ -n "$DEPENDENCIES" ]]; then for DEPENDENCY in $(echo "$DEPENDENCIES" | tr ',' '\n'); do echo " $MODULE_NAME --> $DEPENDENCY" >> "$OUTPUT_FILE" done fi fi fi done echo "Mermaid output generated: $OUTPUT_FILE" echo "Run mermaid-cli with npx and open png file ..." npx @mermaid-js/mermaid-cli -i $OUTPUT_FILE -o tmp/module_dependencies.png --scale 10 $OS_OPEN tmp/module_dependencies.png } # Module Development Commands function install-requirements() { if test -z "$1"; then echo "\$1 is empty."; exit; fi DATABASE="$1" FILE_PATH="$2" # Use default database if second param is not given if test -z "$2"; then DATABASE="$ODOO_DATABASE" FILE_PATH="$1" fi FILE_NAME=$(basename "$FILE_PATH") if [[ "$FILE_NAME" != "requirements.txt" ]]; then echo "\$1 must point to requirements.txt file."; exit; fi CONTAINER_RUNNING=$($CONTAINER_ENGINE container inspect -f '{{.State.Running}}' odoo) if [ "$CONTAINER_RUNNING" == "true" ]; then echo "Installing python packages in container ..." $CONTAINER_ENGINE exec -u root odoo python -m pip install -r "/mnt/$FILE_PATH" else activate-venv echo "Installing python packages in virutal env ..." uv pip install -r "$FILE_PATH" fi } function init-module() { if test -z "$1"; then echo "\$1 is empty."; exit; fi DATABASE="$1" MODULE_PATH="$2" # Use default database if second param is not given if test -z "$2"; then DATABASE="$ODOO_DATABASE" MODULE_PATH="$1" fi # Check if module path is a module repo if [ -f "$MODULE_PATH/README.md" ]; then # Look for manifest files in repo folder # Get parent folder name of manifest files # Create a comma separated string of folder names MODULES=$(get-modules "$MODULE_PATH" basename) else MODULES=$(basename "$MODULE_PATH") fi activate-venv set-addons-path echo "Initialize module $MODULES on $DATABASE ..." odoo -d "$DATABASE" -i "$MODULES" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http } function upgrade-module() { if test -z "$1"; then echo "\$1 is empty."; exit; fi if test -z "$2"; then echo "\$2 is empty."; exit; fi activate-venv echo "Upgrade code of module $1 from $2 ..." odoo upgrade_code --addons-path "$1" --from "$2" } function update-module() { if test -z "$1"; then echo "\$1 is empty."; exit; fi DATABASE="$1" MODULE_NAME=$(basename "$2") # Use default database if second param is not given if test -z "$2"; then DATABASE="$ODOO_DATABASE" MODULE_NAME=$(basename "$1") fi activate-venv set-addons-path echo "Updating module $MODULE_NAME on $DATABASE ..." if [[ -n "$ODOO_PARAM" ]] ; then odoo --database "$DATABASE" --update "$MODULE_NAME" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http "$ODOO_PARAM" else odoo --database "$DATABASE" --update "$MODULE_NAME" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http fi } function translate-module() { if test -z "$1"; then echo "\$1 is empty."; exit; fi DATABASE="$1" MODULE_PATH="$2" MODULE_NAME=$(basename "$2") LANGUAGE="$3" if test -z "$2"; then DATABASE="$ODOO_DATABASE" MODULE_PATH="$1" MODULE_NAME=$(basename "$1") fi if test -z "$3"; then LANGUAGE="$ODOO_LANGUAGE" fi activate-venv set-addons-path echo "Generate $LANGUAGE tranlsation file for $MODULE_NAME on $DATABASE ..." mkdir -p "$MODULE_PATH/i18n/" odoo -d "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --modules "$MODULE_NAME" -l "$LANGUAGE" --i18n-export "$MODULE_PATH/i18n/$LANGUAGE.po" # if lang is de_CH then rename the file to de.po. if [[ "$LANGUAGE" == "de_CH" ]]; then mv "$MODULE_PATH/i18n/$LANGUAGE.po" "$MODULE_PATH/i18n/de.po" fi } function lint-module() { if test -z "$1"; then echo "\$1 is empty."; exit; fi activate-venv echo "Set file and folder permissions on $1" find "$1" -type d -exec chmod u=rwx,go=rx {} \; find "$1" -type f -exec chmod u=rw,go=r {} \; echo "Update index.html" rst2html5 "$1/README.rst" "$1/static/description/index.html" pushd . cd "$1" echo "Run pre-commit in $PWD" pre-commit run --all-files # --show-diff-on-failure --color=always echo "Stage changes" git add . popd } function lint-module-repo() { if test -z "$1"; then echo "\$1 is empty."; exit; fi MODULE_PATHS=$(get-modules "$MODULE_PATH") for MODULE_PATH in ${MODULE_PATHS//,/ }; do lint-module "$MODULE_PATH" done } function test-module() { DATABASE="$1" MODULE_NAME=$(basename "$2") # Use default database if second param is not given if test -z "$2"; then DATABASE="$ODOO_DATABASE" MODULE_NAME=$(basename "$1") fi activate-venv set-addons-path echo "Testing module $MODULE_NAME on $DATABASE ..." odoo -d "$DATABASE" -i "$MODULE_NAME" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --test-tags /"$MODULE_NAME" } function test-module-repo() { DATABASE="$1" MODULE_PATH="$2" # Use default database if second param is not given if test -z "$2"; then DATABASE="$ODOO_DATABASE" MODULE_PATH="$1" fi MODULES=$(get-modules "$MODULE_PATH" basename) for MODULE in ${MODULES//,/ }; do echo "Testing module $MODULE ..." test-module "$DATABASE" "$MODULE" done } function pytest-module() { DATABASE="$1" MODULE_PATH="$2" # Use default database if second param is not given if test -z "$2"; then DATABASE="$ODOO_DATABASE" MODULE_PATH="$1" fi activate-venv set-addons-path ODOO_CONFIG="tmp/odoo.conf" echo "Write $ODOO_CONFIG" echo "[options]" > "$ODOO_CONFIG" echo "addons_path=$ADDONS_PATH" >> "$ODOO_CONFIG" echo "db_host = localhost" >> "$ODOO_CONFIG" echo "db_password = odoo" >> "$ODOO_CONFIG" echo "db_user = odoo" >> "$ODOO_CONFIG" pytest -s --odoo-http --odoo-config="$ODOO_CONFIG" --odoo-database="$DATABASE" "$MODULE_PATH" } function remove-module() { if test -z "$1"; then echo "\$1 is empty."; exit; fi DATABASE="$1" MODULE_NAME=$(basename "$2") # Use default database if second param is not given if test -z "$2"; then DATABASE="$ODOO_DATABASE" MODULE_NAME=$(basename "$1") fi activate-venv set-addons-path echo "Remove module $MODULE_NAME" echo "self.env['ir.module.module'].search([('name', '=', '$MODULE_NAME')]).button_immediate_uninstall()" | odoo shell --database "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http } function create-module() { if test -z "$1"; then echo "\$1 is empty."; exit; fi MODULE_NAME=$(basename "$1") PARENT_DIR=$(dirname "$1") activate-venv echo "Scaffolding module $MODULE_NAME in $PARENT_DIR ..." odoo scaffold "$MODULE_NAME" "$PARENT_DIR" -t templates/module } function create-module-repo() { if test -z "$1"; then echo "\$1 is empty."; exit; fi REPO_PATH="$1" REPO_NAME=$(basename "$REPO_PATH") # If repo already exists, create new branch if [ -d "$REPO_PATH/.git" ]; then echo "Create new branch $REPO_NAME in $REPO_PATH" cd "$REPO_PATH" || exit git switch --orphan -c "$GIT_BRANCH" echo "Remove all files from $REPO_PATH" rm -rf "$REPO_PATH/*" fi echo "Copy template files from templates/GIT_BRANCH to $REPO_PATH" cp templates/GIT_BRANCH/* "$REPO_PATH" } function generate-module-security() { if test -z "$1"; then echo "\$1 is empty."; exit; fi if test -z "$2"; then echo "\$2 is empty."; exit; fi MODULE_NAME=$(basename "$1") PARENT_DIR=$(dirname "$1") MODEL_DOT_NAME="$2" MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') ACCESS_FILE="$PARENT_DIR/$MODULE_NAME/security/ir.model.access.csv" mkdir -p "$PARENT_DIR/$MODULE_NAME/security" if [ ! -f "$ACCESS_FILE" ]; then echo "Init security file: $ACCESS_FILE" echo "id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink" > "$ACCESS_FILE" fi # id # name # model_id:id # group_id:id # perm_read # perm_write # perm_create # perm_unlink echo "Add model access to $ACCESS_FILE" echo "access_${MODEL_NAME}_user,access.$MODEL_DOT_NAME.user,model_${MODEL_NAME},base.group_user,1,0,0,0" >> "$ACCESS_FILE" echo "access_${MODEL_NAME}_manager,access.$MODEL_DOT_NAME.manager,model_${MODEL_NAME},base.group_erp_manager,1,1,1,1" >> "$ACCESS_FILE" } function generate-module-model() { if test -z "$1"; then echo "\$1 is empty."; exit; fi if test -z "$2"; then echo "\$2 is empty."; exit; fi MODULE_PATH="$1" MODULE_NAME=$(basename "$1") # Generate template values MODEL_DOT_NAME="$2" # res.partner MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2 | sed 's/[^_]\+/\L\u&/g') # Partners MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner export NAME MODULE_NAME MODEL_DOT_NAME MODEL_NAME MODEL_DESCRIPTION MODEL_CAMEL_NAME echo "Substitute model template" envsubst '$NAME $MODULE_NAME $MODEL_DOT_NAME $MODEL_NAME $MODEL_DESCRIPTION $MODEL_CAMEL_NAME' < templates/model.py > "$MODULE_PATH/models/$MODEL_NAME.py" echo "from . import $MODEL_NAME" >> "$MODULE_PATH/models/__init__.py" echo "Substitute view template" mkdir -p "$MODULE_PATH/views" envsubst '$NAME $MODULE_NAME $MODEL_DOT_NAME $MODEL_NAME $MODEL_DESCRIPTION $MODEL_CAMEL_NAME' < templates/model_views.xml > "$MODULE_PATH/views/${MODEL_NAME}_views.xml" echo "Update file $MODULE_NAME/__manifest__.py with:" echo -e "\t\"views/${MODEL_NAME}_views.xml\"," } function generate-module-snippet() { if test -z "$1"; then echo "\$1 is empty."; exit; fi if test -z "$2"; then echo "\$2 is empty."; exit; fi MODULE_PATH="$1" VIEW_REF="$2" # Generate template values MODEL_NAME="crm_lead" MODEL_DOT_NAME="crm.lead" MODULE_NAME=$(basename "$1") SRC_MODULE_NAME=$(echo "$VIEW_REF" | cut -d"." -f1) XML_ID=$(echo "$VIEW_REF" | cut -d"." -f1) export MODULE_NAME MODEL_DOT_NAME SRC_MODULE_NAME XML_ID echo "Substitute snippet template" envsubst '$MODULE_NAME $MODEL_DOT_NAME $SRC_MODULE_NAME $XML_ID' < templates/model_snippet.xml > "$MODULE_PATH/views/$MODEL_NAME_views.xml" echo "Update file $MODULE_NAME/__manifest__.py with:" echo -e "\t, \"views/${MODEL_NAME}_views.xml\"" } function release-module() { if test -z "$1"; then echo "\$1 is empty."; exit; fi VERSION=$(get-module-version "$1") cd "$1" || exit LAST_TAG=$(git describe --tags) RELEASE_NOTES=$(git log "$LAST_TAG"..HEAD --oneline) echo "Tag with v$VERSION ..." git tag -a "v$VERSION" -m "v$VERSION" echo "Release notes:" echo "$RELEASE_NOTES" } function zip-module() { if test -z "$1"; then echo "\$1 is empty."; exit; fi # Get version and module name VERSION=$(get-module-version "$1") MODULE=$(basename "$1") # Copy module to tmp folder echo "Copy module $MODULE version $VERSION to tmp folder" MODULE_NAME="$MODULE-$VERSION" MODULE_PATH="tmp/$MODULE_NAME" rm -rf "$MODULE_PATH" mkdir -p "$MODULE_PATH" cp -r "$1/." "$MODULE_PATH" find "$MODULE_PATH" -type d -exec chmod u=rwx,go=rx {} \; find "$MODULE_PATH" -type f -exec chmod u=rw,go=r {} \; # Configure and zip module echo "Remove auto install option" sed -i'' -e "s/'auto_install': True,/'auto_install': False,/" "$MODULE_PATH/__manifest__.py" echo "Remove pycache and hidden folders" find "$MODULE_PATH" | grep -E "(\.git|__pycache__|\.pyc|\.pyo$)" | xargs rm -rf echo "Zip module $MODULE" cd tmp || exit rm -f "$MODULE_NAME".zip zip -q -r "$MODULE_NAME".zip "$MODULE_NAME" cd .. || exit } # Snippet Commands function reset-views() { if test -z "$1"; then echo "\$1 is empty."; exit; fi DATABASE="$1" KEY=$(basename "$2") # Use default database if second param is not given if test -z "$2"; then DATABASE="$ODOO_DATABASE" KEY=$(basename "$1") fi activate-venv set-addons-path echo "Reset views for key $KEY" echo "self.env['ir.ui.view'].search([('key', 'ilike', '$KEY')]).reset_arch(mode='hard')" | odoo shell --database "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http } function create-snippet() { if test -z "$1"; then echo "\$1 is empty."; exit; fi export INHERIT_ID=$(echo "$1" | cut -d. -f1-2) FILE="snippets/$1.xml" echo "Create snippet from template: $FILE" envsubst '$INHERIT_ID' < templates/snippet.xml > "$FILE" } function install-snippet() { if test -z "$1"; then echo "\$1 is empty."; exit; fi if test -z "$2"; then echo "\$2 is empty."; exit; fi # Generate view name from filename FILE=$(basename "$2") NAME="mint_system.${FILE%.xml}" INHERIT_ID=$(echo 'cat //data/@inherit_id' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}') TYPE=$(echo 'cat //data/@type' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}') MODEL=$(echo 'cat //data/@model' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}') PRIORITY=$(echo 'cat //data/@priority' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}') MODULE_NAME=$(echo "$INHERIT_ID" | cut -d. -f 1 ) XML_ID=$(echo "$INHERIT_ID" | cut -d. -f 2 ) # Set default values : ${TYPE:='qweb'} activate-venv load-env "$1" echo "Search for view with name $MODULE_NAME.$XML_ID" INHERIT_ID=$(scripts/odooctl --method "read" --model "ir.model.data" --field "res_id" --domain "[('name', '=', '$XML_ID'), ('module', '=', '$MODULE_NAME')]") echo "Found view id $INHERIT_ID" echo "Apply view $NAME" scripts/odooctl --method 'create' --model 'ir.ui.view' --value "{ 'name': '$NAME', 'type': '$TYPE', 'model': '$MODEL', 'mode': 'extension', 'priority': $PRIORITY, 'inherit_id': $INHERIT_ID, 'arch_base': '''$(cat "$2")''' }" } function update-snippet() { if test -z "$1"; then echo "\$1 is empty."; exit; fi if test -z "$2"; then echo "\$2 is empty."; exit; fi # Generate view name from filename FILE=$(basename "$2") NAME="mint_system.${FILE%.xml}" activate-venv load-env "$1" echo "Update view with name $NAME" scripts/odooctl --method 'write' --model 'ir.ui.view' --domain "[('name', '=', '$NAME')]" --field 'arch_base' --value "'''$(cat "$2")'''" } function disable-snippet() { if test -z "$1"; then echo "\$1 is empty."; exit; fi if test -z "$2"; then echo "\$2 is empty."; exit; fi # Generate view name from filename FILE=$(basename "$2") NAME="mint_system.${FILE%.xml}" activate-venv load-env "$1" echo "Disable view with name $NAME" scripts/odooctl --method 'write' --model "ir.ui.view" --domain "[('name', '=', '$NAME')]" --field "active" --value "False" } function remove-snippet() { if test -z "$1"; then echo "\$1 is empty."; exit; fi if test -z "$2"; then echo "\$2 is empty."; exit; fi # Generate view name from filename FILE=$(basename "$2") NAME="mint_system.${FILE%.xml}" activate-venv load-env "$1" echo "Remove view with name $NAME" scripts/odooctl --method "unlink" --model "ir.ui.view" --domain "[('name', '=', '$NAME')]" } function lint-snippets() { echo "Lint snippets files ..." FILES="snippets/*.xml" for FILE in $FILES; do # Access various snippet parameters FILENAME=$(basename "$FILE") SNIPPET_MODULE=$(echo "$FILENAME" | cut -d. -f1) REPORT=$(echo "$FILENAME" | cut -d. -f2) EDIT=$(echo "$FILENAME" | cut -d. -f3) INHERIT_ID=$(echo 'cat //data/@inherit_id' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}') TYPE=$(echo 'cat //data/@type' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}') MODEL=$(echo 'cat //data/@model' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}') PRIORITY=$(echo 'cat //data/@priority' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}') MODULE_NAME=$(echo "$INHERIT_ID" | cut -d. -f 1 ) XML_ID=$(echo "$INHERIT_ID" | cut -d. -f 2 ) # Check filename COUNT_DOTS=$(echo "$FILENAME" | grep -o "\." | wc -l) if [ "$COUNT_DOTS" -ge 4 ]; then echo "Linting failed for $FILE" echo "Dots count: $COUNT_DOTS" echo " Filename: $SNIPPET_MODULE.$REPORT" exit 1 fi # Check if inherit id matches the filename if [ -n "$INHERIT_ID" ]; then if [[ "$INHERIT_ID" != "$SNIPPET_MODULE.$REPORT" ]]; then echo "Linting failed for $FILE" echo " Inherit: $INHERIT_ID" echo "Filename: $SNIPPET_MODULE.$REPORT" exit 1 fi fi # Check if priority is set if [ -z "$PRIORITY" ] && [ -n "$INHERIT_ID" ]; then echo "Linting failed for $FILE" echo "No priority is set." exit 1 fi done echo "No problems with snippets found." } # Git Commands function git-fetch() { git fetch origin 13.0 git fetch origin 14.0 git fetch origin 15.0 git fetch origin 16.0 git fetch origin 17.0 git fetch origin 18.0 } function git-submodule-add() { if test -z "$1"; then echo "\$1 is empty."; exit; fi if test -z "$2"; then echo "\$2 is empty."; exit; fi git submodule add -f -b "$GIT_BRANCH" "$1" "$2" } function git-submodule-ls() { echo $(git config --file .gitmodules --get-regexp url | sed -e 's/submodule.//g' -e 's/.url.*//g') } function git-submodule-list() { # git submodule foreach --quiet 'printf "%-30s %-20s\n" "$name" "$(git rev-parse --abbrev-ref HEAD)"' git config --file .gitmodules --get-regexp url | sed -e 's/submodule.//g' -e 's/.url//g' | awk '{ print $1 "," $2}' } function git-submodule-delete() { echo "Delete git submodules." # Using "git submodule update --init --recursive" is not possible as modules are registered from different branches SUBMODULES=$(git-submodule-ls) for SUBMODULE in $SUBMODULES; do echo "Delete submodule: $SUBMODULE" rm -rf "$SUBMODULE" done } function git-submodule-remove() { if test -z "$1"; then echo "\$1 is empty."; exit; fi echo "Deinit submodule" git submodule deinit -f "$1" echo "Move submodule to tmp folder" mkdir -p tmp/"$1" mv "$1" tmp/"$1" echo "Remove submodule from work tree" git rm -f "$1" echo "Remove submodule from .git folder" rm -rf .git/modules/"$1" } function git-submodule-init() { echo "Init git submodules." # Using "git submodule update --init --recursive" is not possible as modules are registered from different branches SUBMODULES=$(git-submodule-ls) for SUBMODULE in $SUBMODULES; do SHALLOW=$(git config --file .gitmodules --get-regexp submodule."$SUBMODULE".shallow | awk '{ print $2 }') if [ "$SHALLOW" = "true" ]; then echo "Initializing submodule: $SUBMODULE (shallow)" git submodule update --init --depth 1 "$SUBMODULE" else echo "Initializing submodule: $SUBMODULE" git submodule update --init "$SUBMODULE" fi done } function git-submodule-pull() { echo "Pull git submodules" SUBMODULES=$(git-submodule-ls) for SUBMODULE in $SUBMODULES; do echo "Pull git submodule: $SUBMODULE" git -C "$SUBMODULE" stash UPDATE=$(git config submodule."$SUBMODULE".update) if [ -z "$UPDATE" ]; then git -C "$SUBMODULE" pull origin "$GIT_BRANCH" fi; done } function git-submodule-checkout() { echo "Checkout submodule commits." SUBMODULES=$(git-submodule-ls) for SUBMODULE in $SUBMODULES; do SHALLOW=$(git config --file .gitmodules --get-regexp submodule."$SUBMODULE".shallow | awk '{ print $2 }') COMMIT_HASH=$(git ls-tree HEAD "$SUBMODULE" | awk '{print $3}') if [ "$SHALLOW" = "true" ]; then echo "Update git submodule: $SUBMODULE (shallow)" git -C "$SUBMODULE" stash git -C "$SUBMODULE" fetch origin "$COMMIT_HASH" git -C "$SUBMODULE" checkout "$COMMIT_HASH" else echo "Update git submodule: $SUBMODULE" git -C "$SUBMODULE" stash git -C "$SUBMODULE" checkout "$COMMIT_HASH" fi done } function git-submodule-switch() { if [ -n "$1" ]; then GIT_BRANCH="$1" fi echo "Switch branch on git submodules." SUBMODULES=$(git-submodule-ls) for SUBMODULE in $SUBMODULES; do echo "Switch git submodule branch to $GIT_BRANCH: $SUBMODULE" git -C "$SUBMODULE" stash git -C "$SUBMODULE" switch "$GIT_BRANCH" done } function git-submodule-sync() { echo "Switch and pull submodules." git-submodule-switch git-submodule-pull } function git-feature-branch(){ if test -z "$1"; then echo "\$1 is empty."; exit; fi MODULE_NAME=$(basename "$1") MODULE_PATH=$(dirname "$1") echo "Create feature branch for $MODULE_NAME in $MODULE_PATH ..." cd ./"$MODULE_PATH" || exit pwd git switch -c "feature-$MODULE_NAME" } function git-mig-branch(){ if test -z "$1"; then echo "\$1 is empty."; exit; fi MODULE_NAME=$(basename "$1") MODULE_PATH=$(dirname "$1") echo "Create mig branch for $MODULE_NAME in $MODULE_PATH ..." cd ./"$MODULE_PATH" || exit pwd git switch -c "$GIT_BRANCH-mig-$MODULE_NAME" } function checkout-config-files() { if test -z "$1"; then echo "\$1 is empty."; exit; fi echo "Checkout config files $1 branch." git checkout "$1" .git-checkout-files .git-reset-files echo "Remove reset files." git rm -r $(tr '\n' ' ' < ./.git-reset-files) >/dev/null 2>&1 echo "Sync checkout files from $1 branch." git checkout "$1" $(tr '\n' ' ' < ./.git-checkout-files) } function checkout() { if test -z "$1"; then echo "\$1 is empty."; exit; fi GIT_BRANCH="$1" echo "Switch current branch to $GIT_BRANCH." git switch "$GIT_BRANCH" git pull git-submodule-init git-submodule-switch } # Revision Commands function list-revision() { ls scripts/odoo_revision_* | sed 's/scripts\/odoo_revision_//g' } function show-revision() { if [ -n "$1" ]; then ODOO_REVISION="$1" fi ODOO_VERSION=$(echo "$ODOO_REVISION" | cut -d'.' -f1-2) echo "Load refs for revision: $ODOO_REVISION" source "scripts/odoo_revision_$ODOO_REVISION" echo "Odoo Ref: $ODOO_REF" echo "Odoo Enterprise Ref: $ODOO_ENTERPRISE_REF" echo "Odoo Themes Ref: $ODOO_THEME_REF" echo "Odoo Image Ref: $ODOO_IMAGE_REF" } function checkout-revision() { if [ -n "$1" ]; then ODOO_REVISION="$1" fi ODOO_VERSION=$(echo "$ODOO_REVISION" | cut -d'.' -f1-2) echo "Update revision var in .env file." if grep -q "ODOO_REVISION" .env; then sed -i -e "s/^ODOO_REVISION=.*/ODOO_REVISION=${ODOO_REVISION}/" .env else echo "ODOO_REVISION=${ODOO_REVISION}" >> .env fi echo "Load refs for revision: $ODOO_REVISION" export $(cat "scripts/odoo_revision_$ODOO_REVISION" | sed 's/#.*//g' | xargs) echo "Checkout and tag git repos ..." git -C odoo checkout $ODOO_REF git -C odoo tag -a $ODOO_REVISION -m "$ODOO_REVISION" git -C enterprise checkout $ODOO_ENTERPRISE_REF git -C enterprise tag -a $ODOO_REVISION -m "$ODOO_REVISION" git -C themes checkout $ODOO_THEME_REF git -C themes tag -a $ODOO_REVISION -m "$ODOO_REVISION" if $ODOO_REVISION_CONTAINER_PULL; then echo "Pull container image ..." $CONTAINER_ENGINE pull "$ODOO_IMAGE_REF" IMAGE_ID=$($CONTAINER_ENGINE image inspect "$ODOO_IMAGE_REF" --format "{{ .ID }}") echo "Tag container image with odoo:$ODOO_REVISION" $CONTAINER_ENGINE tag "$IMAGE_ID" odoo:$ODOO_REVISION fi } function create-revision() { if test -z "$1"; then echo "\$1 is empty."; exit; fi REVISION="$1" # 16.0.20241029 ODOO_VERSION=$(echo "$REVISION" | cut -d'.' -f1-2) ODOO_DATE=$(echo "$REVISION" | cut -d'.' -f3-) BEFORE_DATE=$(date -d "$ODOO_DATE -1 day" +"%Y-%m-%d") # Pull container image with revision IMAGE=odoo:"$ODOO_VERSION-$ODOO_DATE" $CONTAINER_ENGINE pull "$IMAGE" CONTAINER_REF=$($CONTAINER_ENGINE inspect --format='{{index .RepoDigests 0}}' "$IMAGE") # Get git commit by date cd odoo REF=$(git rev-list -n 1 --before="$BEFORE_DATE" origin/$ODOO_VERSION) git checkout "$REF" cd .. cd enterprise ENTERPRISE_REF=$(git rev-list -n 1 --before="$BEFORE_DATE" origin/$ODOO_VERSION) git checkout "$ENTERPRISE_REF" cd .. cd themes THEME_REF=$(git rev-list -n 1 --before="$BEFORE_DATE" origin/$ODOO_VERSION) git checkout "$THEME_REF" cd .. echo "Create revision: $REVISION" export REVISION REF ENTERPRISE_REF THEME_REF CONTAINER_REF envsubst '$REVISION $REF $ENTERPRISE_REF $THEME_REF $CONTAINER_REF' < templates/odoo_revision > scripts/odoo_revision_"$REVISION" chmod +x scripts/odoo_revision_"$REVISION" echo "Checkout revision: $REVISION" checkout-revision "$REVISION" } # RPC Commands function test-xmlrpc() { if test -z "$1"; then echo "\$1 is empty."; exit 1; fi activate-venv load-env "$1" echo "Connect to database and count res.partner records: $(scripts/odooctl --model "res.partner" --method "search_count" --domain '[]')" } # Pass Commands function pass-store-dotenv() { if [ -f .env ]; then echo "Store .env file in pass: $PASS_ENTRY" cat .env | pass insert -m -f "$PASS_ENTRY" else echo "No .env file found." fi } function pass-restore-dotenv() { if pass find "$PASS_ENTRY" >/dev/null; then echo "Restore .env file from pass: $PASS_ENTRY" pass show "$PASS_ENTRY" > .env else echo "Pass entry not found." fi } # LLM Commands function llm-update() { if test -z "$1"; then echo "\$1 is empty."; exit 1; fi # Get list of files FILES=$(find "$1" -type f \( -name "*.py" -o -name "*.xml" \)) echo -e "Loaded these files into prompt:\n\n$FILES\n" # Get task description read -p "Enter the task description: " TASK_DESCRIPTION # Prepare the files content for prompt FILE_CONTENTS="" for file in $FILES; do FILE_CONTENTS+="<<< $file >>> $(cat "$file") " done # Define the prompt PROMPT="EOF Look at the code files below and do the following: $TASK_DESCRIPTION Output your response in the format of a git patch file. The patch should include only the changes to the files, and it should be applicable with 'git apply'. Do not include any explanations or extra information outside of the patch format. Here are the files: $FILE_CONTENTS EOF" # Run the llm command with the prompt echo -e "\nWait for the response of the $LLM_MODEL llm." RESULT=$(llm -m "$LLM_MODEL" "$PROMPT") # Check if the result is empty if test -z "$RESULT"; then echo "No response from the model. Exiting." exit 1 fi # Save the patch to a file PATCH_FILE="tmp/llm_changes.patch" echo "$RESULT" > "$PATCH_FILE" echo -e "Saved patch to $PATCH_FILE.\n" # Preview patch file and ask to apply git apply --check "$PATCH_FILE" less "$PATCH_FILE" read -p "Do you want to apply this patch? (y/n): " CONFIRM if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then git apply "$PATCH_FILE" echo "Applied patch file." fi } if declare -f "$1" > /dev/null; then "$1" "${@:2}" else case "$1" in help) help task exit 1 ;; node-install) npm install ;; node-dev) npm run dev ;; node-build) npm run build ;; node-serve-build) cd .vuepress/dist npx serve ;; source) activate-venv ;; *) echo "Unknown command: $1" help task exit 1 ;; esac fi