Plain is headed towards 1.0! Subscribe for development updates →

  1import os
  2import time
  3from pathlib import Path
  4
  5import click
  6
  7from plain.cli import register_cli
  8
  9from .core import DatabaseBackups
 10
 11
 12@register_cli("backups")
 13@click.group("backups")
 14def cli():
 15    """Local database backups"""
 16    pass
 17
 18
 19@cli.command("list")
 20def list_backups():
 21    backups_handler = DatabaseBackups()
 22    backups = backups_handler.find_backups()
 23    if not backups:
 24        click.secho("No backups found", fg="yellow")
 25        return
 26
 27    for backup in backups:
 28        click.secho(
 29            f"{backup.name} ({backup.updated_at().strftime('%Y-%m-%d %H:%M:%S')})",
 30            bold=True,
 31        )
 32
 33        for backup_file in backup.iter_files():
 34            size = os.path.getsize(backup_file)
 35            click.echo(f"- {backup_file.name} ({size / 1024 / 1024:.2f} MB)")
 36
 37        click.echo()
 38
 39
 40@cli.command("create")
 41@click.option("--pg-dump", default="pg_dump", envvar="PG_DUMP")
 42@click.argument("backup_name", default="")
 43def create_backup(backup_name, pg_dump):
 44    backups_handler = DatabaseBackups()
 45
 46    if not backup_name:
 47        backup_name = f"backup_{time.strftime('%Y%m%d_%H%M%S')}"
 48
 49    try:
 50        backup_dir = backups_handler.create(
 51            backup_name,
 52            pg_dump=pg_dump,
 53        )
 54    except Exception as e:
 55        click.secho(str(e), fg="red")
 56        exit(1)
 57
 58    click.secho(f"Backup created in {backup_dir.relative_to(Path.cwd())}", fg="green")
 59
 60
 61@cli.command("restore")
 62@click.option("--latest", is_flag=True)
 63@click.option("--pg-restore", default="pg_restore", envvar="PG_RESTORE")
 64@click.argument("backup_name", default="")
 65def restore_backup(backup_name, latest, pg_restore):
 66    backups_handler = DatabaseBackups()
 67
 68    if backup_name and latest:
 69        raise click.UsageError("Only one of --latest or backup_name is allowed")
 70
 71    if not backup_name and not latest:
 72        raise click.UsageError("Backup name or --latest is required")
 73
 74    if not backup_name and latest:
 75        backup_name = backups_handler.find_backups()[0].name
 76
 77    click.secho(f"Restoring backup {backup_name}...", bold=True)
 78
 79    try:
 80        backups_handler.restore(
 81            backup_name,
 82            pg_restore=pg_restore,
 83        )
 84    except Exception as e:
 85        click.secho(str(e), fg="red")
 86        exit(1)
 87    click.echo(f"Backup {backup_name} restored successfully.")
 88
 89
 90@cli.command("delete")
 91@click.argument("backup_name")
 92def delete_backup(backup_name):
 93    backups_handler = DatabaseBackups()
 94    try:
 95        backups_handler.delete(backup_name)
 96    except Exception as e:
 97        click.secho(str(e), fg="red")
 98        return
 99    click.secho(f"Backup {backup_name} deleted", fg="green")
100
101
102@cli.command("clear")
103@click.confirmation_option(prompt="Are you sure you want to delete all backups?")
104def clear_backups():
105    backups_handler = DatabaseBackups()
106    backups = backups_handler.find_backups()
107    for backup in backups:
108        backup.delete()
109    click.secho("All backups deleted", fg="green")