Skip to content

Commit

Permalink
[ADD][1587] auto_backup_sh: add Odoo.sh backup support
Browse files Browse the repository at this point in the history
  • Loading branch information
sswapnesh authored Dec 19, 2023
1 parent f2bfd35 commit a826401
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 0 deletions.
1 change: 1 addition & 0 deletions auto_backup_sh/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
24 changes: 24 additions & 0 deletions auto_backup_sh/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
{
"name": "Database auto-backup for Odoo.sh",
"summary": "Automated backups for Odoo.sh",
"description": """
Allow automated backups from Odoo.sh to a remote (FTP) server
""",
"author": "Yenthe Van Ginneken",
"website": "https://mainframemonkey.com",
"category": "Administration",
"version": "16.0.1.0.1",
"installable": True,
"license": "LGPL-3",

# any module necessary for this one to work correctly
"depends": [
"auto_backup"
],

# always loaded
"data": [
"views/odoosh_db_backup_view.xml",
],
}
48 changes: 48 additions & 0 deletions auto_backup_sh/i18n/nl.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auto_backup_sh
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-12-19 13:33+0000\n"
"PO-Revision-Date: 2023-12-19 13:33+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: auto_backup_sh
#: model:ir.model,name:auto_backup_sh.model_db_backup
msgid "Backup configuration record"
msgstr "Back-up configuratie"

#. module: auto_backup_sh
#: model:ir.model.fields,help:auto_backup_sh.field_db_backup__is_odoo_sh_instance
msgid "Flag used to determine if the Odoo instance is on Odoo.sh or not."
msgstr "Checkbox om te bepalen of het om een Odoo instantie op Odoo.sh gaat of niet"

#. module: auto_backup_sh
#: model:ir.model.fields,field_description:auto_backup_sh.field_db_backup__is_odoo_sh_instance
msgid "Is Odoo.sh instance?"
msgstr "Is Odoo.sh instantie?"

#. module: auto_backup_sh
#. odoo-python
#: code:addons/auto_backup_sh/models/odoosh_db_backup.py:0
#, python-format
msgid "The \"Backup Type\" has to be set to \"Zip\" for Odoo.sh instances"
msgstr "De \"Soort backup\" moet op ZIP staan voor Odoo.sh instanties"

#. module: auto_backup_sh
#. odoo-python
#: code:addons/auto_backup_sh/models/odoosh_db_backup.py:0
#, python-format
msgid ""
"The option \"Write to external server with sftp\" must be activated as we "
"can only backup to remote instances with Odoo.sh."
msgstr "De optie \"Schrijf naar externe server met SFTP\" moet aangevinkt zijn "
"aangezien we alleen back-ups naar externe locaties kunnen schrijven op Odoo.sh."
2 changes: 2 additions & 0 deletions auto_backup_sh/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

from . import odoosh_db_backup
200 changes: 200 additions & 0 deletions auto_backup_sh/models/odoosh_db_backup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@

import os
import datetime
import time
import shutil
import json
import tempfile
import gzip
from odoo.exceptions import ValidationError

from odoo import models, fields, api, tools, _
import odoo

import logging
_logger = logging.getLogger(__name__)

try:
import paramiko
except ImportError:
raise ImportError(
'This module needs paramiko to automatically write backups to the FTP through SFTP. '
'Please install paramiko on your system. (sudo pip3 install paramiko)')


class DbBackup(models.Model):
_inherit = 'db.backup'

is_odoo_sh_instance = fields.Boolean(
string="Is Odoo.sh instance?",
default=True,
help="Flag used to determine if the Odoo instance is on Odoo.sh or not.")

@api.onchange('is_odoo_sh_instance')
def _onchange_is_odoo_sh_instance(self):
if self.is_odoo_sh_instance:
self.update({
'backup_type' :'zip',
'sftp_write': True
})

@api.constrains('backup_type', 'is_odoo_sh_instance')
def _constrains_is_odoo_sh_instance(self):
if self.is_odoo_sh_instance and self.backup_type != 'zip':
raise ValidationError(_('The "Backup Type" has to be set to "Zip" for Odoo.sh instances'))
if self.is_odoo_sh_instance and not self.sftp_write:
raise ValidationError(_('The option "Write to external server with sftp" must be activated as we can only backup to remote instances with Odoo.sh.'))

def _take_dump(self, db_name, stream, model, backup_format='zip', odoo_sh=True):
if odoo_sh:
if backup_format == 'zip':
with tempfile.TemporaryDirectory() as dump_dir:
filestore = os.path.join(os.getcwd(), 'backup.daily', f'{db_name}_daily', 'home', 'odoo', 'data', 'filestore', db_name)
# filestore = os.path.join(os.getcwd(), 'backup.daily', f'{db_name}_daily', 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)

dump_sql_path = os.path.join(os.getcwd(), 'backup.daily', f"{db_name}_daily.sql.gz")
with gzip.open(dump_sql_path, 'rb') as dump_sql_in, open(os.path.join(dump_dir, 'dump.sql'), 'wb') as dump_sql_out:
shutil.copyfileobj(dump_sql_in, dump_sql_out)

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:
return super()._take_dump(db_name, stream, model, backup_format)

@api.model
def schedule_backup(self):
db_backup_obj = self.env['db.backup']
odoo_sh_db_backups = db_backup_obj.search([('is_odoo_sh_instance','=', True)])
for backup in odoo_sh_db_backups:
backup_path_location = backup.folder
ts = datetime.datetime.utcnow().strftime("%Y-%m-%d_%H-%M-%S")
backup_file_name = "%s_%s.%s" % (backup.name, ts, backup.backup_type)
file_path = os.path.join(backup_path_location, backup_file_name)

try:
fp = open(file_path, 'wb')
self._take_dump(backup.name, fp, 'db.backup', backup.backup_type, odoo_sh=True)
fp.close()
except Exception as error:
_logger.info("Something Went Wrong : %s", str(error))
os.remove(file_path)
continue

# Check if user wants to write to SFTP or not.
if backup.sftp_write:
try:
# Store all values in variables
dir = backup.folder
path_to_write_to = backup.sftp_path
ip_host = backup.sftp_host
port_host = backup.sftp_port
username_login = backup.sftp_user
password_login = backup.sftp_password

try:
s = paramiko.SSHClient()
s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
s.connect(ip_host, port_host, username_login, password_login, timeout=20)
sftp = s.open_sftp()
except Exception as error:
_logger.critical('Error connecting to remote server! Error: %s', str(error))

try:
sftp.chdir(path_to_write_to)
except IOError:
# Create directory and subdirs if they do not exist.
current_directory = ''
for dir_element in path_to_write_to.split('/'):
current_directory += dir_element + '/'
try:
sftp.chdir(current_directory)
except:
_logger.info('(Part of the) path didn\'t exist. Creating it now at %s', current_directory)
# Make directory and then navigate into it
sftp.mkdir(current_directory, 777)
sftp.chdir(current_directory)
pass
sftp.chdir(path_to_write_to)
# Loop over all files in the directory.
for f in os.listdir(dir):
if backup.name in f:
fullpath = os.path.join(dir, f)
if os.path.isfile(fullpath):
try:
sftp.stat(os.path.join(path_to_write_to, f))
_logger.debug('File %s already exists on the remote FTP Server ------ skipped', fullpath)
# This means the file does not exist (remote) yet!
except IOError:
try:
sftp.put(fullpath, os.path.join(path_to_write_to, f))
_logger.info('Copying File % s------ success', fullpath)
except Exception as err:
_logger.critical('We couldn\'t write the file to the remote server. Error: %s', str(err))

# Navigate in to the correct folder.
sftp.chdir(path_to_write_to)

_logger.debug("Checking expired files")
# Loop over all files in the directory from the back-ups.
# We will check the creation date of every back-up.

for file in sftp.listdir(path_to_write_to):
if backup.name in file:
# Get the full path
fullpath = os.path.join(path_to_write_to, file)
# Get the timestamp from the file on the external server
timestamp = sftp.stat(fullpath).st_mtime
createtime = datetime.datetime.fromtimestamp(timestamp)
now = datetime.datetime.now()
delta = now - createtime
# If the file is older than the days_to_keep_sftp (the days to keep that the user filled in
# on the Odoo form it will be removed.
if delta.days >= backup.days_to_keep_sftp:
# Only delete files, no directories!
if ".dump" in file or '.zip' in file:
_logger.info("Delete too old file from SFTP servers: %s", file)
sftp.unlink(file)
# Close the SFTP session.
sftp.close()
s.close()
except Exception as e:
try:
sftp.close()
s.close()
except:
pass
_logger.error('Exception! We couldn\'t back up to the FTP server. Here is what we got back '
'instead: %s', str(e))
# At this point the SFTP backup failed. We will now check if the user wants
# an e-mail notification about this.
if backup.send_mail_sftp_fail:
try:
ir_mail_server = self.env['ir.mail_server'].search([], order='sequence asc', limit=1)
message = "Dear,\n\nThe backup for the server " + backup.host + " (IP: " + backup.sftp_host + \
") failed. Please check the following details:\n\nIP address SFTP server: " + \
backup.sftp_host + "\nUsername: " + backup.sftp_user + \
"\n\nError details: " + tools.ustr(e) + \
"\n\nWith kind regards"
catch_all_domain = self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain")
response_mail = "auto_backup@%s" % catch_all_domain if catch_all_domain else self.env.user.partner_id.email
msg = ir_mail_server.build_email(response_mail, [backup.email_to_notify],
"Backup from " + backup.host + "(" + backup.sftp_host +
") failed",
message)
ir_mail_server.send_email(msg)
except Exception:
pass

return super(DbBackup, db_backup_obj.search([('is_odoo_sh_instance','=', False)])).schedule_backup()
27 changes: 27 additions & 0 deletions auto_backup_sh/views/odoosh_db_backup_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<odoo>

<!-- Form view inherit form view -->
<record id="odoo_sh_db_backup_inherit_views" model="ir.ui.view">
<field name="name">odoo.sh.db.backup.inherit.view.form</field>
<field name="model">db.backup</field>
<field name="inherit_id" ref="auto_backup.view_backup_config_form"/>
<field name="arch" type="xml">
<field name="days_to_keep" position="after">
<field name="is_odoo_sh_instance" widget="boolean_toggle"/>
</field>
</field>
</record>

<!-- Tree` view inherit form view -->
<record id="odoo_sh_db_backup_inherit_tree_views" model="ir.ui.view">
<field name="name">odoo.sh.db.backup.inherit.view.tree</field>
<field name="model">db.backup</field>
<field name="inherit_id" ref="auto_backup.view_backup_config_tree"/>
<field name="arch" type="xml">
<field name="sftp_host" position="after">
<field name="is_odoo_sh_instance" widget="boolean_toggle"/>
</field>
</field>
</record>

</odoo>

0 comments on commit a826401

Please sign in to comment.