# -*- coding: utf-8 -*- import os import tempfile import logging from datetime import datetime from odoo import models, fields, api from odoo.exceptions import UserError from odoo.service import db _logger = logging.getLogger(__name__) try: import boto3 except ImportError: boto3 = None class SaasBackup(models.Model): _name = 'saas.backup' _description = 'Dine360 SaaS Backup Log' _order = 'timestamp desc' restaurant_id = fields.Many2one('saas.restaurant', string='Restaurant Tenant', required=True) database_name = fields.Char(related='restaurant_id.database_name', store=True, readonly=True) backup_file = fields.Char(string='Backup Filename', readonly=True) timestamp = fields.Datetime(string='Backup Time', default=fields.Datetime.now, readonly=True) status = fields.Selection([ ('success', 'Success'), ('failed', 'Failed') ], string='Status', default='failed', readonly=True) storage_type = fields.Selection([ ('local', 'Local Storage'), ('s3', 'Amazon S3') ], string='Storage Type', default='local', required=True) s3_url = fields.Char(string='S3 URL', readonly=True) def action_backup_database(self): self.ensure_one() db_name = self.database_name if not db_name: raise UserError("Database name is missing on the restaurant record!") timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S') filename = f"{db_name}_{timestamp_str}.zip" # Create temporary file to store Odoo dump temp_dir = tempfile.gettempdir() temp_filepath = os.path.join(temp_dir, filename) try: _logger.info(f"Dumping database {db_name} to {temp_filepath}...") with open(temp_filepath, 'wb') as f: # Odoo service dump database to stream db.dump_db(db_name, f, format='zip') _logger.info("Database dumped successfully.") # S3 upload logic s3_bucket = os.environ.get('AWS_S3_BUCKET') aws_access_key = os.environ.get('AWS_ACCESS_KEY_ID') aws_secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY') if self.storage_type == 's3' and boto3 and s3_bucket: _logger.info(f"Uploading {filename} to AWS S3 bucket {s3_bucket}...") s3_client = boto3.client( 's3', aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key ) s3_client.upload_file(temp_filepath, s3_bucket, filename) s3_url = f"https://{s3_bucket}.s3.amazonaws.com/{filename}" self.write({ 'backup_file': filename, 'status': 'success', 's3_url': s3_url }) # Clean up local temp file os.remove(temp_filepath) else: # Local storage: move backup to persistent local directory backup_dir = os.environ.get('ODOO_BACKUP_DIR', '/var/lib/odoo/backups') if not os.path.exists(backup_dir): os.makedirs(backup_dir) persistent_path = os.path.join(backup_dir, filename) os.rename(temp_filepath, persistent_path) self.write({ 'backup_file': filename, 'status': 'success', 's3_url': f"file://{persistent_path}" }) except Exception as e: _logger.error(f"Backup generation failed: {str(e)}") self.write({ 'backup_file': filename, 'status': 'failed' }) if os.path.exists(temp_filepath): os.remove(temp_filepath) raise UserError(f"Backup failed: {str(e)}") @api.model def cron_automatic_backups(self): """ Triggered by Odoo cron job to run daily backups for all active tenants """ tenants = self.env['saas.restaurant'].search([('status', '=', 'active')]) s3_bucket = os.environ.get('AWS_S3_BUCKET') storage = 's3' if s3_bucket else 'local' for tenant in tenants: try: backup = self.create({ 'restaurant_id': tenant.id, 'storage_type': storage }) backup.action_backup_database() except Exception as e: _logger.error(f"Scheduled backup failed for {tenant.database_name}: {str(e)}")