mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-15 00:38:12 +00:00
Backup update (#10586)
* Update django-dbbackup version * Specify STORAGES option for dbbackup * Add more backup configuration * Support custom date formats * Add connector options * Extend functionality of invoke backup * Add extra options for restore task * Add invoke task for finding additional backups * Small tweaks * Add docs around backup / restore * Fix typo * Add example for GCS storage * More docs
This commit is contained in:
133
src/backend/InvenTree/InvenTree/backup.py
Normal file
133
src/backend/InvenTree/InvenTree/backup.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""Configuration options for InvenTree backup / restore functionality.
|
||||
|
||||
We use the django-dbbackup library to handle backup and restore operations.
|
||||
|
||||
Ref: https://archmonger.github.io/django-dbbackup/latest/configuration/
|
||||
"""
|
||||
|
||||
import InvenTree.config
|
||||
|
||||
|
||||
def get_backup_connector_options() -> dict:
|
||||
"""Options which are specific to the selected backup connector.
|
||||
|
||||
These options apply to the database connector, not to the backup storage.
|
||||
|
||||
Ref: https://archmonger.github.io/django-dbbackup/latest/databases/
|
||||
"""
|
||||
default_options = {'EXCLUDE': ['django_session']}
|
||||
|
||||
# Allow user to specify custom options here if necessary
|
||||
connector_options = InvenTree.config.get_setting(
|
||||
'INVENTREE_BACKUP_CONNECTOR_OPTIONS',
|
||||
'backup_connector_options',
|
||||
default_value=default_options,
|
||||
typecast=dict,
|
||||
)
|
||||
|
||||
return connector_options
|
||||
|
||||
|
||||
def get_backup_storage_backend() -> str:
|
||||
"""Return the backup storage backend string."""
|
||||
backend = InvenTree.config.get_setting(
|
||||
'INVENTREE_BACKUP_STORAGE',
|
||||
'backup_storage',
|
||||
'django.core.files.storage.FileSystemStorage',
|
||||
)
|
||||
|
||||
# Validate that the selected backend is valid
|
||||
# It must be able to be imported, and a class must be found
|
||||
# It also must be a subclass of django.core.files.storage.Storage
|
||||
try:
|
||||
from django.core.files.storage import Storage
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
backend_class = import_string(backend)
|
||||
|
||||
if not issubclass(backend_class, Storage):
|
||||
raise TypeError(
|
||||
f"Backup storage backend '{backend}' is not a valid Storage class"
|
||||
)
|
||||
except Exception as e:
|
||||
raise ImportError(f"Could not load backup storage backend '{backend}': {e}")
|
||||
|
||||
return backend
|
||||
|
||||
|
||||
def get_backup_storage_options() -> dict:
|
||||
"""Return the backup storage options dictionary."""
|
||||
# Default backend options which are used for FileSystemStorage
|
||||
default_options = {'location': InvenTree.config.get_backup_dir()}
|
||||
|
||||
options = InvenTree.config.get_setting(
|
||||
'INVENTREE_BACKUP_OPTIONS',
|
||||
'backup_options',
|
||||
default_value=default_options,
|
||||
typecast=dict,
|
||||
)
|
||||
|
||||
if not isinstance(options, dict):
|
||||
raise ValueError('Backup storage options must be a dictionary')
|
||||
|
||||
return options
|
||||
|
||||
|
||||
def backup_email_on_error() -> bool:
|
||||
"""Return whether to send emails to admins on backup failure."""
|
||||
return InvenTree.config.get_setting(
|
||||
'INVENTREE_BACKUP_SEND_EMAIL',
|
||||
'backup_send_email',
|
||||
default_value=False,
|
||||
typecast=bool,
|
||||
)
|
||||
|
||||
|
||||
def backup_email_prefix() -> str:
|
||||
"""Return the email subject prefix for backup emails."""
|
||||
return InvenTree.config.get_setting(
|
||||
'INVENTREE_BACKUP_EMAIL_PREFIX',
|
||||
'backup_email_prefix',
|
||||
default_value='[InvenTree Backup]',
|
||||
typecast=str,
|
||||
)
|
||||
|
||||
|
||||
def backup_gpg_recipient() -> str:
|
||||
"""Return the GPG recipient for encrypted backups."""
|
||||
return InvenTree.config.get_setting(
|
||||
'INVENTREE_BACKUP_GPG_RECIPIENT',
|
||||
'backup_gpg_recipient',
|
||||
default_value='',
|
||||
typecast=str,
|
||||
)
|
||||
|
||||
|
||||
def backup_date_format() -> str:
|
||||
"""Return the date format string for database backups."""
|
||||
return InvenTree.config.get_setting(
|
||||
'INVENTREE_BACKUP_DATE_FORMAT',
|
||||
'backup_date_format',
|
||||
default_value='%Y-%m-%d-%H%M%S',
|
||||
typecast=str,
|
||||
)
|
||||
|
||||
|
||||
def backup_filename_template() -> str:
|
||||
"""Return the filename template for database backups."""
|
||||
return InvenTree.config.get_setting(
|
||||
'INVENTREE_BACKUP_DATABASE_FILENAME_TEMPLATE',
|
||||
'backup_database_filename_template',
|
||||
default_value='InvenTree-db-{datetime}.{extension}',
|
||||
typecast=str,
|
||||
)
|
||||
|
||||
|
||||
def backup_media_filename_template() -> str:
|
||||
"""Return the filename template for media backups."""
|
||||
return InvenTree.config.get_setting(
|
||||
'INVENTREE_BACKUP_MEDIA_FILENAME_TEMPLATE',
|
||||
'backup_media_filename_template',
|
||||
default_value='InvenTree-media-{datetime}.{extension}',
|
||||
typecast=str,
|
||||
)
|
||||
@@ -24,6 +24,7 @@ from django.http import Http404, HttpResponseGone
|
||||
import structlog
|
||||
from corsheaders.defaults import default_headers as default_cors_headers
|
||||
|
||||
import InvenTree.backup
|
||||
from InvenTree.cache import get_cache_config, is_global_cache_enabled
|
||||
from InvenTree.config import (
|
||||
get_boolean_setting,
|
||||
@@ -247,22 +248,32 @@ if DEBUG and 'collectstatic' not in sys.argv:
|
||||
STATICFILES_DIRS.append(BASE_DIR.joinpath('plugin', 'samples', 'static'))
|
||||
|
||||
# Database backup options
|
||||
# Ref: https://django-dbbackup.readthedocs.io/en/master/configuration.html
|
||||
DBBACKUP_SEND_EMAIL = False
|
||||
DBBACKUP_STORAGE = get_setting(
|
||||
'INVENTREE_BACKUP_STORAGE',
|
||||
'backup_storage',
|
||||
'django.core.files.storage.FileSystemStorage',
|
||||
)
|
||||
# Ref: https://archmonger.github.io/django-dbbackup/latest/configuration/
|
||||
|
||||
# Default backup configuration
|
||||
DBBACKUP_STORAGE_OPTIONS = get_setting(
|
||||
'INVENTREE_BACKUP_OPTIONS',
|
||||
'backup_options',
|
||||
default_value={'location': config.get_backup_dir()},
|
||||
typecast=dict,
|
||||
)
|
||||
# For core backup functionality, refer to the STORAGES["dbbackup"] entry (below)
|
||||
|
||||
DBBACKUP_DATE_FORMAT = InvenTree.backup.backup_date_format()
|
||||
DBBACKUP_FILENAME_TEMPLATE = InvenTree.backup.backup_filename_template()
|
||||
DBBACKUP_MEDIA_FILENAME_TEMPLATE = InvenTree.backup.backup_media_filename_template()
|
||||
|
||||
DBBACKUP_GPG_RECIPIENT = InvenTree.backup.backup_gpg_recipient()
|
||||
|
||||
DBBACKUP_SEND_EMAIL = InvenTree.backup.backup_email_on_error()
|
||||
DBBACKUP_EMAIL_SUBJECT_PREFIX = InvenTree.backup.backup_email_prefix()
|
||||
|
||||
DBBACKUP_CONNECTORS = {'default': InvenTree.backup.get_backup_connector_options()}
|
||||
|
||||
# Data storage options
|
||||
STORAGES = {
|
||||
'default': {'BACKEND': 'django.core.files.storage.FileSystemStorage'},
|
||||
'staticfiles': {'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage'},
|
||||
'dbbackup': {
|
||||
'BACKEND': InvenTree.backup.get_backup_storage_backend(),
|
||||
'OPTIONS': InvenTree.backup.get_backup_storage_options(),
|
||||
},
|
||||
}
|
||||
|
||||
# Enable django admin interface?
|
||||
INVENTREE_ADMIN_ENABLED = get_boolean_setting(
|
||||
'INVENTREE_ADMIN_ENABLED', config_key='admin_enabled', default_value=True
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ django-anymail[amazon_ses,postal] # Email backend for various providers
|
||||
django-allauth[mfa,socialaccount,saml,openid] # SSO for external providers via OpenID
|
||||
django-cleanup # Automated deletion of old / unused uploaded files
|
||||
django-cors-headers # CORS headers extension for DRF
|
||||
django-dbbackup # Backup / restore of database and media files
|
||||
django-dbbackup>=5.0.0 # Backup / restore of database and media files
|
||||
django-error-report-2 # Error report viewer for the admin interface
|
||||
django-filter # Extended filtering options
|
||||
django-flags # Feature flags
|
||||
|
||||
@@ -438,9 +438,9 @@ django-cors-headers==4.9.0 \
|
||||
--hash=sha256:15c7f20727f90044dcee2216a9fd7303741a864865f0c3657e28b7056f61b449 \
|
||||
--hash=sha256:fe5d7cb59fdc2c8c646ce84b727ac2bca8912a247e6e68e1fb507372178e59e8
|
||||
# via -r src/backend/requirements.in
|
||||
django-dbbackup==4.3.0 \
|
||||
--hash=sha256:3549c8ccfdb167f20ca2c26eb0dfa55c79aa3f31ea4e4dbfa0816bc18ceec6dc \
|
||||
--hash=sha256:b4003b353d49d914ffbc033c793198426572d0a2b137ec795ddb2fb82225b960
|
||||
django-dbbackup==5.0.0 \
|
||||
--hash=sha256:a0301b14a4bb3c7243a2fde76d09f8f572f16cd7639f75f4cd42d898fc1b82a2 \
|
||||
--hash=sha256:aa9cc88e1413adfec0e547dd91e0afed6dbb91a02459697663a9b988dbc71f18
|
||||
# via -r src/backend/requirements.in
|
||||
django-error-report-2==0.4.2 \
|
||||
--hash=sha256:1dd99c497af09b7ea99f5fbaf910501838150a9d5390796ea00e187bc62f6c1b \
|
||||
@@ -1295,10 +1295,6 @@ python3-saml==1.16.0 \
|
||||
--hash=sha256:97c9669aecabc283c6e5fb4eb264f446b6e006f5267d01c9734f9d8bffdac133 \
|
||||
--hash=sha256:c49097863c278ff669a337a96c46dc1f25d16307b4bb2679d2d1733cc4f5176a
|
||||
# via django-allauth
|
||||
pytz==2025.2 \
|
||||
--hash=sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3 \
|
||||
--hash=sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00
|
||||
# via django-dbbackup
|
||||
pyyaml==6.0.3 \
|
||||
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
|
||||
--hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \
|
||||
|
||||
Reference in New Issue
Block a user