Search code examples
djangosqlitepythonanywhere

Scheduled, timestamped sqlite3 .backup?


Running a small db on pythonanywhere, and am trying to set up a scheduled .backup of my sqlite3 database. Is there any way in the command line to add a time/date stamp to the filename, so that it doesn't overwrite the previous days backup?

Here's the code I'm using, if it matters:

sqlite3 db.sqlite3
.backup dbbackup.sqlite3
.quit

Running every 24 hours. The previous day's backup gets overwritten, though. I'd love to just be able to save it as dbbackup.timestamp.sqlite3 or something, so I could have multiple backups available.

Thanks!


Solution

  • I suggest you to handle this case with management commands and cronjob.

    This an example how to do it; save this file eg in yourapp/management/commands/dbackup.py, don't forget to add __init__.py files.

    yourapp/management/__init__.py
    yourapp/management/commands/__init__.py
    yourapp/management/commands/dbackup.py
    

    But, previously add these lines below to your settings.py

    USERNAME_SUPERUSER = 'yourname`
    PASSWORD_SUPERUSER = `yourpassword`
    EMAIL_SUPERUSER = `[email protected]`
    DATABASE_NAME = 'db.sqlite3'
    

    The important tree path project if you deploying at pythonanywhere;

    /home/yourusername/yourproject/manage.py
    /home/yourusername/yourproject/db.sqlite3
    /home/yourusername/yourproject/yourproject/settings.py
    /home/yourusername/yourproject/yourapp/management/commands/dbackup.py
    

    Add these script below into yourapp/management/commands/dbackup.py, you also can custom this script as you need.

    import os
    import time
    from django.conf import settings
    from django.contrib.auth.models import User
    from django.core.management.base import (BaseCommand, CommandError)
    
    USERNAME_SUPERUSER = settings.USERNAME_SUPERUSER
    PASSWORD_SUPERUSER = settings.PASSWORD_SUPERUSER
    EMAIL_SUPERUSER = settings.EMAIL_SUPERUSER
    DATABASE_NAME = settings.DATABASE_NAME #eg: 'db.sqlite3'
    
    
    class Command(BaseCommand):
        help = ('Command to deploy and backup the latest database.')
    
        def add_arguments(self, parser):
            parser.add_argument(
                '-b', '--backup', action='store_true',
                help='Just backup command confirmation.'
            )
    
        def success_info(self, info):
            return self.stdout.write(self.style.SUCCESS(info))
    
        def error_info(self, info):
            return self.stdout.write(self.style.ERROR(info))
    
        def handle(self, *args, **options):
            backup = options['backup']
    
            if backup == False:
                return self.print_help()
    
            # Removing media files, if you need to remove all media files
            # os.system('rm -rf media/images/')
            # self.success_info("[+] Removed media files at `media/images/`")
    
            # Removing database `db.sqlite3`
            if os.path.isfile(DATABASE_NAME):
                # backup the latest database, eg to: `db.2017-02-03.sqlite3`
                backup_database = 'db.%s.sqlite3' % time.strftime('%Y-%m-%d')
                os.rename(DATABASE_NAME, backup_database)
                self.success_info("[+] Backup the database `%s` to %s" % (DATABASE_NAME, backup_database))
    
                # remove the latest database
                os.remove(DATABASE_NAME)
                self.success_info("[+] Removed %s" % DATABASE_NAME)
    
            # Removing all files migrations for `yourapp`
            def remove_migrations(path):
                exclude_files = ['__init__.py', '.gitignore']
                path = os.path.join(settings.BASE_DIR, path)
    
                filelist = [
                    f for f in os.listdir(path)
                    if f.endswith('.py')
                    and f not in exclude_files
                ]
                for f in filelist:
                    os.remove(path + f)
                self.success_info('[+] Removed files migrations for {}'.format(path))
    
            # do remove all files migrations
            remove_migrations('yourapp/migrations/')
    
            # Removing all `.pyc` files
            os.system('find . -name *.pyc -delete')
            self.success_info('[+] Removed all *.pyc files.')
    
            # Creating database migrations
            # These commands should re-generate the new database, eg: `db.sqlite3`
            os.system('python manage.py makemigrations')
            os.system('python manage.py migrate')
            self.success_info('[+] Created database migrations.')
    
            # Creating a superuser
            user = User.objects.create_superuser(
                username=USERNAME_SUPERUSER,
                password=PASSWORD_SUPERUSER,
                email=EMAIL_SUPERUSER
            )
            user.save()
            self.success_info('[+] Created a superuser for `{}`'.format(USERNAME_SUPERUSER))
    

    Setup this command with crontab

    $ sudo crontab -e
    

    And add these following below lines;

    # [minute] [hour] [date] [month] [year]
    59 23 * * * source ~/path/to/yourenv/bin/activate && cd ~/path/to/yourenv/yourproject/ && ./manage.py dbackup -b
    

    But, if you need to deploy at pythonanywhere, you just need to add these..

    Daily at [hour] : [minute] UTC, ... fill the hour=23 and minute=59

    source /home/yourusername/.virtualenvs/yourenv/bin/activate && cd /home/yourusername/yourproject/ && ./manage.py dbackup -b
    

    Update 1

    I suggest you to update the commands to execute the file of manage.py such as os.system('python manage.py makemigrations') with function of call_command;

    from django.core.management import call_command
    call_command('collectstatic', verbosity=3, interactive=False)
    call_command('migrate', 'myapp', verbosity=3, interactive=False)
    

    ...is equal to the following commands typed in terminal:

    $ ./manage.py collectstatic --noinput -v 3
    $ ./manage.py migrate myapp --noinput -v 3
    

    See running management commands from django docs.

    Update 2

    Previous condition is if you need to re-deploy your project and using a fresh database. But, if you only want to backup it by renaming the database, you can using module of shutil.copyfile

    import os
    import time
    import shutil
    from django.conf import settings
    from django.core.management.base import (BaseCommand, CommandError)
    
    DATABASE_NAME = settings.DATABASE_NAME #eg: 'db.sqlite3'
    
    
    class Command(BaseCommand):
        help = ('Command to deploy and backup the latest database.')
    
        def add_arguments(self, parser):
            parser.add_argument(
                '-b', '--backup', action='store_true',
                help='Just backup command confirmation.'
            )
    
        def success_info(self, info):
            return self.stdout.write(self.style.SUCCESS(info))
    
        def error_info(self, info):
            return self.stdout.write(self.style.ERROR(info))
    
        def handle(self, *args, **options):
            backup = options['backup']
    
            if backup == False:
                return self.print_help()
    
            if os.path.isfile(DATABASE_NAME):
                # backup the latest database, eg to: `db.2017-02-29.sqlite3`
                backup_database = 'db.%s.sqlite3' % time.strftime('%Y-%m-%d')
                shutil.copyfile(DATABASE_NAME, backup_database)
                self.success_info("[+] Backup the database `%s` to %s" % (DATABASE_NAME, backup_database))