Files
2026-06-23 21:45:03 +03:00

106 lines
4.3 KiB
Python

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)