Compare commits

...

8 Commits

Author SHA1 Message Date
Stéphan Sainléger
1281517b2f [IMP] avoid directory changes in migration loop
Replace cd into version directories with absolute path execution:

Before:
  cd "${version}.0"
  ./pre_upgrade.sh
  ./upgrade.sh
  ./post_upgrade.sh
  cd ..

After:
  "${SCRIPT_DIR}/${version}.0/pre_upgrade.sh"
  "${SCRIPT_DIR}/${version}.0/upgrade.sh"
  "${SCRIPT_DIR}/${version}.0/post_upgrade.sh"

Benefits:
- No working directory state to track
- More robust: script works regardless of where it's called from
- Easier debugging: no need to remember current directory
- Avoids potential issues if a subscript changes directory
2026-02-02 22:00:31 +01:00
Stéphan Sainléger
7a22648429 [FIX] correct undefined variable FINALE_DB_MODEL_NAME
Replace $FINALE_DB_MODEL_NAME with $FINALE_DB_NAME in the call to
prepare_db.sh.

FINALE_DB_MODEL_NAME was never defined anywhere in the codebase,
causing the script to fail immediately with 'set -u' (unbound variable
error). The intended variable is FINALE_DB_NAME which contains the
target database name (e.g., 'ou16').
2026-02-02 22:00:08 +01:00
Stéphan Sainléger
7b1ce9cc5a [IMP] factor out user confirmation prompts into reusable function
Add confirm_or_exit() function to lib/common.sh to eliminate duplicated
confirmation dialog code in prepare_db.sh.

Before: Two 10-line case statements with identical logic
After: Two single-line function calls

The function provides consistent behavior:
- Displays the question with Y/N options
- Returns 0 on Y/y (continue execution)
- Exits with error on any other input

This follows DRY principle and ensures all confirmation prompts
behave identically across the codebase.
2026-02-02 21:59:47 +01:00
Stéphan Sainléger
c815374cf1 [IMP] use rsync instead of cp for filestore copy
Replace mkdir + rm -rf + cp -a sequence with rsync --delete:

Before (3 commands):
  sudo mkdir -p "$dst_path"
  sudo rm -rf "$dst_path"
  sudo cp -a "$src_path" "$dst_path"

After (2 commands):
  sudo mkdir -p "$(dirname "$dst_path")"
  sudo rsync -a --delete "${src_path}/" "${dst_path}/"

Benefits:
- Incremental copy: only transfers changed files on re-run
- Atomic delete + copy: --delete removes extra files in destination
- Preserves all attributes like cp -a
- Faster for large filestores when re-running migration

Added rsync to required commands check.
2026-02-02 21:59:14 +01:00
Stéphan Sainléger
dd6c41b382 [IMP] combine SQL queries into single transaction with documentation
Merge three separate SQL queries into one for better performance:
- 1 database connection instead of 3
- Atomic execution of all cleanup operations

Added detailed SQL comments explaining each operation:
- DROP SEQUENCE: Why stale sequences prevent Odoo startup
- UPDATE ir_ui_view: Why website templates are reset except pages
- DELETE ir_attachment: Why compiled assets must be purged

Also changed DROP SEQUENCE to DROP SEQUENCE IF EXISTS to avoid
errors if sequences don't exist.
2026-02-02 21:58:46 +01:00
Stéphan Sainléger
0a75c2291b [IMP] simplify migration path construction with seq
Replace manual loop building version array with seq + readarray:

Before (4 lines):
  declare -a versions
  nb_migrations=$((FINAL_VERSION - ORIGIN_VERSION))
  for ((i = 0; i < nb_migrations; i++)); do
      versions[i]=$((ORIGIN_VERSION + 1 + i))
  done

After (1 line):
  readarray -t versions < <(seq $((ORIGIN_VERSION + 1)) "$FINAL_VERSION")

The seq command is purpose-built for generating number sequences,
making the intent clearer and the code more concise.
2026-02-02 21:58:13 +01:00
Stéphan Sainléger
55365df5f9 [IMP] simplify PostgreSQL container detection with readarray
Replace double grep pattern with readarray for cleaner container detection:
- Single grep call instead of two
- Native bash array instead of string manipulation
- Array length check instead of grep -c
- Proper formatting when listing multiple containers

The readarray approach is more idiomatic and avoids edge cases with
empty strings and newline handling.
2026-02-02 21:57:52 +01:00
Stéphan Sainléger
e4d11de009 [IMP] remove redundant SQL query and grep for missing addons
The SQL query already filters on module_origin.state = 'installed',
so the second query to get installed addons and the grep intersection
were completely redundant.

Before: 2 SQL queries + grep + 3 temp files
After: 1 SQL query + variable

This simplifies the code and reduces database round-trips.
2026-02-02 21:57:32 +01:00
4 changed files with 58 additions and 90 deletions

View File

@@ -4,42 +4,41 @@ set -euo pipefail
DB_NAME="$1" DB_NAME="$1"
ODOO_SERVICE="$2" ODOO_SERVICE="$2"
FINALE_SQL=$(cat <<'EOF' echo "Running SQL cleanup..."
/*Delete sequences that prevent Odoo to start*/ CLEANUP_SQL=$(cat <<'EOF'
drop sequence base_registry_signaling; -- Drop sequences that prevent Odoo from starting.
drop sequence base_cache_signaling; -- These sequences are recreated by Odoo on startup but stale values
EOF -- from the old version can cause conflicts.
) DROP SEQUENCE IF EXISTS base_registry_signaling;
query_postgres_container "$FINALE_SQL" "$DB_NAME" || exit 1 DROP SEQUENCE IF EXISTS base_cache_signaling;
# Fix duplicated views -- Reset website templates to their original state.
PYTHON_SCRIPT=post_migration_fix_duplicated_views.py -- Views with arch_fs (file source) that have been customized (arch_db not null)
echo "Remove duplicated views with script $PYTHON_SCRIPT ..." -- are reset to use the file version, EXCEPT for actual website pages which
exec_python_script_in_odoo_shell "$DB_NAME" "$DB_NAME" "$PYTHON_SCRIPT" || exit 1 -- contain user content that must be preserved.
# Reset all website templates with custom content
FINALE_SQL_2=$(cat <<'EOF'
UPDATE ir_ui_view UPDATE ir_ui_view
SET arch_db = NULL SET arch_db = NULL
WHERE arch_fs IS NOT NULL WHERE arch_fs IS NOT NULL
AND arch_fs LIKE 'website/%' AND arch_fs LIKE 'website/%'
AND arch_db IS NOT NULL AND arch_db IS NOT NULL
AND id NOT IN (SELECT view_id FROM website_page); AND id NOT IN (SELECT view_id FROM website_page);
EOF
)
query_postgres_container "$FINALE_SQL_2" "$DB_NAME" || exit 1
# Purge QWeb cache from compiled assets -- Purge compiled frontend assets (CSS/JS bundles).
FINALE_SQL_3=$(cat <<'EOF' -- These cached files reference old asset versions and must be regenerated
-- by Odoo after migration to avoid broken stylesheets and scripts.
DELETE FROM ir_attachment DELETE FROM ir_attachment
WHERE name LIKE '/web/assets/%' WHERE name LIKE '/web/assets/%'
OR name LIKE '%.assets_%' OR name LIKE '%.assets_%'
OR (res_model = 'ir.ui.view' AND mimetype = 'text/css'); OR (res_model = 'ir.ui.view' AND mimetype = 'text/css');
EOF EOF
) )
query_postgres_container "$FINALE_SQL_3" "$DB_NAME" || exit 1 query_postgres_container "$CLEANUP_SQL" "$DB_NAME"
# Uninstall obsolette add-ons PYTHON_SCRIPT=post_migration_fix_duplicated_views.py
echo "Remove duplicated views with script $PYTHON_SCRIPT ..."
exec_python_script_in_odoo_shell "$DB_NAME" "$DB_NAME" "$PYTHON_SCRIPT"
# Uninstall obsolete add-ons
PYTHON_SCRIPT=post_migration_cleanup_obsolete_modules.py PYTHON_SCRIPT=post_migration_cleanup_obsolete_modules.py
echo "Uninstall obsolete add-ons with script $PYTHON_SCRIPT ..." echo "Uninstall obsolete add-ons with script $PYTHON_SCRIPT ..."
exec_python_script_in_odoo_shell "$DB_NAME" "$DB_NAME" "$PYTHON_SCRIPT" || exit 1 exec_python_script_in_odoo_shell "$DB_NAME" "$DB_NAME" "$PYTHON_SCRIPT" || exit 1

View File

@@ -11,7 +11,7 @@ readonly FILESTORE_SUBPATH="var/lib/odoo/filestore"
check_required_commands() { check_required_commands() {
local missing=() local missing=()
for cmd in docker compose sudo; do for cmd in docker compose sudo rsync; do
if ! command -v "$cmd" &>/dev/null; then if ! command -v "$cmd" &>/dev/null; then
missing+=("$cmd") missing+=("$cmd")
fi fi
@@ -28,6 +28,21 @@ log_warn() { printf "[WARN] %s\n" "$*" >&2; }
log_error() { printf "[ERROR] %s\n" "$*" >&2; } log_error() { printf "[ERROR] %s\n" "$*" >&2; }
log_step() { printf "\n===== %s =====\n" "$*"; } log_step() { printf "\n===== %s =====\n" "$*"; }
confirm_or_exit() {
local message="$1"
local choice
echo ""
echo "$message"
echo "Y - Yes, continue"
echo "N - No, cancel"
read -r -n 1 -p "Your choice: " choice
echo ""
case "$choice" in
[Yy]) return 0 ;;
*) log_error "Cancelled by user."; exit 1 ;;
esac
}
query_postgres_container() { query_postgres_container() {
local query="$1" local query="$1"
local db_name="$2" local db_name="$2"
@@ -62,9 +77,8 @@ copy_filestore() {
local src_path="${DATASTORE_PATH}/${from_service}/${FILESTORE_SUBPATH}/${from_db}" local src_path="${DATASTORE_PATH}/${from_service}/${FILESTORE_SUBPATH}/${from_db}"
local dst_path="${DATASTORE_PATH}/${to_service}/${FILESTORE_SUBPATH}/${to_db}" local dst_path="${DATASTORE_PATH}/${to_service}/${FILESTORE_SUBPATH}/${to_db}"
sudo mkdir -p "$dst_path" sudo mkdir -p "$(dirname "$dst_path")"
sudo rm -rf "$dst_path" sudo rsync -a --delete "${src_path}/" "${dst_path}/"
sudo cp -a "$src_path" "$dst_path"
echo "Filestore ${from_service}/${from_db} copied to ${to_service}/${to_db}." echo "Filestore ${from_service}/${from_db} copied to ${to_service}/${to_db}."
} }
@@ -77,6 +91,6 @@ exec_python_script_in_odoo_shell() {
} }
export DATASTORE_PATH FILESTORE_SUBPATH export DATASTORE_PATH FILESTORE_SUBPATH
export -f log_info log_warn log_error log_step export -f log_info log_warn log_error log_step confirm_or_exit
export -f check_required_commands export -f check_required_commands
export -f query_postgres_container copy_database copy_filestore exec_python_script_in_odoo_shell export -f query_postgres_container copy_database copy_filestore exec_python_script_in_odoo_shell

View File

@@ -42,7 +42,7 @@ echo "Base neutralized..."
## List add-ons not in final version ## ## List add-ons not in final version ##
####################################### #######################################
SQL_404_ADDONS_LIST=$(cat <<EOF SQL_MISSING_ADDONS=$(cat <<EOF
SELECT module_origin.name SELECT module_origin.name
FROM ir_module_module module_origin FROM ir_module_module module_origin
LEFT JOIN ( LEFT JOIN (
@@ -56,54 +56,18 @@ WHERE (module_dest.name IS NULL)
ORDER BY module_origin.name; ORDER BY module_origin.name;
EOF EOF
) )
echo "Retrieve 404 addons... " echo "Retrieve missing addons..."
echo "SQL REQUEST = $SQL_404_ADDONS_LIST" missing_addons=$(query_postgres_container "$SQL_MISSING_ADDONS" "$DB_NAME")
query_postgres_container "$SQL_404_ADDONS_LIST" "$DB_NAME" > "${TMPDIR}/404_addons"
INSTALLED_ADDONS="SELECT name FROM ir_module_module WHERE state='installed';" log_step "ADD-ONS CHECK"
query_postgres_container "$INSTALLED_ADDONS" "$DB_NAME" > "${TMPDIR}/installed_addons" echo "Installed add-ons not available in final Odoo version:"
echo "$missing_addons"
confirm_or_exit "Do you accept to migrate with these add-ons still installed?"
grep -Fx -f "${TMPDIR}/404_addons" "${TMPDIR}/installed_addons" > "${TMPDIR}/final_404_addons" || true
echo "
==== ADD-ONS CHECK ====
Installed add-ons not available in final Odoo version:
"
cat "${TMPDIR}/final_404_addons"
echo "
Do you accept to migrate the database with all these add-ons still installed? (Y/N/R)"
echo "Y - Yes, let's go on with the upgrade."
echo "N - No, stop the upgrade"
read -n 1 -p "Your choice: " choice
case "$choice" in
[Yy] ) echo "
Let's go on!";;
[Nn] ) echo "
Upgrade cancelled!"; exit 1;;
* ) echo "
Please answer by Y or N.";;
esac
# Check the views
PYTHON_SCRIPT=pre_migration_view_checking.py PYTHON_SCRIPT=pre_migration_view_checking.py
echo "Check views with script $PYTHON_SCRIPT ..." echo "Check views with script $PYTHON_SCRIPT ..."
exec_python_script_in_odoo_shell "$DB_NAME" "$DB_NAME" "$PYTHON_SCRIPT" || exit 1 exec_python_script_in_odoo_shell "$DB_NAME" "$DB_NAME" "$PYTHON_SCRIPT"
echo " confirm_or_exit "Do you accept to migrate with the current views state?"
Do you accept to migrate the database with the current views states? (Y/N/R)"
echo "Y - Yes, let's go on with the upgrade."
echo "N - No, stop the upgrade"
read -n 1 -p "Your choice: " choice
case "$choice" in
[Yy] ) echo "
Upgrade confirmed!";;
[Nn] ) echo "
Upgrade cancelled!"; exit 1;;
* ) echo "
Please answer by Y or N.";;
esac
echo "Database successfully prepared!" echo "Database successfully prepared!"

View File

@@ -41,20 +41,19 @@ export FINALE_DB_NAME="ou${FINAL_VERSION}"
readonly FINALE_DB_NAME readonly FINALE_DB_NAME
readonly FINALE_SERVICE_NAME="${FINALE_DB_NAME}" readonly FINALE_SERVICE_NAME="${FINALE_DB_NAME}"
postgres_containers=$(docker ps --format '{{.Names}}' | grep postgres || true) readarray -t postgres_containers < <(docker ps --format '{{.Names}}' | grep postgres || true)
postgres_count=$(echo "$postgres_containers" | grep -c . || echo 0)
if [[ "$postgres_count" -eq 0 ]]; then if [[ ${#postgres_containers[@]} -eq 0 ]]; then
log_error "No running PostgreSQL container found. Please start a PostgreSQL container and try again." log_error "No running PostgreSQL container found. Please start a PostgreSQL container and try again."
exit 1 exit 1
elif [[ "$postgres_count" -gt 1 ]]; then elif [[ ${#postgres_containers[@]} -gt 1 ]]; then
log_error "Multiple PostgreSQL containers found:" log_error "Multiple PostgreSQL containers found:"
echo "$postgres_containers" >&2 printf ' %s\n' "${postgres_containers[@]}" >&2
log_error "Please ensure only one PostgreSQL container is running." log_error "Please ensure only one PostgreSQL container is running."
exit 1 exit 1
fi fi
export POSTGRES_SERVICE_NAME="$postgres_containers" export POSTGRES_SERVICE_NAME="${postgres_containers[0]}"
readonly POSTGRES_SERVICE_NAME readonly POSTGRES_SERVICE_NAME
log_step "INPUT PARAMETERS" log_step "INPUT PARAMETERS"
@@ -112,18 +111,13 @@ log_info "Original filestore copied."
log_step "PATH OF MIGRATION" log_step "PATH OF MIGRATION"
declare -a versions readarray -t versions < <(seq $((ORIGIN_VERSION + 1)) "$FINAL_VERSION")
nb_migrations=$((FINAL_VERSION - ORIGIN_VERSION))
for ((i = 0; i < nb_migrations; i++)); do
versions[i]=$((ORIGIN_VERSION + 1 + i))
done
log_info "Migration path is ${versions[*]}" log_info "Migration path is ${versions[*]}"
log_step "DATABASE PREPARATION" log_step "DATABASE PREPARATION"
./prepare_db.sh "$COPY_DB_NAME" "$COPY_DB_NAME" "$FINALE_DB_MODEL_NAME" "$FINALE_SERVICE_NAME" ./prepare_db.sh "$COPY_DB_NAME" "$COPY_DB_NAME" "$FINALE_DB_NAME" "$FINALE_SERVICE_NAME"
log_step "UPGRADE PROCESS" log_step "UPGRADE PROCESS"
@@ -131,13 +125,10 @@ log_step "UPGRADE PROCESS"
for version in "${versions[@]}"; do for version in "${versions[@]}"; do
log_info "START UPGRADE TO ${version}.0" log_info "START UPGRADE TO ${version}.0"
cd "${version}.0" "${SCRIPT_DIR}/${version}.0/pre_upgrade.sh"
"${SCRIPT_DIR}/${version}.0/upgrade.sh"
"${SCRIPT_DIR}/${version}.0/post_upgrade.sh"
./pre_upgrade.sh
./upgrade.sh
./post_upgrade.sh
cd ..
log_info "END UPGRADE TO ${version}.0" log_info "END UPGRADE TO ${version}.0"
done done