2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-31 13:15:43 +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:
Oliver
2025-10-18 07:28:18 +11:00
committed by GitHub
parent de270a5fe7
commit d34f44221e
11 changed files with 336 additions and 48 deletions

View 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,
)

View File

@@ -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
)

View File

@@ -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

View File

@@ -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 \