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"
ODOO_SERVICE="$2"
FINALE_SQL=$(cat <<'EOF'
/*Delete sequences that prevent Odoo to start*/
drop sequence base_registry_signaling;
drop sequence base_cache_signaling;
EOF
)
query_postgres_container "$FINALE_SQL" "$DB_NAME" || exit 1
echo "Running SQL cleanup..."
CLEANUP_SQL=$(cat <<'EOF'
-- Drop sequences that prevent Odoo from starting.
-- These sequences are recreated by Odoo on startup but stale values
-- from the old version can cause conflicts.
DROP SEQUENCE IF EXISTS base_registry_signaling;
DROP SEQUENCE IF EXISTS base_cache_signaling;
# Fix duplicated views
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" || exit 1
# Reset all website templates with custom content
FINALE_SQL_2=$(cat <<'EOF'
-- Reset website templates to their original state.
-- Views with arch_fs (file source) that have been customized (arch_db not null)
-- are reset to use the file version, EXCEPT for actual website pages which
-- contain user content that must be preserved.
UPDATE ir_ui_view
SET arch_db = NULL
WHERE arch_fs IS NOT NULL
AND arch_fs LIKE 'website/%'
AND arch_db IS NOT NULL
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
FINALE_SQL_3=$(cat <<'EOF'
-- Purge compiled frontend assets (CSS/JS bundles).
-- 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
WHERE name LIKE '/web/assets/%'
OR name LIKE '%.assets_%'
OR (res_model = 'ir.ui.view' AND mimetype = 'text/css');
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
echo "Uninstall obsolete add-ons with script $PYTHON_SCRIPT ..."
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() {
local missing=()
for cmd in docker compose sudo; do
for cmd in docker compose sudo rsync; do
if ! command -v "$cmd" &>/dev/null; then
missing+=("$cmd")
fi
@@ -28,6 +28,21 @@ log_warn() { printf "[WARN] %s\n" "$*" >&2; }
log_error() { printf "[ERROR] %s\n" "$*" >&2; }
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() {
local query="$1"
local db_name="$2"
@@ -62,9 +77,8 @@ copy_filestore() {
local src_path="${DATASTORE_PATH}/${from_service}/${FILESTORE_SUBPATH}/${from_db}"
local dst_path="${DATASTORE_PATH}/${to_service}/${FILESTORE_SUBPATH}/${to_db}"
sudo mkdir -p "$dst_path"
sudo rm -rf "$dst_path"
sudo cp -a "$src_path" "$dst_path"
sudo mkdir -p "$(dirname "$dst_path")"
sudo rsync -a --delete "${src_path}/" "${dst_path}/"
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 -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 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 ##
#######################################
SQL_404_ADDONS_LIST=$(cat <<EOF
SQL_MISSING_ADDONS=$(cat <<EOF
SELECT module_origin.name
FROM ir_module_module module_origin
LEFT JOIN (
@@ -56,54 +56,18 @@ WHERE (module_dest.name IS NULL)
ORDER BY module_origin.name;
EOF
)
echo "Retrieve 404 addons... "
echo "SQL REQUEST = $SQL_404_ADDONS_LIST"
query_postgres_container "$SQL_404_ADDONS_LIST" "$DB_NAME" > "${TMPDIR}/404_addons"
echo "Retrieve missing addons..."
missing_addons=$(query_postgres_container "$SQL_MISSING_ADDONS" "$DB_NAME")
INSTALLED_ADDONS="SELECT name FROM ir_module_module WHERE state='installed';"
query_postgres_container "$INSTALLED_ADDONS" "$DB_NAME" > "${TMPDIR}/installed_addons"
log_step "ADD-ONS CHECK"
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
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 "
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
confirm_or_exit "Do you accept to migrate with the current views state?"
echo "Database successfully prepared!"

View File

@@ -41,20 +41,19 @@ export FINALE_DB_NAME="ou${FINAL_VERSION}"
readonly FINALE_DB_NAME
readonly FINALE_SERVICE_NAME="${FINALE_DB_NAME}"
postgres_containers=$(docker ps --format '{{.Names}}' | grep postgres || true)
postgres_count=$(echo "$postgres_containers" | grep -c . || echo 0)
readarray -t postgres_containers < <(docker ps --format '{{.Names}}' | grep postgres || true)
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."
exit 1
elif [[ "$postgres_count" -gt 1 ]]; then
elif [[ ${#postgres_containers[@]} -gt 1 ]]; then
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."
exit 1
fi
export POSTGRES_SERVICE_NAME="$postgres_containers"
export POSTGRES_SERVICE_NAME="${postgres_containers[0]}"
readonly POSTGRES_SERVICE_NAME
log_step "INPUT PARAMETERS"
@@ -112,18 +111,13 @@ log_info "Original filestore copied."
log_step "PATH OF MIGRATION"
declare -a versions
nb_migrations=$((FINAL_VERSION - ORIGIN_VERSION))
for ((i = 0; i < nb_migrations; i++)); do
versions[i]=$((ORIGIN_VERSION + 1 + i))
done
readarray -t versions < <(seq $((ORIGIN_VERSION + 1)) "$FINAL_VERSION")
log_info "Migration path is ${versions[*]}"
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"
@@ -131,13 +125,10 @@ log_step "UPGRADE PROCESS"
for version in "${versions[@]}"; do
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"
done