Обновить backup.py
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import os
|
||||
import sqlite3
|
||||
import tarfile
|
||||
import subprocess
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
# --- НАСТРОЙКИ ПУТЕЙ ---
|
||||
# Эти пути соответствуют внутренностям Docker-контейнера раннера
|
||||
DATA_DIR = "/vw-data"
|
||||
DB_FILE = os.path.join(DATA_DIR, "db.sqlite3")
|
||||
ATTACHMENTS_DIR = os.path.join(DATA_DIR, "attachments")
|
||||
|
||||
# Временная папка для обработки (внутри контейнера раннера)
|
||||
BACKUP_TEMP_DIR = "/tmp/vw_backup_processing"
|
||||
|
||||
# Имена крипто-пультов rclone, которые вы настроили
|
||||
RCLONE_REMOTES = [
|
||||
"google-crypt:/vaultwarden-backups",
|
||||
"yandex-crypt:/vaultwarden-backups"
|
||||
]
|
||||
|
||||
def safe_sqlite_backup(src_path, dest_path):
|
||||
"""Делает безопасный слепок запущенной базы данных SQLite"""
|
||||
print(f"[*] Создание безопасного дампа базы данных из {src_path}...")
|
||||
src = sqlite3.connect(src_path)
|
||||
dest = sqlite3.connect(dest_path)
|
||||
with dest:
|
||||
src.backup(dest)
|
||||
src.close()
|
||||
dest.close()
|
||||
print("[+] Дамп базы успешно создан.")
|
||||
|
||||
def create_backup_archive():
|
||||
"""Собирает базу и вложения в один tar.gz архив"""
|
||||
if os.path.exists(BACKUP_TEMP_DIR):
|
||||
shutil.rmtree(BACKUP_TEMP_DIR)
|
||||
os.makedirs(BACKUP_TEMP_DIR, exist_ok=True)
|
||||
|
||||
# 1. Дамп БД
|
||||
temp_db_path = os.path.join(BACKUP_TEMP_DIR, "db.sqlite3")
|
||||
safe_sqlite_backup(DB_FILE, temp_db_path)
|
||||
|
||||
# 2. Формирование архива
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
archive_name = f"vaultwarden_{timestamp}.tar.gz"
|
||||
archive_path = os.path.join(BACKUP_TEMP_DIR, archive_name)
|
||||
|
||||
print(f"[*] Упаковка файлов в архив {archive_name}...")
|
||||
with tarfile.open(archive_path, "w:gz") as tar:
|
||||
# Добавляем БД в корень архива
|
||||
tar.add(temp_db_path, arcname="db.sqlite3")
|
||||
# Если есть вложения (файлы в паролях), добавляем и их
|
||||
if os.path.exists(ATTACHMENTS_DIR):
|
||||
tar.add(ATTACHMENTS_DIR, arcname="attachments")
|
||||
print("[+] Вложения добавлены в архив.")
|
||||
|
||||
# Удаляем сырой дамп, оставляем только архив
|
||||
os.remove(temp_db_path)
|
||||
print("[+] Архив успешно сформирован.")
|
||||
return archive_path
|
||||
|
||||
def upload_to_clouds(archive_path):
|
||||
"""Отправляет архив через Rclone в облака"""
|
||||
filename = os.path.basename(archive_path)
|
||||
all_success = True
|
||||
|
||||
for remote in RCLONE_REMOTES:
|
||||
print(f"[*] Синхронизация с облаком {remote}...")
|
||||
# Вызываем команду rclone copy
|
||||
cmd = ["rclone", "copy", archive_path, remote]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f"[+] Успешно загружено в {remote}")
|
||||
else:
|
||||
print(f"[-] ОШИБКА загрузки в {remote}:")
|
||||
print(result.stderr)
|
||||
all_success = False
|
||||
|
||||
return all_success
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== Старт процесса резервного копирования Vaultwarden ===")
|
||||
|
||||
if not os.path.exists(DB_FILE):
|
||||
print(f"[!] КРИТИЧЕСКАЯ ОШИБКА: Файл БД {DB_FILE} не найден. Проверьте монтирование Volumes.")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
archive_file = create_backup_archive()
|
||||
is_upload_ok = upload_to_clouds(archive_file)
|
||||
|
||||
# Очистка следов после работы
|
||||
print("[*] Очистка временных файлов...")
|
||||
shutil.rmtree(BACKUP_TEMP_DIR)
|
||||
|
||||
if is_upload_ok:
|
||||
print("=== Бэкап успешно завершен ===")
|
||||
else:
|
||||
print("=== Бэкап завершен с ошибками при отправке ===")
|
||||
exit(1) # Уронит пайплайн в красный крестик в Gitea, если облако отвалилось
|
||||
|
||||
except Exception as e:
|
||||
print(f"[!] Произошел непредвиденный сбой: {e}")
|
||||
exit(1)
|
||||
Reference in New Issue
Block a user