124 lines
4.2 KiB
Python
124 lines
4.2 KiB
Python
|
|
import os
|
|||
|
|
import sys
|
|||
|
|
import subprocess
|
|||
|
|
import logging
|
|||
|
|
from datetime import datetime
|
|||
|
|
from utils import load_config, setup_cron
|
|||
|
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|||
|
|
|
|||
|
|
# 配置日志输出到标准输出
|
|||
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s')
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
# 加载配置
|
|||
|
|
config = load_config()
|
|||
|
|
|
|||
|
|
def load_encryption_key():
|
|||
|
|
key_path = '/app/encryption.key'
|
|||
|
|
if not os.path.exists(key_path):
|
|||
|
|
logger.error(f"未找到加密密钥文件:{key_path}")
|
|||
|
|
sys.exit(1)
|
|||
|
|
with open(key_path, 'rb') as key_file:
|
|||
|
|
key = key_file.read()
|
|||
|
|
return key
|
|||
|
|
|
|||
|
|
def encrypt_file(file_name, key):
|
|||
|
|
if len(key) == 16 or len(key) == 32:
|
|||
|
|
algorithm = algorithms.AES(key)
|
|||
|
|
# 生成随机的 12 字节(96 位)IV
|
|||
|
|
iv = os.urandom(12)
|
|||
|
|
cipher = Cipher(algorithm, mode=modes.GCM(iv))
|
|||
|
|
else:
|
|||
|
|
logger.error("不支持的密钥长度,必须是 16 或 32 字节(AES-128 或 AES-256)")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
encryptor = cipher.encryptor()
|
|||
|
|
|
|||
|
|
with open(file_name, 'rb') as infile:
|
|||
|
|
plaintext = infile.read()
|
|||
|
|
|
|||
|
|
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
|
|||
|
|
tag = encryptor.tag
|
|||
|
|
|
|||
|
|
# 将 IV、认证标签和密文写入文件
|
|||
|
|
with open(file_name, 'wb') as outfile:
|
|||
|
|
outfile.write(iv + tag + ciphertext)
|
|||
|
|
|
|||
|
|
def upload_to_cloud(file_name):
|
|||
|
|
storage_provider = config['backup']['storage']['provider']
|
|||
|
|
if storage_provider == 'aws':
|
|||
|
|
import boto3
|
|||
|
|
s3 = boto3.client(
|
|||
|
|
's3',
|
|||
|
|
aws_access_key_id=config['backup']['storage']['access_key'],
|
|||
|
|
aws_secret_access_key=config['backup']['storage']['secret_key']
|
|||
|
|
)
|
|||
|
|
s3.upload_file(file_name, config['backup']['storage']['bucket_name'], file_name)
|
|||
|
|
logger.info(f"备份文件已上传到 AWS S3:{file_name}")
|
|||
|
|
elif storage_provider == 'aliyun':
|
|||
|
|
import oss2
|
|||
|
|
auth = oss2.Auth(
|
|||
|
|
config['backup']['storage']['access_key'],
|
|||
|
|
config['backup']['storage']['secret_key']
|
|||
|
|
)
|
|||
|
|
bucket = oss2.Bucket(auth, config['backup']['storage']['endpoint'], config['backup']['storage']['bucket_name'])
|
|||
|
|
with open(file_name, 'rb') as fileobj:
|
|||
|
|
bucket.put_object(file_name, fileobj)
|
|||
|
|
logger.info(f"备份文件已上传到阿里云 OSS:{file_name}")
|
|||
|
|
else:
|
|||
|
|
logger.error(f"不支持的存储提供商:{storage_provider}")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
def backup():
|
|||
|
|
logger.info("开始备份过程")
|
|||
|
|
backup_type = config['backup'].get('type', 'full')
|
|||
|
|
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
|
|||
|
|
backup_file = f"backup_{backup_type}_{timestamp}.sql"
|
|||
|
|
|
|||
|
|
# 执行 pg_dump 命令
|
|||
|
|
pg_dump_cmd = [
|
|||
|
|
'pg_dump',
|
|||
|
|
'-h', os.environ.get('DB_HOST', 'localhost'),
|
|||
|
|
'-U', os.environ.get('DB_USER', 'postgres'),
|
|||
|
|
os.environ.get('DB_NAME', 'postgres'),
|
|||
|
|
'-F', 'c', # 使用自定义格式,便于压缩
|
|||
|
|
'-b', # 包含大对象
|
|||
|
|
'-f', backup_file
|
|||
|
|
]
|
|||
|
|
try:
|
|||
|
|
subprocess.run(pg_dump_cmd, check=True, env=os.environ)
|
|||
|
|
logger.info(f"数据库备份成功:{backup_file}")
|
|||
|
|
except subprocess.CalledProcessError as e:
|
|||
|
|
logger.error(f"数据库备份失败:{e}")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
# 默认启用加密
|
|||
|
|
encryption_enabled = config['backup'].get('encryption', True)
|
|||
|
|
if encryption_enabled:
|
|||
|
|
key = load_encryption_key()
|
|||
|
|
encrypt_file(backup_file, key)
|
|||
|
|
logger.info(f"备份文件已加密:{backup_file}")
|
|||
|
|
|
|||
|
|
# 上传到云存储
|
|||
|
|
upload_to_cloud(backup_file)
|
|||
|
|
|
|||
|
|
# 删除本地备份文件
|
|||
|
|
os.remove(backup_file)
|
|||
|
|
logger.info("本地备份文件已删除")
|
|||
|
|
|
|||
|
|
def verify_backup():
|
|||
|
|
# 实现备份验证逻辑
|
|||
|
|
logger.info("开始备份验证过程")
|
|||
|
|
# 这里可以实现下载备份文件,尝试解密和解压,确保备份文件有效
|
|||
|
|
logger.info("备份验证完成")
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
if 'setup_cron' in sys.argv:
|
|||
|
|
setup_cron()
|
|||
|
|
elif 'run_backup' in sys.argv:
|
|||
|
|
backup()
|
|||
|
|
elif 'verify_backup' in sys.argv:
|
|||
|
|
verify_backup()
|
|||
|
|
else:
|
|||
|
|
logger.error("请指定 'setup_cron'、'run_backup' 或 'verify_backup'")
|