🚧 Under development. DBS is in active development and its API may change.
Created by Sudum Technology — Research and Development sector. Our very tiny contribution to this world.
A backup library you drop into a Django project's source. It reads your models, relations and files and writes one encrypted file that is redundant and self-healing: every backup stores two copies of the data plus Reed-Solomon parity, so silent corruption — the kind a non-ECC RAM stick produces — is detected and repaired on restore instead of quietly poisoning your data.
io ──► security (Argon2id + AES-256-GCM) ──► integrity (2 copies · per-block
hashes · Reed-Solomon) ──► data (models · relations · files)
- Two self-healing copies in one file plus Reed-Solomon parity for bit-rot.
- Per-field mapping: value, embedded file, or file-path.
- Passphrase-derived key, never stored (Argon2id).
- Per-file and per-block integrity hashes.
- CLI · admin-UI download · SFTP.
pip install django-dbs # core
pip install "django-dbs[ssh]" # + SFTP transport (paramiko)Add the app:
INSTALLED_APPS = [..., "dbs"]By default DBS auto-discovers every model, treats FileField/ImageField as
files, and preserves relations. Register a model only to override the defaults —
in a dbs.py module inside your app (auto-discovered like admin.py):
# myapp/dbs.py
from dbs import backup_registry, FieldType, ModelBackup
from .models import Invoice
@backup_registry.register(Invoice)
class InvoiceBackup(ModelBackup):
overrides = {
"scanned_pdf_path": FieldType.FILE_PATH, # CharField holding a path -> embed the file
"render_cache": FieldType.EXCLUDE, # don't back this column up
}
file_roots = ["/srv/myapp/uploads"] # extra non-model file treesFieldType values: VALUE (default), FILE (embed a FileField's bytes),
FILE_PATH (a string column whose path's file is embedded), EXCLUDE.
python manage.py dbs_backup backup.dbs # prompts for a passphrase
python manage.py dbs_validate backup.dbs # structural check, no passphrase
python manage.py dbs_validate backup.dbs -p secret # + verify decryption
python manage.py dbs_restore backup.dbs # restore rows + filesThe passphrase comes from --passphrase, then $DBS_PASSPHRASE, then a prompt.
# urls.py
urlpatterns += [path("dbs/", include("dbs.contrib.urls"))]Superusers can then visit /dbs/backup/ to download an encrypted backup and
/dbs/restore/ to upload one. The passphrase is entered in the form and never
stored server-side.
# settings.py — profiles reference a key by path; no secrets embedded
DBS_SSH_TARGETS = {
"offsite": {
"host": "backups.example.com", "username": "deploy",
"key_filename": "/home/deploy/.ssh/id_ed25519",
"remote_dir": "/var/backups/myproject",
"known_hosts": "/home/deploy/.ssh/known_hosts",
}
}from dbs import create_backup
from dbs.transports import SSHTarget, push_backup
data = create_backup("my passphrase")
push_backup(data, "backup.dbs", SSHTarget.from_settings("offsite"))from dbs import create_backup, restore_backup, validate_backup
blob = create_backup("passphrase", output="backup.dbs")
report = validate_backup(blob, "passphrase") # report.ok / report.summary()
result = restore_backup(blob, "passphrase") # result.healed is True if it repaired corruptionOn write, the encrypted stream is split into blocks; each block gets a BLAKE2b hash and a layer of Reed-Solomon parity, and the whole stream is stored twice (plus the header and manifest are stored twice). On read, each block is taken from whichever copy verifies; sparse bit-flips are corrected in place by Reed-Solomon even when both copies are hit. Every recovered block is checked against its stored hash, so a mis-correction can never slip through — and a freshly written backup is re-read and verified end-to-end (verify-after-write) before the command reports success.
What it can recover from: whole-block loss in one copy, and sparse byte errors (up to the parity budget, ~8 bytes per 255-byte codeword by default) in both copies. What it cannot: a block destroyed beyond the parity budget in both copies — DBS then refuses to restore and reports exactly which blocks failed, rather than producing silently wrong data.
- Argon2id derives a key from your passphrase (memory-hard ⇒ brute-force
resistant). Raise
KDFParamscost for more resistance. - Envelope encryption: a random data key encrypts the payload with AES-256-GCM; that data key is wrapped by the passphrase-derived key. The file stores only the salt, Argon2 parameters and the wrapped key — never the passphrase and never the raw data key.
- A wrong passphrase fails the GCM tag check and is reported as such; it can never yield partial/garbage data.
| Setting | Purpose |
|---|---|
DBS_EXCLUDE_MODELS |
["app.Model", ...] to skip (defaults skip contenttypes, permissions, admin log, sessions). |
DBS_FILE_ROOTS |
Extra directories embedded in every backup. |
DBS_SSH_TARGETS |
Named SFTP connection profiles. |
pip install -e ".[dev]"
pytestMIT