不使用 Databasus 从备份恢复 PostgreSQL
备份不只是保护数据,还包括把数据恢复出来。Databasus 特别注意让你的备份始终可恢复:即使装有 Databasus 的 VPS 被删除、你丢失了访问权限或因故打不开界面。备份以标准格式 存储,没有供应商锁定,所以恢复备份并不需要 Databasus。
你需要什么
手动恢复备份需要:
- 备份文件,来自你的存储(本地存储、S3、 Google Drive 等)
- 元数据文件,来自同一存储。它与备份文件 同名,只是多了
.metadata扩展名。 - 密钥文件,位于
./databasus-data/secret.key(与备份文件在 同一目录,通常是/opt/databasus/)
文件结构
每个备份由两个文件组成,存放在你的存储中(本地或云端):
{database-name}-{timestamp}-{backup-id}:加密并压缩后的备份数据{database-name}-{timestamp}-{backup-id}.metadata:包含加密信息的 JSON 文件
元数据文件包含 Base64 格式的加密盐和 IV(nonce):
{
"backupId": "550e8400-e29b-41d4-a716-446655440000",
"encryptionSalt": "base64-encoded-salt",
"encryptionIV": "base64-encoded-nonce",
"encryption": "encrypted"
}解密
Databasus 使用 AES-256-GCM 加密,并通过 PBKDF2 派生密钥。每个备份都有独立的加密 密钥,由以下部分派生:
- 主密钥(来自 secret.key 文件)
- 备份 ID
- 随机盐(保存在元数据中)
使用这个 Python 脚本解密备份:
import json
import base64
import struct
import os
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA256
# Constants from Databasus encryption
MAGIC_BYTES = b"PGRSUS01"
HEADER_LENGTH = 64
CHUNK_SIZE = 1024 * 1024
PBKDF2_ITERATIONS = 100000
def decrypt_backup(backup_file, metadata_file, master_key):
"""
Decrypt a Databasus backup file using metadata and master key.
Args:
backup_file: Path to encrypted backup file
metadata_file: Path to metadata JSON file
master_key: Master key from ./databasus-data/secret.key
"""
# Validate files exist
if not os.path.exists(backup_file):
print(f"Error: Backup file not found: {backup_file}")
return
if not os.path.exists(metadata_file):
print(f"Error: Metadata file not found: {metadata_file}")
return
# Read metadata
with open(metadata_file, "r") as f:
metadata = json.load(f)
# Check if file is encrypted (case-insensitive check)
encryption_status = metadata.get("encryption", "").upper()
if encryption_status != "ENCRYPTED":
print(
f"Error: Backup is not encrypted (encryption status: {metadata.get('encryption')})"
)
print("No decryption needed. You can decompress/restore the file directly.")
return
backup_id = metadata["backupId"]
salt = base64.b64decode(metadata["encryptionSalt"])
iv = base64.b64decode(metadata["encryptionIV"])
# Generate output filename with decrypted_ prefix
backup_dir = os.path.dirname(backup_file) or "."
backup_name = os.path.basename(backup_file)
output_file = os.path.join(backup_dir, f"decrypted_{backup_name}")
# Derive encryption key using PBKDF2
key_material = (master_key + backup_id).encode("utf-8")
derived_key = PBKDF2(
key_material, salt, dkLen=32, count=PBKDF2_ITERATIONS, hmac_hash_module=SHA256
)
try:
with open(backup_file, "rb") as f_in, open(output_file, "wb") as f_out:
# Read and validate header
header = f_in.read(HEADER_LENGTH)
# Validate magic bytes
magic = header[:8]
if magic != MAGIC_BYTES:
raise ValueError(
f"Invalid magic bytes: expected {MAGIC_BYTES}, got {magic}"
)
# Decrypt chunks
chunk_index = 0
while True:
# Read chunk length (4 bytes)
length_bytes = f_in.read(4)
if not length_bytes:
break
chunk_length = struct.unpack(">I", length_bytes)[0]
# Read encrypted chunk
encrypted_chunk = f_in.read(chunk_length)
if not encrypted_chunk:
break
# Generate chunk nonce (base IV + chunk index)
chunk_nonce = bytearray(iv)
chunk_nonce[4:12] = struct.pack(">Q", chunk_index)
# Create cipher for this chunk
chunk_cipher = AES.new(derived_key, AES.MODE_GCM, nonce=bytes(chunk_nonce))
# Decrypt chunk
try:
decrypted_chunk = chunk_cipher.decrypt_and_verify(
encrypted_chunk[:-16], # ciphertext
encrypted_chunk[-16:], # auth tag
)
except ValueError as e:
if "MAC check failed" in str(e):
print("\nError: Failed to decrypt backup (MAC check failed)")
print("This usually means:")
print(" - The master key is incorrect")
print(" - The backup file is corrupted")
print(" - The metadata doesn't match this backup file")
print(f"\nFailed at chunk {chunk_index}")
raise
raise
# Write decrypted data
f_out.write(decrypted_chunk)
chunk_index += 1
print(f"Successfully decrypted {chunk_index} chunks to {output_file}")
except ValueError as e:
# Clean up partial output file after files are closed
if "MAC check failed" in str(e) and os.path.exists(output_file):
os.remove(output_file)
return
# Example usage:
if __name__ == "__main__":
decrypt_backup(
backup_file="./your-backup-file", # <--- change this to your backup file
metadata_file="./your-backup-file.metadata", # <--- change this to your metadata file
master_key="your-master-key-here", # <--- change this to your master key
)安装所需依赖:
pip install pycryptodome脚本用法:
- 把上面的脚本保存为文件(例如
decrypt_backup.py) - 修改文件末尾示例用法部分的参数
- 运行脚本:
python decrypt_backup.py脚本会自动生成带 decrypted_ 前缀的输出文件。例如,备份文件是 backup-id.dump,解密后的文件就是 decrypted_backup-id.dump。
恢复到数据库
解密之后,使用各数据库自带的工具恢复:
PostgreSQL
PostgreSQL 备份使用内置压缩,可以直接恢复:
本地数据库:
# Restore to local database
pg_restore -d your_database decrypted-backup.dump远程数据库:
# Restore to remote database
pg_restore -h hostname -p 5432 -U username -d database_name decrypted-backup.dumpMySQL
MySQL 备份使用 zstd 5 级压缩,恢复之前必须先解压。
第 1 步:解压备份
使用 zstd 命令行工具或任何兼容的解压工具(7-Zip、PeaZip、 WinRAR 等):
# Decompress with zstd command-line tool
zstd -d decrypted-backup.sql.zst -o decrypted-backup.sql
# Or use graphical tools like 7-Zip, PeaZip, or WinRAR第 2 步:恢复到数据库
本地数据库:
# Restore to local database
mysql your_database < decrypted-backup.sql远程数据库:
# Restore to remote database
mysql -h hostname -P 3306 -u username -p database_name < decrypted-backup.sqlMariaDB
MariaDB 备份使用 zstd 5 级压缩,恢复之前必须先解压。
第 1 步:解压备份
使用 zstd 命令行工具或任何兼容的解压工具(7-Zip、PeaZip、 WinRAR 等):
# Decompress with zstd command-line tool
zstd -d decrypted-backup.sql.zst -o decrypted-backup.sql
# Or use graphical tools like 7-Zip, PeaZip, or WinRAR第 2 步:恢复到数据库
本地数据库:
# Restore to local database
mariadb your_database < decrypted-backup.sql远程数据库:
# Restore to remote database
mariadb -h hostname -P 3306 -u username -p database_name < decrypted-backup.sqlMongoDB
MongoDB 备份使用内置 gzip 压缩,可以直接恢复:
本地数据库:
# Restore to local database
mongorestore --archive=decrypted-backup.archive --gzip --db your_database远程数据库:
# Restore to remote database
mongorestore --host hostname:27017 --username username --password password \
--archive=decrypted-backup.archive --gzip --db database_name遇到问题怎么办?
如果恢复过程中遇到任何问题:
- 向 AI 求助。ChatGPT、Claude、Gemini 这类 AI 助手非常擅长解答压缩工具和数据库恢复方面的问题。描述一下 你的问题,它们就能一步步指导你。
- 加入我们的 社区。我们的开发者和社区成员可以针对你的具体情况提供帮助。