# -*- coding: utf-8 -*- ################################################################################# # # Copyright (c) 2016-Present Webkul Software Pvt. Ltd. () # See LICENSE file for full copyright and licensing details. # License URL : # ################################################################################# import os import logging import datetime import pytz import shutil import subprocess import tempfile import json import odoo from odoo import http, _ from odoo.http import request from odoo.exceptions import AccessError, UserError from odoo.tools.misc import exec_pg_environ, find_pg_tool _logger = logging.getLogger(__name__) class BackupController(http.Controller): @http.route('/backupfile/download', type='http', auth='user') def file_download(self, **kwargs): file_path = request.httprequest.args.get('path') # The actual file path backup_location = request.httprequest.args.get('backup_location') or 'local' _logger.info(f"=====backup_location========= {backup_location} ====== file_path ====== {file_path}") try: # Read the file and return it as a response file_data = None with open(file_path, 'rb') as file: file_data = file.read() # Set the response headers for file download response = request.make_response(file_data) response.headers['Content-Disposition'] = f"attachment; filename={file_path.split('/')[-1]}" response.mimetype = 'application/octet-stream' # Delete the remote backup file from Main Server if backup_location == 'remote': os.remove(file_path) return response except Exception as e: _logger.info(f"======= Backup File Download Error ======= {e} ========") raise UserError(e) @http.route('/saas/database/backup', type='http', auth="none", methods=['POST'], csrf=False) def db_backup(self, **kwargs): master_pwd = kwargs.get('master_pwd') dbname = kwargs.get('name') backup_format = kwargs.get('backup_format') or 'zip' response = None user = request.env['res.users'].sudo().browse([2]) tz = pytz.timezone(user.tz) if user.tz else pytz.utc time_now = pytz.utc.localize(datetime.datetime.now()).astimezone(tz) ts = time_now.strftime("%m-%d-%Y-%H.%M.%S") filename = "%s_%s.%s" % (dbname, ts, backup_format) try: odoo.service.db.check_super(master_pwd) dump_stream = self.dump_db(dbname, None, backup_format) response = request.make_response(dump_stream) response.headers['Content-Disposition'] = f"attachment; filename={filename}" response.mimetype = 'application/octet-stream' except Exception as e: error = "Database backup error: %s" % (str(e) or repr(e)) _logger.exception('Database.backup --- %r', error) response = request.make_response(error) response.mimetype = 'text/html' response.headers['Backup-Filename'] = filename response.headers['Backup-Time'] = time_now.strftime("%m-%d-%Y-%H:%M:%S") return response def dump_db_manifest(self, cr): pg_version = "%d.%d" % divmod(cr._obj.connection.server_version / 100, 100) cr.execute("SELECT name, latest_version FROM ir_module_module WHERE state = 'installed'") modules = dict(cr.fetchall()) manifest = { 'odoo_dump': '1', 'db_name': cr.dbname, 'version': odoo.release.version, 'version_info': odoo.release.version_info, 'major_version': odoo.release.major_version, 'pg_version': pg_version, 'modules': modules, } return manifest def dump_db(self, db_name, stream, backup_format='zip'): """Dump database `db` into file-like object `stream` if stream is None return a file object with the dump """ _logger.info('DUMP DB: %s format %s', db_name, backup_format) cmd = [find_pg_tool('pg_dump'), '--no-owner', db_name] env = exec_pg_environ() if backup_format == 'zip': with tempfile.TemporaryDirectory() as dump_dir: filestore = odoo.tools.config.filestore(db_name) if os.path.exists(filestore): shutil.copytree(filestore, os.path.join(dump_dir, 'filestore')) with open(os.path.join(dump_dir, 'manifest.json'), 'w') as fh: db = odoo.sql_db.db_connect(db_name) with db.cursor() as cr: json.dump(self.dump_db_manifest(cr), fh, indent=4) cmd.insert(-1, '--file=' + os.path.join(dump_dir, 'dump.sql')) subprocess.run(cmd, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=True) if stream: odoo.tools.osutil.zip_dir(dump_dir, stream, include_dir=False, fnct_sort=lambda file_name: file_name != 'dump.sql') else: t=tempfile.TemporaryFile() odoo.tools.osutil.zip_dir(dump_dir, t, include_dir=False, fnct_sort=lambda file_name: file_name != 'dump.sql') t.seek(0) return t else: cmd.insert(-1, '--format=c') stdout = subprocess.Popen(cmd, env=env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE).stdout if stream: shutil.copyfileobj(stdout, stream) else: return stdout