2020-03-26 18:58:50 +00:00
|
|
|
import os
|
|
|
|
import datetime
|
2020-06-25 17:53:31 +00:00
|
|
|
import json
|
2020-03-26 18:58:50 +00:00
|
|
|
import tarfile
|
|
|
|
import hashlib
|
|
|
|
import frappe
|
|
|
|
import boto3
|
|
|
|
|
2020-04-16 06:19:40 +00:00
|
|
|
from new import get_password
|
2020-03-26 18:58:50 +00:00
|
|
|
from push_backup import DATE_FORMAT, check_environment_variables
|
2020-07-09 11:57:54 +00:00
|
|
|
from utils import run_command
|
2020-03-26 18:58:50 +00:00
|
|
|
from frappe.utils import get_sites, random_string
|
2020-06-25 17:53:31 +00:00
|
|
|
from frappe.installer import make_conf, get_conf_params, make_site_dirs, update_site_config
|
2020-06-29 17:58:10 +00:00
|
|
|
from check_connection import get_site_config, get_config, COMMON_SITE_CONFIG_FILE
|
2020-03-26 18:58:50 +00:00
|
|
|
|
2020-04-28 20:15:59 +00:00
|
|
|
|
2020-03-26 18:58:50 +00:00
|
|
|
def list_directories(path):
|
|
|
|
directories = []
|
|
|
|
for name in os.listdir(path):
|
|
|
|
if os.path.isdir(os.path.join(path, name)):
|
|
|
|
directories.append(name)
|
|
|
|
return directories
|
|
|
|
|
2020-04-28 20:15:59 +00:00
|
|
|
|
2020-03-26 18:58:50 +00:00
|
|
|
def get_backup_dir():
|
|
|
|
return os.path.join(
|
|
|
|
os.path.expanduser('~'),
|
|
|
|
'backups'
|
|
|
|
)
|
|
|
|
|
2020-04-28 20:15:59 +00:00
|
|
|
|
2020-07-10 04:37:22 +00:00
|
|
|
def decompress_db(database_file, site):
|
|
|
|
command = ["gunzip", "-c", database_file]
|
|
|
|
with open(database_file.replace(".gz", ""), "w") as db_file:
|
|
|
|
print('Extract Database GZip for site {}'.format(site))
|
|
|
|
run_command(command, stdout=db_file)
|
2020-03-26 18:58:50 +00:00
|
|
|
|
2020-04-28 20:15:59 +00:00
|
|
|
|
2020-06-25 17:53:31 +00:00
|
|
|
def restore_database(files_base, site_config_path, site):
|
2020-03-27 10:37:12 +00:00
|
|
|
# restore database
|
2020-03-26 18:58:50 +00:00
|
|
|
database_file = files_base + '-database.sql.gz'
|
2020-07-10 04:37:22 +00:00
|
|
|
decompress_db(database_file, site)
|
2020-03-26 18:58:50 +00:00
|
|
|
config = get_config()
|
|
|
|
|
2020-06-29 17:58:10 +00:00
|
|
|
# Set db_type if it exists in backup site_config.json
|
|
|
|
set_key_in_site_config('db_type', site, site_config_path)
|
|
|
|
# Set db_host if it exists in backup site_config.json
|
|
|
|
set_key_in_site_config('db_host', site, site_config_path)
|
|
|
|
# Set db_port if it exists in backup site_config.json
|
|
|
|
set_key_in_site_config('db_port', site, site_config_path)
|
2020-03-26 18:58:50 +00:00
|
|
|
|
2020-06-29 17:58:10 +00:00
|
|
|
# get updated site_config
|
|
|
|
site_config = get_site_config(site)
|
2020-03-26 18:58:50 +00:00
|
|
|
|
2020-06-29 17:58:10 +00:00
|
|
|
# if no db_type exists, default to mariadb
|
|
|
|
db_type = site_config.get('db_type', 'mariadb')
|
|
|
|
is_database_restored = False
|
|
|
|
|
|
|
|
if db_type == 'mariadb':
|
|
|
|
restore_mariadb(
|
|
|
|
config=config,
|
|
|
|
site_config=site_config,
|
|
|
|
database_file=database_file)
|
|
|
|
is_database_restored = True
|
|
|
|
elif db_type == 'postgres':
|
|
|
|
restore_postgres(
|
|
|
|
config=config,
|
|
|
|
site_config=site_config,
|
|
|
|
database_file=database_file)
|
|
|
|
is_database_restored = True
|
|
|
|
|
|
|
|
if is_database_restored:
|
|
|
|
# Set encryption_key if it exists in backup site_config.json
|
|
|
|
set_key_in_site_config('encryption_key', site, site_config_path)
|
|
|
|
|
|
|
|
|
|
|
|
def set_key_in_site_config(key, site, site_config_path):
|
|
|
|
site_config = get_site_config_from_path(site_config_path)
|
|
|
|
value = site_config.get(key)
|
|
|
|
if value:
|
|
|
|
print('Set {key} in site config for site: {site}'.format(key=key, site=site))
|
|
|
|
update_site_config(key, value,
|
|
|
|
site_config_path=os.path.join(os.getcwd(), site, "site_config.json"))
|
|
|
|
|
|
|
|
|
|
|
|
def get_site_config_from_path(site_config_path):
|
|
|
|
site_config = dict()
|
2020-06-25 17:53:31 +00:00
|
|
|
if os.path.exists(site_config_path):
|
|
|
|
with open(site_config_path, 'r') as sc:
|
|
|
|
site_config = json.load(sc)
|
2020-06-29 17:58:10 +00:00
|
|
|
return site_config
|
2020-06-25 17:53:31 +00:00
|
|
|
|
2020-04-28 20:15:59 +00:00
|
|
|
|
2020-03-26 18:58:50 +00:00
|
|
|
def restore_files(files_base):
|
|
|
|
public_files = files_base + '-files.tar'
|
|
|
|
# extract tar
|
|
|
|
public_tar = tarfile.open(public_files)
|
|
|
|
print('Extracting {}'.format(public_files))
|
|
|
|
public_tar.extractall()
|
|
|
|
|
2020-04-28 20:15:59 +00:00
|
|
|
|
2020-03-26 18:58:50 +00:00
|
|
|
def restore_private_files(files_base):
|
|
|
|
private_files = files_base + '-private-files.tar'
|
|
|
|
private_tar = tarfile.open(private_files)
|
|
|
|
print('Extracting {}'.format(private_files))
|
|
|
|
private_tar.extractall()
|
|
|
|
|
2020-04-28 20:15:59 +00:00
|
|
|
|
2020-03-26 18:58:50 +00:00
|
|
|
def pull_backup_from_s3():
|
|
|
|
check_environment_variables()
|
|
|
|
|
|
|
|
# https://stackoverflow.com/a/54672690
|
|
|
|
s3 = boto3.resource(
|
|
|
|
's3',
|
2020-04-26 17:18:35 +00:00
|
|
|
region_name=os.environ.get('REGION'),
|
2020-03-26 18:58:50 +00:00
|
|
|
aws_access_key_id=os.environ.get('ACCESS_KEY_ID'),
|
|
|
|
aws_secret_access_key=os.environ.get('SECRET_ACCESS_KEY'),
|
|
|
|
endpoint_url=os.environ.get('ENDPOINT_URL')
|
|
|
|
)
|
|
|
|
|
|
|
|
bucket_dir = os.environ.get('BUCKET_DIR')
|
|
|
|
bucket_name = os.environ.get('BUCKET_NAME')
|
|
|
|
bucket = s3.Bucket(bucket_name)
|
|
|
|
|
|
|
|
# Change directory to /home/frappe/backups
|
|
|
|
os.chdir(get_backup_dir())
|
|
|
|
|
2020-05-22 09:45:33 +00:00
|
|
|
backup_files = []
|
|
|
|
sites = set()
|
|
|
|
site_timestamps = set()
|
|
|
|
download_backups = []
|
|
|
|
|
2020-04-28 20:15:59 +00:00
|
|
|
for obj in bucket.objects.filter(Prefix=bucket_dir):
|
|
|
|
backup_file = obj.key.replace(os.path.join(bucket_dir, ''), '')
|
2020-05-22 09:45:33 +00:00
|
|
|
backup_files.append(backup_file)
|
|
|
|
site_name, timestamp, backup_type = backup_file.split('/')
|
|
|
|
site_timestamp = site_name + '/' + timestamp
|
|
|
|
sites.add(site_name)
|
|
|
|
site_timestamps.add(site_timestamp)
|
|
|
|
|
|
|
|
# sort sites for latest backups
|
|
|
|
for site in sites:
|
|
|
|
backup_timestamps = []
|
|
|
|
for site_timestamp in site_timestamps:
|
|
|
|
site_name, timestamp = site_timestamp.split('/')
|
|
|
|
if site == site_name:
|
|
|
|
timestamp_datetime = datetime.datetime.strptime(
|
|
|
|
timestamp, DATE_FORMAT
|
|
|
|
)
|
|
|
|
backup_timestamps.append(timestamp)
|
|
|
|
download_backups.append(site + '/' + max(backup_timestamps))
|
|
|
|
|
|
|
|
# Only download latest backups
|
|
|
|
for backup_file in backup_files:
|
|
|
|
for backup in download_backups:
|
|
|
|
if backup in backup_file:
|
|
|
|
if not os.path.exists(os.path.dirname(backup_file)):
|
|
|
|
os.makedirs(os.path.dirname(backup_file))
|
|
|
|
print('Downloading {}'.format(backup_file))
|
|
|
|
bucket.download_file(bucket_dir + '/' + backup_file, backup_file)
|
2020-03-26 18:58:50 +00:00
|
|
|
|
|
|
|
os.chdir(os.path.join(os.path.expanduser('~'), 'frappe-bench', 'sites'))
|
|
|
|
|
2020-04-28 20:15:59 +00:00
|
|
|
|
2020-06-29 17:58:10 +00:00
|
|
|
def restore_postgres(config, site_config, database_file):
|
|
|
|
# common config
|
|
|
|
common_site_config_path = os.path.join(os.getcwd(), COMMON_SITE_CONFIG_FILE)
|
|
|
|
|
|
|
|
db_root_user = config.get('root_login')
|
|
|
|
if not db_root_user:
|
|
|
|
postgres_user = os.environ.get('DB_ROOT_USER')
|
|
|
|
if not postgres_user:
|
|
|
|
print('Variable DB_ROOT_USER not set')
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
db_root_user = postgres_user
|
|
|
|
update_site_config(
|
|
|
|
"root_login",
|
|
|
|
db_root_user,
|
|
|
|
validate=False,
|
|
|
|
site_config_path=common_site_config_path)
|
|
|
|
|
|
|
|
db_root_password = config.get('root_password')
|
|
|
|
if not db_root_password:
|
|
|
|
root_password = get_password('POSTGRES_PASSWORD')
|
|
|
|
if not root_password:
|
|
|
|
print('Variable POSTGRES_PASSWORD not set')
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
db_root_password = root_password
|
|
|
|
update_site_config(
|
|
|
|
"root_password",
|
|
|
|
db_root_password,
|
|
|
|
validate=False,
|
|
|
|
site_config_path=common_site_config_path)
|
|
|
|
|
|
|
|
# site config
|
|
|
|
db_host = site_config.get('db_host')
|
2020-06-30 01:02:46 +00:00
|
|
|
db_port = site_config.get('db_port', 5432)
|
2020-06-29 17:58:10 +00:00
|
|
|
db_name = site_config.get('db_name')
|
|
|
|
db_password = site_config.get('db_password')
|
|
|
|
|
2020-07-10 09:34:29 +00:00
|
|
|
psql_command = ["psql"]
|
|
|
|
psql_uri = f"postgres://{db_root_user}:{db_root_password}@{db_host}:{db_port}"
|
2020-06-29 17:58:10 +00:00
|
|
|
|
|
|
|
print('Restoring PostgreSQL')
|
2020-07-10 09:34:29 +00:00
|
|
|
run_command(psql_command + [psql_uri, "-c", f"DROP DATABASE IF EXISTS \"{db_name}\""])
|
|
|
|
run_command(psql_command + [psql_uri, "-c", f"DROP USER IF EXISTS {db_name}"])
|
|
|
|
run_command(psql_command + [psql_uri, "-c", f"CREATE DATABASE \"{db_name}\""])
|
|
|
|
run_command(psql_command + [psql_uri, "-c", f"CREATE user {db_name} password '{db_password}'"])
|
|
|
|
run_command(psql_command + [psql_uri, "-c", f"GRANT ALL PRIVILEGES ON DATABASE \"{db_name}\" TO {db_name}"])
|
2020-07-10 10:48:25 +00:00
|
|
|
with open(database_file.replace('.gz', ''), 'r') as db_file:
|
|
|
|
run_command(psql_command + [f"{psql_uri}/{db_name}", "<"], stdin=db_file)
|
2020-06-29 17:58:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
def restore_mariadb(config, site_config, database_file):
|
|
|
|
db_root_password = get_password('MYSQL_ROOT_PASSWORD')
|
|
|
|
if not db_root_password:
|
|
|
|
print('Variable MYSQL_ROOT_PASSWORD not set')
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
db_root_user = os.environ.get("DB_ROOT_USER", 'root')
|
|
|
|
|
2020-06-30 01:02:46 +00:00
|
|
|
db_host = site_config.get('db_host', config.get('db_host'))
|
2020-07-10 13:51:59 +00:00
|
|
|
db_port = site_config.get('db_port', config.get('db_port', 3306))
|
2020-07-09 11:31:50 +00:00
|
|
|
db_name = site_config.get('db_name')
|
|
|
|
db_password = site_config.get('db_password')
|
2020-06-30 01:02:46 +00:00
|
|
|
|
2020-06-29 17:58:10 +00:00
|
|
|
# mysql command prefix
|
2020-07-09 11:31:50 +00:00
|
|
|
mysql_command = ["mysql", f"-u{db_root_user}", f"-h{db_host}", f"-p{db_root_password}", f"-P{db_port}"]
|
2020-06-29 17:58:10 +00:00
|
|
|
|
|
|
|
# drop db if exists for clean restore
|
2020-07-10 13:51:59 +00:00
|
|
|
drop_database = mysql_command + ["-e", f"DROP DATABASE IF EXISTS `{db_name}`;"]
|
2020-07-09 11:31:50 +00:00
|
|
|
run_command(drop_database)
|
2020-06-29 17:58:10 +00:00
|
|
|
|
|
|
|
# create db
|
2020-07-10 13:51:59 +00:00
|
|
|
create_database = mysql_command + ["-e", f"CREATE DATABASE IF NOT EXISTS `{db_name}`;"]
|
2020-07-09 11:31:50 +00:00
|
|
|
run_command(create_database)
|
2020-06-29 17:58:10 +00:00
|
|
|
|
|
|
|
# create user
|
2020-07-10 13:51:59 +00:00
|
|
|
create_user = mysql_command + ["-e", f"CREATE USER IF NOT EXISTS '{db_name}'@'%' IDENTIFIED BY '{db_password}'; FLUSH PRIVILEGES;"]
|
2020-07-09 11:31:50 +00:00
|
|
|
run_command(create_user)
|
2020-06-29 17:58:10 +00:00
|
|
|
|
|
|
|
# grant db privileges to user
|
2020-07-10 13:51:59 +00:00
|
|
|
grant_privileges = mysql_command + ["-e", f"GRANT ALL PRIVILEGES ON `{db_name}`.* TO '{db_name}'@'%' IDENTIFIED BY '{db_password}'; FLUSH PRIVILEGES;"]
|
2020-07-09 11:31:50 +00:00
|
|
|
run_command(grant_privileges)
|
2020-06-29 17:58:10 +00:00
|
|
|
|
|
|
|
print('Restoring MariaDB')
|
2020-07-10 13:51:59 +00:00
|
|
|
with open(database_file.replace('.gz', ''), 'r') as db_file:
|
2020-07-10 14:15:28 +00:00
|
|
|
run_command(mysql_command + [f"{db_name}"], stdin=db_file)
|
2020-06-29 17:58:10 +00:00
|
|
|
|
|
|
|
|
2020-03-26 18:58:50 +00:00
|
|
|
def main():
|
|
|
|
backup_dir = get_backup_dir()
|
|
|
|
|
|
|
|
if len(list_directories(backup_dir)) == 0:
|
|
|
|
pull_backup_from_s3()
|
|
|
|
|
|
|
|
for site in list_directories(backup_dir):
|
2020-04-28 20:15:59 +00:00
|
|
|
site_slug = site.replace('.', '_')
|
|
|
|
backups = [datetime.datetime.strptime(backup, DATE_FORMAT) for backup in list_directories(os.path.join(backup_dir, site))]
|
2020-03-26 18:58:50 +00:00
|
|
|
latest_backup = max(backups).strftime(DATE_FORMAT)
|
|
|
|
files_base = os.path.join(backup_dir, site, latest_backup, '')
|
|
|
|
files_base += latest_backup + '-' + site_slug
|
2020-06-25 18:12:55 +00:00
|
|
|
site_config_path = files_base + '-site_config_backup.json'
|
|
|
|
if not os.path.exists(site_config_path):
|
|
|
|
site_config_path = os.path.join(backup_dir, site, 'site_config.json')
|
2020-03-26 18:58:50 +00:00
|
|
|
if site in get_sites():
|
2020-06-29 17:58:10 +00:00
|
|
|
print('Overwrite site {}'.format(site))
|
2020-06-25 17:53:31 +00:00
|
|
|
restore_database(files_base, site_config_path, site)
|
2020-03-26 18:58:50 +00:00
|
|
|
restore_private_files(files_base)
|
|
|
|
restore_files(files_base)
|
|
|
|
else:
|
|
|
|
site_config = get_conf_params(
|
|
|
|
db_name='_' + hashlib.sha1(site.encode()).hexdigest()[:16],
|
|
|
|
db_password=random_string(16)
|
|
|
|
)
|
|
|
|
|
|
|
|
frappe.local.site = site
|
|
|
|
frappe.local.sites_path = os.getcwd()
|
|
|
|
frappe.local.site_path = os.getcwd() + '/' + site
|
|
|
|
make_conf(
|
|
|
|
db_name=site_config.get('db_name'),
|
2020-03-27 10:37:12 +00:00
|
|
|
db_password=site_config.get('db_password'),
|
2020-03-26 18:58:50 +00:00
|
|
|
)
|
2020-03-27 10:37:12 +00:00
|
|
|
make_site_dirs()
|
2020-06-29 17:58:10 +00:00
|
|
|
|
|
|
|
print('Create site {}'.format(site))
|
2020-06-26 00:17:17 +00:00
|
|
|
restore_database(files_base, site_config_path, site)
|
2020-03-26 18:58:50 +00:00
|
|
|
restore_private_files(files_base)
|
|
|
|
restore_files(files_base)
|
|
|
|
|
2020-06-29 17:58:10 +00:00
|
|
|
if frappe.redis_server:
|
|
|
|
frappe.redis_server.connection_pool.disconnect()
|
|
|
|
|
2020-03-26 18:58:50 +00:00
|
|
|
exit(0)
|
|
|
|
|
2020-04-28 20:15:59 +00:00
|
|
|
|
2020-03-26 18:58:50 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|