Files
full_encoach_platform/scripts/backup-db.sh
Yamen Ahmad fa6f4976c3 chore(ops): add scripts/backup-db.sh — Odoo-format DB backup
Produces an Odoo-compatible <db>.zip restorable via /web/database/manager
on the live VPS (or curl -F backup_file=@... /web/database/restore).

Why this script instead of POST /web/database/backup:
the Odoo HTTP backup endpoint shells out to pg_dump from $PATH and on
this host that returns "Command pg_dump not found" because pg_dump
lives in the conda env that Odoo wasn't launched from. This script
calls pg_dump directly with PGBIN, copies the filestore, builds a
correct manifest.json (odoo_dump, db_name, version, pg_version,
modules), and zips the three artefacts in the exact layout Odoo's
restore code expects.

Layout matches Odoo's own backup output:
  <db>.zip
  |- dump.sql       (pg_dump --no-owner --format=p)
  |- manifest.json  (odoo_dump=1, version, pg_version, installed modules)
  |- filestore/     (data_dir/filestore/<db>/ contents)

Defaults are encoach_v2 / yamenahmad / /tmp/odoo-data, all overridable
via env vars (DB, DB_USER, DATA_DIR, PGBIN, OUT_DIR). Output filename
is encoach_v2_YYYYMMDD_HHMMSS.zip in ./backups/.

Verified: latest run produced encoach_v2_20260425_031949.zip — 30 MB
compressed, 815 entries, 633 tables x 633 COPY data blocks, 575
filestore files, 94 modules in manifest.

Made-with: Cursor
2026-04-25 03:20:41 +04:00

111 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# Encoach / Odoo 19 — full database backup
#
# Produces an Odoo-format .zip backup that can be restored on the live VPS via
# /web/database/manager (the "Restore Database" button) or via the matching
# /web/database/restore HTTP endpoint.
#
# The zip layout matches what Odoo writes itself:
#
# <db>.zip
# ├── dump.sql # plain-text pg_dump (--no-owner --format=p)
# ├── manifest.json # Odoo metadata: db, version, modules
# └── filestore/ # contents of <data_dir>/filestore/<db>/
#
# We do this manually (instead of POST /web/database/backup) because Odoo's
# subprocess PATH on this host doesn't always contain pg_dump 18, which makes
# the HTTP endpoint return "Command pg_dump not found".
#
# Usage:
# ./scripts/backup-db.sh # backs up encoach_v2 → ./backups/
# DB=mydb OUT_DIR=/tmp ./scripts/backup-db.sh
#
# The output filename is encoach_v2_YYYYMMDD_HHMMSS.zip (UTC-local clock).
# -----------------------------------------------------------------------------
set -euo pipefail
PGBIN="${PGBIN:-/Users/yamenahmad/micromamba/envs/odoo19/bin}"
DB="${DB:-encoach_v2}"
DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-5432}"
DB_USER="${DB_USER:-yamenahmad}"
DATA_DIR="${DATA_DIR:-/tmp/odoo-data}"
OUT_DIR="${OUT_DIR:-$(cd "$(dirname "$0")/.." && pwd)/backups}"
if [[ ! -x "$PGBIN/pg_dump" ]]; then
echo "ERROR: pg_dump not found at $PGBIN/pg_dump" >&2
exit 1
fi
mkdir -p "$OUT_DIR"
TS=$(date +%Y%m%d_%H%M%S)
OUT="$OUT_DIR/${DB}_${TS}.zip"
WORK=$(mktemp -d -t encoach_backup_XXXXXX)
trap 'rm -rf "$WORK"' EXIT
echo "[1/4] pg_dump ${DB} -> ${WORK}/dump.sql"
"$PGBIN/pg_dump" \
--host="$DB_HOST" --port="$DB_PORT" --username="$DB_USER" \
--no-owner --no-privileges --format=p \
--dbname="$DB" \
> "$WORK/dump.sql"
DUMP_BYTES=$(wc -c < "$WORK/dump.sql" | tr -d ' ')
echo " dump.sql = $((DUMP_BYTES / 1024)) KB"
echo "[2/4] copy filestore -> ${WORK}/filestore/"
if [[ -d "$DATA_DIR/filestore/$DB" ]]; then
mkdir -p "$WORK/filestore"
# cp -R preserves symlinks/timestamps; the leading /. copies *contents*
cp -R "$DATA_DIR/filestore/$DB/." "$WORK/filestore/"
FS_BYTES=$(du -sk "$WORK/filestore" | awk '{print $1}')
echo " filestore = $((FS_BYTES / 1024)) MB"
else
echo " WARN: no filestore at $DATA_DIR/filestore/$DB (continuing without it)"
fi
echo "[3/4] write manifest.json"
PG_VERSION=$("$PGBIN/psql" -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB" -At -c "SHOW server_version;")
ODOO_VERSION=$("$PGBIN/psql" -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB" -At -c "SELECT latest_version FROM ir_module_module WHERE name='base';")
MAJOR=$(echo "$ODOO_VERSION" | cut -d. -f1-2)
# Build modules JSON object: {"name": "version", ...} for installed modules.
MODULES_JSON=$("$PGBIN/psql" -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB" -At -c \
"SELECT json_object_agg(name, latest_version) FROM ir_module_module WHERE state='installed';")
cat > "$WORK/manifest.json" <<EOF
{
"odoo_dump": "1",
"db_name": "${DB}",
"version": "${MAJOR}",
"version_info": [${MAJOR%%.*}, ${MAJOR#*.}, 0, "final", 0, ""],
"major_version": "${MAJOR}",
"pg_version": "${PG_VERSION}",
"modules": ${MODULES_JSON}
}
EOF
echo " odoo=${MAJOR} pg=${PG_VERSION} modules=$(echo "$MODULES_JSON" | tr ',' '\n' | wc -l | tr -d ' ')"
echo "[4/4] zip -> ${OUT}"
( cd "$WORK" && zip -qr "$OUT" dump.sql manifest.json filestore 2>/dev/null || \
zip -qr "$OUT" dump.sql manifest.json )
OUT_BYTES=$(wc -c < "$OUT" | tr -d ' ')
echo
echo "OK: $OUT"
echo " size = $((OUT_BYTES / 1024 / 1024)) MB"
echo
echo "Restore on live VPS:"
echo " 1) Open https://<vps>/web/database/manager"
echo " 2) Click 'Restore Database', enter the master password,"
echo " pick this .zip, set the destination DB name (e.g. encoach_v2),"
echo " and submit."
echo
echo "Or via curl:"
echo " curl -F 'master_pwd=<MASTER>' -F 'name=encoach_v2' -F 'copy=true' \\"
echo " -F 'backup_file=@${OUT}' https://<vps>/web/database/restore"