From d5a83722de3473ce367c2209f9145c59797d48fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20I=C3=9Fbr=C3=BCcker?= Date: Sun, 7 Apr 2024 17:49:30 +0200 Subject: [PATCH] Add full backup method (#686) --- bookmarks/management/commands/backup.py | 5 ++ bookmarks/management/commands/full_backup.py | 62 ++++++++++++++++++++ docs/backup.md | 61 +++++++++++++++---- 3 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 bookmarks/management/commands/full_backup.py diff --git a/bookmarks/management/commands/backup.py b/bookmarks/management/commands/backup.py index 94b2d3f..8ad1c1c 100644 --- a/bookmarks/management/commands/backup.py +++ b/bookmarks/management/commands/backup.py @@ -24,3 +24,8 @@ class Command(BaseCommand): source_db.close() self.stdout.write(self.style.SUCCESS(f"Backup created at {destination}")) + self.stdout.write( + self.style.WARNING( + "This backup method is deprecated and may be removed in the future. Please use the full_backup command instead, which creates backup zip file with all contents of the data folder." + ) + ) diff --git a/bookmarks/management/commands/full_backup.py b/bookmarks/management/commands/full_backup.py new file mode 100644 index 0000000..4924156 --- /dev/null +++ b/bookmarks/management/commands/full_backup.py @@ -0,0 +1,62 @@ +import sqlite3 +import os +import tempfile +import zipfile + +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = "Creates a backup of the linkding data folder" + + def add_arguments(self, parser): + parser.add_argument("backup_file", type=str, help="Backup zip file destination") + + def handle(self, *args, **options): + backup_file = options["backup_file"] + with zipfile.ZipFile(backup_file, "w", zipfile.ZIP_DEFLATED) as zip_file: + # Backup the database + self.stdout.write("Create database backup...") + with tempfile.TemporaryDirectory() as temp_dir: + backup_db_file = os.path.join(temp_dir, "db.sqlite3") + self.backup_database(backup_db_file) + zip_file.write(backup_db_file, "db.sqlite3") + + # Backup the assets folder + if not os.path.exists(os.path.join("data", "assets")): + self.stdout.write( + self.style.WARNING("No assets folder found. Skipping...") + ) + else: + self.stdout.write("Backup bookmark assets...") + assets_folder = os.path.join("data", "assets") + for root, _, files in os.walk(assets_folder): + for file in files: + file_path = os.path.join(root, file) + zip_file.write(file_path, os.path.join("assets", file)) + + # Backup the favicons folder + if not os.path.exists(os.path.join("data", "favicons")): + self.stdout.write( + self.style.WARNING("No favicons folder found. Skipping...") + ) + else: + self.stdout.write("Backup bookmark favicons...") + favicons_folder = os.path.join("data", "favicons") + for root, _, files in os.walk(favicons_folder): + for file in files: + file_path = os.path.join(root, file) + zip_file.write(file_path, os.path.join("favicons", file)) + + self.stdout.write(self.style.SUCCESS(f"Backup created at {backup_file}")) + + def backup_database(self, backup_db_file): + def progress(status, remaining, total): + self.stdout.write(f"Copied {total-remaining} of {total} pages...") + + source_db = sqlite3.connect(os.path.join("data", "db.sqlite3")) + backup_db = sqlite3.connect(backup_db_file) + with backup_db: + source_db.backup(backup_db, pages=50, progress=progress) + backup_db.close() + source_db.close() diff --git a/docs/backup.md b/docs/backup.md index 03a1494..e43bbb9 100644 --- a/docs/backup.md +++ b/docs/backup.md @@ -4,24 +4,56 @@ Linkding stores all data in the application's data folder. The full path to that folder in the Docker container is `/etc/linkding/data`. As described in the installation docs, you should mount the `/etc/linkding/data` folder to a folder on your host system. -The data folder contains the following contents: +The data folder contains the following contents that are relevant for backups: - `db.sqlite3` - the SQLite database +- `assets` - folder that contains HTML snapshots of bookmarks - `favicons` - folder that contains downloaded favicons The following sections explain how to back up the individual contents. -## Database +## Full backup -This section describes several methods on how to back up the contents of the SQLite database. +lindking provides a CLI command to create a full backup of the data folder. This creates a zip file that contains backups of the database, assets, and favicons. + +> [!NOTE] +> This method assumes that you are using the default SQLite database. +> If you are using a different database, such as Postgres, you'll have to back up the database and other contents of the data folder manually. + +To create a full backup, execute the following command: +```shell +docker exec -it linkding python manage.py full_backup /etc/linkding/data/backup.zip +``` +This creates a `backup.zip` file in the Docker container under `/etc/linkding/data`. + +To copy the backup file to your host system, execute the following command: +```shell +docker cp linkding:/etc/linkding/data/backup.zip backup.zip +``` +This copies the backup file from the Docker container to the current folder on your host system. +Now you can move that file to your backup location. + +To restore a backup: +- Extract the zip file in a folder of your new installation. +- Rename the extracted folder to `data`. +- When starting the Docker container, mount that folder to `/etc/linkding/data` as explained in the README. +- Then start the Docker container. + +## Alternative backup methods + +If you can't use the full backup method, this section describes alternatives how to back up the individual contents of the data folder. + +### SQLite database backup + +linkding includes a CLI command for creating a backup copy of the database. > [!WARNING] > While the SQLite database is just a single file, it is not recommended to just copy that file. > This method is not transaction safe and may result in a [corrupted database](https://www.sqlite.org/howtocorrupt.html). > Use one of the backup methods described below. -### Using the backup command - -linkding includes a CLI command for creating a backup copy of the database. +> [!WARNING] +> This method is deprecated and may be removed in the future. +> Please use the full backup method described above. To create a backup, execute the following command: ```shell @@ -38,12 +70,12 @@ Now you can move that file to your backup location. To restore the backup, just copy the backup file to the data folder of your new installation and rename it to `db.sqlite3`. Then start the Docker container. -### Using the SQLite dump function +### SQLite database SQL dump Requires [SQLite](https://www.sqlite.org/index.html) to be installed on your host system. With this method you create a plain text file with the SQL statements to recreate the SQLite database. -To create a backup, execute the following command in the data folder: +To create a backup, execute the following command in the data folder on your host system: ```shell sqlite3 db.sqlite3 .dump > backup.sql ``` @@ -56,8 +88,8 @@ Using git, you can commit the changes, followed by a git push to a remote reposi This is the least technical option to back up bookmarks, but has several limitations: - It does not export user profiles. - It only exports your own bookmarks, not those of other users. -- It does not export archived bookmarks. - It does not export URLs of snapshots on the Internet Archive Wayback machine. +- It does not export HTML snapshots of bookmarks. Even if you backup and restore the assets folder, the bookmarks will not be linked to the snapshots anymore. - It does not export favicons. Only use this method if you are fine with the above limitations. @@ -70,7 +102,16 @@ To restore bookmarks, open the general settings on your new installation. In the Import section, click on the *Choose file* button to select the HTML file you downloaded before. Then click on the *Import* button to import the bookmarks. -## Favicons +### Assets + +If you are using the HTML snapshots feature, you should also do backups of the `assets` folder. +It contains the HTML snapshots files of your bookmarks which are referenced from the database. + +To back up the assets, then you have to copy the `assets` folder to your backup location. + +To restore the assets, copy the `assets` folder back to the data folder of your new installation. + +### Favicons Doing a backup of the icons is optional, as they can be downloaded again.