113 lines
4.4 KiB
Python
113 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Dump automatique de la base MariaDB.
|
|
Execute chaque nuit via cron dans le conteneur Flask.
|
|
Garde les 7 derniers dumps (rotation).
|
|
"""
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import gzip
|
|
import tarfile
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
DUMP_DIR = Path("/backups")
|
|
DUMP_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
TIMESTAMP = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
DUMP_FILE = DUMP_DIR / f"gmao_db_{TIMESTAMP}.sql.gz"
|
|
BUNDLE_FILE = DUMP_DIR / f"gmao_backup_{TIMESTAMP}.tar.gz"
|
|
UPLOAD_DIR = Path(os.environ.get("UPLOAD_FOLDER", "/app/app_new/uploads"))
|
|
|
|
DB_HOST = os.environ.get("DB_HOST", "mariadb")
|
|
DB_PORT = os.environ.get("DB_PORT", "3306")
|
|
DB_NAME = os.environ.get("MARIADB_DATABASE", "gmao_db")
|
|
DB_USER = os.environ.get("MARIADB_USER", "gmao")
|
|
DB_PASS = os.environ.get("MARIADB_PASSWORD", "gmao123")
|
|
ROOT_PASS = os.environ.get("MARIADB_ROOT_PASSWORD", "root123")
|
|
|
|
# Utiliser pymysql pour exporter le schema + donnees
|
|
try:
|
|
import pymysql
|
|
from pymysql.cursors import DictCursor
|
|
|
|
conn = pymysql.connect(
|
|
host=DB_HOST, port=int(DB_PORT),
|
|
user="root", password=ROOT_PASS,
|
|
database=DB_NAME, charset="utf8mb4"
|
|
)
|
|
cursor = conn.cursor()
|
|
cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ")
|
|
cursor.execute("START TRANSACTION WITH CONSISTENT SNAPSHOT")
|
|
|
|
# Obtenir la liste des tables
|
|
cursor.execute("SHOW TABLES")
|
|
tables = [row[0] for row in cursor.fetchall()]
|
|
|
|
with gzip.open(str(DUMP_FILE), "wt", encoding="utf-8") as f:
|
|
f.write(f"-- Dump {DB_NAME} - {datetime.now().isoformat()}\n")
|
|
f.write("SET FOREIGN_KEY_CHECKS=0;\n\n")
|
|
|
|
for table in tables:
|
|
# Schema
|
|
cursor.execute(f"SHOW CREATE TABLE `{table}`")
|
|
create_sql = cursor.fetchone()[1]
|
|
f.write(f"DROP TABLE IF EXISTS `{table}`;\n")
|
|
f.write(f"{create_sql};\n\n")
|
|
|
|
# Donnees
|
|
cursor.execute(f"SELECT * FROM `{table}`")
|
|
rows = cursor.fetchall()
|
|
if rows:
|
|
cols = [d[0] for d in cursor.description]
|
|
col_names = ", ".join(f"`{c}`" for c in cols)
|
|
for row in rows:
|
|
values = []
|
|
for val in row:
|
|
if val is None:
|
|
values.append("NULL")
|
|
elif isinstance(val, (int, float)):
|
|
values.append(str(val))
|
|
else:
|
|
escaped = str(val).replace("\\", "\\\\").replace("'", "\\'")
|
|
values.append(f"'{escaped}'")
|
|
f.write(f"INSERT INTO `{table}` ({col_names}) VALUES ({', '.join(values)});\n")
|
|
f.write("\n")
|
|
|
|
f.write("SET FOREIGN_KEY_CHECKS=1;\n")
|
|
|
|
conn.rollback()
|
|
conn.close()
|
|
|
|
# Une archive de reprise contient la base cohérente et les documents.
|
|
with tarfile.open(BUNDLE_FILE, "w:gz") as archive:
|
|
archive.add(DUMP_FILE, arcname=f"database/{DUMP_FILE.name}")
|
|
if UPLOAD_DIR.exists():
|
|
archive.add(UPLOAD_DIR, arcname="uploads", recursive=True)
|
|
version_file = Path("/app/VERSION")
|
|
if version_file.exists():
|
|
archive.add(version_file, arcname="VERSION")
|
|
|
|
# Validation minimale immédiate : l'archive doit être lisible et contenir
|
|
# un dump SQL non vide. Une restauration complète reste à tester hors ligne.
|
|
with tarfile.open(BUNDLE_FILE, "r:gz") as archive:
|
|
sql_members = [m for m in archive.getmembers() if m.name.startswith("database/")]
|
|
if not sql_members or sql_members[0].size <= 0:
|
|
raise RuntimeError("Archive de sauvegarde invalide : dump SQL absent")
|
|
|
|
# Rotation : garder les 7 derniers
|
|
dumps = sorted(DUMP_DIR.glob("gmao_db_*.sql.gz"), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
for old in dumps[7:]:
|
|
old.unlink()
|
|
|
|
bundles = sorted(DUMP_DIR.glob("gmao_backup_*.tar.gz"), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
for old in bundles[7:]:
|
|
old.unlink()
|
|
|
|
size = DUMP_FILE.stat().st_size / 1024
|
|
bundle_size = BUNDLE_FILE.stat().st_size / 1024
|
|
print(f"{datetime.now()}: Sauvegarde validee: {BUNDLE_FILE} ({bundle_size:.0f} KB, {len(tables)} tables)")
|
|
|
|
except Exception as e:
|
|
print(f"{datetime.now()}: ERREUR dump: {e}", file=sys.stderr)
|
|
sys.exit(1)
|