2020-03-26 18:58:50 +00:00
|
|
|
import datetime
|
|
|
|
import hashlib
|
2021-12-10 08:53:00 +00:00
|
|
|
import os
|
|
|
|
import tarfile
|
2020-03-26 18:58:50 +00:00
|
|
|
|
2021-12-10 08:53:00 +00:00
|
|
|
import boto3
|
|
|
|
import frappe
|
|
|
|
from constants import COMMON_SITE_CONFIG_FILE, DATE_FORMAT, RDS_DB, RDS_PRIVILEGES
|
2020-07-10 17:45:36 +00:00
|
|
|
from frappe.installer import (
|
|
|
|
get_conf_params,
|
2021-12-10 08:53:00 +00:00
|
|
|
make_conf,
|
2020-07-10 17:45:36 +00:00
|
|
|
make_site_dirs,
|
2021-12-10 08:52:40 +00:00
|
|
|
update_site_config,
|
2020-07-10 17:45:36 +00:00
|
|
|
)
|
2021-12-10 08:53:00 +00:00
|
|
|
from frappe.utils import get_sites, random_string
|
2020-07-10 17:45:36 +00:00
|
|
|
from utils import (
|
2021-12-10 08:53:00 +00:00
|
|
|
check_s3_environment_variables,
|
2020-07-10 17:45:36 +00:00
|
|
|
get_config,
|
|
|
|
get_password,
|
2021-12-10 08:53:00 +00:00
|
|
|
get_site_config,
|
|
|
|
list_directories,
|
|
|
|
run_command,
|
|
|
|
set_key_in_site_config,
|
2020-07-10 17:45:36 +00:00
|
|
|
)
|
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 get_backup_dir():
|
2021-12-10 08:52:40 +00:00
|
|
|
return os.path.join(os.path.expanduser("~"), "backups")
|
2020-03-26 18:58:50 +00:00
|
|
|
|
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:
|
2021-12-10 08:52:40 +00:00
|
|
|
print(f"Extract Database GZip for site {site}")
|
2020-07-10 04:37:22 +00:00
|
|
|
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
|
2021-12-10 08:52:40 +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
|
2021-12-10 08:52:40 +00:00
|
|
|
set_key_in_site_config("db_type", site, site_config_path)
|
2020-06-29 17:58:10 +00:00
|
|
|
# Set db_host if it exists in backup site_config.json
|
2021-12-10 08:52:40 +00:00
|
|
|
set_key_in_site_config("db_host", site, site_config_path)
|
2020-06-29 17:58:10 +00:00
|
|
|
# Set db_port if it exists in backup site_config.json
|
2021-12-10 08:52:40 +00:00
|
|
|
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
|
2021-12-10 08:52:40 +00:00
|
|
|
db_type = site_config.get("db_type", "mariadb")
|
2020-06-29 17:58:10 +00:00
|
|
|
is_database_restored = False
|
|
|
|
|
2021-12-10 08:52:40 +00:00
|
|
|
if db_type == "mariadb":
|
2020-06-29 17:58:10 +00:00
|
|
|
restore_mariadb(
|
2021-12-10 08:52:40 +00:00
|
|
|
config=config, site_config=site_config, database_file=database_file
|
|
|
|
)
|
2020-06-29 17:58:10 +00:00
|
|
|
is_database_restored = True
|
2021-12-10 08:52:40 +00:00
|
|
|
elif db_type == "postgres":
|
2020-06-29 17:58:10 +00:00
|
|
|
restore_postgres(
|
2021-12-10 08:52:40 +00:00
|
|
|
config=config, site_config=site_config, database_file=database_file
|
|
|
|
)
|
2020-06-29 17:58:10 +00:00
|
|
|
is_database_restored = True
|
|
|
|
|
|
|
|
if is_database_restored:
|
|
|
|
# Set encryption_key if it exists in backup site_config.json
|
2021-12-10 08:52:40 +00:00
|
|
|
set_key_in_site_config("encryption_key", site, site_config_path)
|
2020-06-29 17:58:10 +00:00
|
|
|
|
|
|
|
|
2020-03-26 18:58:50 +00:00
|
|
|
def restore_files(files_base):
|
2021-12-10 08:52:40 +00:00
|
|
|
public_files = files_base + "-files.tar"
|
2020-03-26 18:58:50 +00:00
|
|
|
# extract tar
|
|
|
|
public_tar = tarfile.open(public_files)
|
2021-12-10 08:52:40 +00:00
|
|
|
print(f"Extracting {public_files}")
|
2020-03-26 18:58:50 +00:00
|
|
|
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):
|
2021-12-10 08:52:40 +00:00
|
|
|
private_files = files_base + "-private-files.tar"
|
2020-03-26 18:58:50 +00:00
|
|
|
private_tar = tarfile.open(private_files)
|
2021-12-10 08:52:40 +00:00
|
|
|
print(f"Extracting {private_files}")
|
2020-03-26 18:58:50 +00:00
|
|
|
private_tar.extractall()
|
|
|
|
|
2020-04-28 20:15:59 +00:00
|
|
|
|
2020-03-26 18:58:50 +00:00
|
|
|
def pull_backup_from_s3():
|
2020-07-10 17:45:36 +00:00
|
|
|
check_s3_environment_variables()
|
2020-03-26 18:58:50 +00:00
|
|
|
|
|
|
|
# https://stackoverflow.com/a/54672690
|
|
|
|
s3 = boto3.resource(
|
2021-12-10 08:52:40 +00:00
|
|
|
"s3",
|
|
|
|
region_name=os.environ.get("REGION"),
|
|
|
|
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"),
|
2020-03-26 18:58:50 +00:00
|
|
|
)
|
|
|
|
|
2021-12-10 08:52:40 +00:00
|
|
|
bucket_dir = os.environ.get("BUCKET_DIR")
|
|
|
|
bucket_name = os.environ.get("BUCKET_NAME")
|
2020-03-26 18:58:50 +00:00
|
|
|
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):
|
2020-10-06 15:17:56 +00:00
|
|
|
if obj.get()["ContentType"] == "application/x-directory":
|
|
|
|
continue
|
2021-12-10 08:52:40 +00:00
|
|
|
backup_file = obj.key.replace(os.path.join(bucket_dir, ""), "")
|
2020-05-22 09:45:33 +00:00
|
|
|
backup_files.append(backup_file)
|
2021-12-10 08:52:40 +00:00
|
|
|
site_name, timestamp, backup_type = backup_file.split("/")
|
|
|
|
site_timestamp = site_name + "/" + timestamp
|
2020-05-22 09:45:33 +00:00
|
|
|
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:
|
2021-12-10 08:52:40 +00:00
|
|
|
site_name, timestamp = site_timestamp.split("/")
|
2020-05-22 09:45:33 +00:00
|
|
|
if site == site_name:
|
2021-12-10 08:52:40 +00:00
|
|
|
timestamp_datetime = datetime.datetime.strptime(timestamp, DATE_FORMAT)
|
2020-05-22 09:45:33 +00:00
|
|
|
backup_timestamps.append(timestamp)
|
2021-12-10 08:52:40 +00:00
|
|
|
download_backups.append(site + "/" + max(backup_timestamps))
|
2020-05-22 09:45:33 +00:00
|
|
|
|
|
|
|
# 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))
|
2021-12-10 08:52:40 +00:00
|
|
|
print(f"Downloading {backup_file}")
|
|
|
|
bucket.download_file(bucket_dir + "/" + backup_file, backup_file)
|
2020-03-26 18:58:50 +00:00
|
|
|
|
2021-12-10 08:52:40 +00:00
|
|
|
os.chdir(os.path.join(os.path.expanduser("~"), "frappe-bench", "sites"))
|
2020-03-26 18:58:50 +00:00
|
|
|
|
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)
|
|
|
|
|
2021-12-10 08:52:40 +00:00
|
|
|
db_root_user = config.get("root_login")
|
2020-06-29 17:58:10 +00:00
|
|
|
if not db_root_user:
|
2021-12-10 08:52:40 +00:00
|
|
|
postgres_user = os.environ.get("DB_ROOT_USER")
|
2020-06-29 17:58:10 +00:00
|
|
|
if not postgres_user:
|
2021-12-10 08:52:40 +00:00
|
|
|
print("Variable DB_ROOT_USER not set")
|
2020-06-29 17:58:10 +00:00
|
|
|
exit(1)
|
|
|
|
|
|
|
|
db_root_user = postgres_user
|
|
|
|
update_site_config(
|
|
|
|
"root_login",
|
|
|
|
db_root_user,
|
|
|
|
validate=False,
|
2021-12-10 08:52:40 +00:00
|
|
|
site_config_path=common_site_config_path,
|
|
|
|
)
|
2020-06-29 17:58:10 +00:00
|
|
|
|
2021-12-10 08:52:40 +00:00
|
|
|
db_root_password = config.get("root_password")
|
2020-06-29 17:58:10 +00:00
|
|
|
if not db_root_password:
|
2021-12-10 08:52:40 +00:00
|
|
|
root_password = get_password("POSTGRES_PASSWORD")
|
2020-06-29 17:58:10 +00:00
|
|
|
if not root_password:
|
2021-12-10 08:52:40 +00:00
|
|
|
print("Variable POSTGRES_PASSWORD not set")
|
2020-06-29 17:58:10 +00:00
|
|
|
exit(1)
|
|
|
|
|
|
|
|
db_root_password = root_password
|
|
|
|
update_site_config(
|
|
|
|
"root_password",
|
|
|
|
db_root_password,
|
|
|
|
validate=False,
|
2021-12-10 08:52:40 +00:00
|
|
|
site_config_path=common_site_config_path,
|
|
|
|
)
|
2020-06-29 17:58:10 +00:00
|
|
|
|
|
|
|
# site config
|
2021-12-10 08:52:40 +00:00
|
|
|
db_host = site_config.get("db_host")
|
|
|
|
db_port = site_config.get("db_port", 5432)
|
|
|
|
db_name = site_config.get("db_name")
|
|
|
|
db_password = site_config.get("db_password")
|
2020-06-29 17:58:10 +00:00
|
|
|
|
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
|
|
|
|
2021-12-10 08:52:40 +00:00
|
|
|
print("Restoring PostgreSQL")
|
|
|
|
run_command(psql_command + [psql_uri, "-c", f'DROP DATABASE IF EXISTS "{db_name}"'])
|
2020-07-10 09:34:29 +00:00
|
|
|
run_command(psql_command + [psql_uri, "-c", f"DROP USER IF EXISTS {db_name}"])
|
2021-12-10 08:52:40 +00:00
|
|
|
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}']
|
|
|
|
)
|
|
|
|
with open(database_file.replace(".gz", "")) as db_file:
|
2020-07-10 10:48:25 +00:00
|
|
|
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):
|
2021-12-10 08:52:40 +00:00
|
|
|
db_root_password = get_password("MYSQL_ROOT_PASSWORD")
|
2020-06-29 17:58:10 +00:00
|
|
|
if not db_root_password:
|
2021-12-10 08:52:40 +00:00
|
|
|
print("Variable MYSQL_ROOT_PASSWORD not set")
|
2020-06-29 17:58:10 +00:00
|
|
|
exit(1)
|
|
|
|
|
2021-12-10 08:52:40 +00:00
|
|
|
db_root_user = os.environ.get("DB_ROOT_USER", "root")
|
2020-06-29 17:58:10 +00:00
|
|
|
|
2021-12-10 08:52:40 +00:00
|
|
|
db_host = site_config.get("db_host", config.get("db_host"))
|
|
|
|
db_port = site_config.get("db_port", config.get("db_port", 3306))
|
|
|
|
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
|
2021-12-10 08:52:40 +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
|
2021-12-10 08:52:40 +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
|
2021-12-10 08:52:40 +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
|
2021-12-10 08:52:40 +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-08-04 10:11:58 +00:00
|
|
|
|
|
|
|
grant_privileges = "ALL PRIVILEGES"
|
|
|
|
|
|
|
|
# for Amazon RDS
|
|
|
|
if config.get(RDS_DB) or site_config.get(RDS_DB):
|
|
|
|
grant_privileges = RDS_PRIVILEGES
|
|
|
|
|
2021-12-10 08:52:40 +00:00
|
|
|
grant_privileges_command = mysql_command + [
|
|
|
|
"-e",
|
|
|
|
f"GRANT {grant_privileges} ON `{db_name}`.* TO '{db_name}'@'%' IDENTIFIED BY '{db_password}'; FLUSH PRIVILEGES;",
|
|
|
|
]
|
2020-08-04 10:11:58 +00:00
|
|
|
run_command(grant_privileges_command)
|
2020-06-29 17:58:10 +00:00
|
|
|
|
2021-12-10 08:52:40 +00:00
|
|
|
print("Restoring MariaDB")
|
|
|
|
with open(database_file.replace(".gz", "")) 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):
|
2021-12-10 08:52:40 +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)
|
2021-12-10 08:52:40 +00:00
|
|
|
files_base = os.path.join(backup_dir, site, latest_backup, "")
|
|
|
|
files_base += latest_backup + "-" + site_slug
|
|
|
|
site_config_path = files_base + "-site_config_backup.json"
|
2020-06-25 18:12:55 +00:00
|
|
|
if not os.path.exists(site_config_path):
|
2021-12-10 08:52:40 +00:00
|
|
|
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():
|
2021-12-10 08:52:40 +00:00
|
|
|
print(f"Overwrite site {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(
|
2021-12-10 08:52:40 +00:00
|
|
|
db_name="_" + hashlib.sha1(site.encode()).hexdigest()[:16],
|
|
|
|
db_password=random_string(16),
|
2020-03-26 18:58:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
frappe.local.site = site
|
|
|
|
frappe.local.sites_path = os.getcwd()
|
2021-12-10 08:52:40 +00:00
|
|
|
frappe.local.site_path = os.getcwd() + "/" + site
|
2020-03-26 18:58:50 +00:00
|
|
|
make_conf(
|
2021-12-10 08:52:40 +00:00
|
|
|
db_name=site_config.get("db_name"),
|
|
|
|
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
|
|
|
|
2021-12-10 08:52:40 +00:00
|
|
|
print(f"Create site {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()
|