From 11e16d7daac5ee9489043d40347dca51a7d38c01 Mon Sep 17 00:00:00 2001 From: redjinald Date: Tue, 23 Jun 2026 21:45:03 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20backup.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backup.py | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/backup.py b/backup.py index e69de29..b2688c0 100644 --- a/backup.py +++ b/backup.py @@ -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) \ No newline at end of file