#!/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: # # .zip # ├── dump.sql # plain-text pg_dump (--no-owner --format=p) # ├── manifest.json # Odoo metadata: db, version, modules # └── filestore/ # contents of /filestore// # # 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" < ${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:///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=' -F 'name=encoach_v2' -F 'copy=true' \\" echo " -F 'backup_file=@${OUT}' https:///web/database/restore"