z230
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
KB_MYSQL_HOST=192.168.1.50
|
||||
KB_MYSQL_PORT=3306
|
||||
KB_MYSQL_USER=root
|
||||
KB_MYSQL_PASSWORD=Vlado9674+
|
||||
KB_MYSQL_DB=kanboard
|
||||
|
||||
KB_BACKUP_DIR=U:\MySQLBackup\Kanboard
|
||||
KB_KEEP_DAYS=30
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
import gzip
|
||||
import shutil
|
||||
import subprocess
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
MYSQLDUMP = PROJECT_ROOT / "bin" / "mysqldump.exe"
|
||||
|
||||
|
||||
def load_dotenv(dotenv_path: Path) -> None:
|
||||
if not dotenv_path.exists():
|
||||
return
|
||||
for line in dotenv_path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
k, v = line.split("=", 1)
|
||||
k = k.strip()
|
||||
v = v.strip().strip('"').strip("'")
|
||||
os.environ.setdefault(k, v)
|
||||
|
||||
def require_env(name: str) -> str:
|
||||
v = os.getenv(name)
|
||||
if not v:
|
||||
raise SystemExit(f"Chybí proměnná {name} (dej ji do .env nebo env).")
|
||||
return v
|
||||
|
||||
def run(cmd: list[str], *, cwd: Path | None = None) -> None:
|
||||
# pro ladění si můžeš odkomentovat print(cmd)
|
||||
p = subprocess.run(cmd, cwd=str(cwd) if cwd else None)
|
||||
if p.returncode != 0:
|
||||
raise SystemExit(f"Příkaz selhal (exit={p.returncode}): {' '.join(cmd)}")
|
||||
|
||||
def main() -> None:
|
||||
here = Path(__file__).resolve().parent
|
||||
load_dotenv(here / ".env")
|
||||
|
||||
host = require_env("KB_MYSQL_HOST")
|
||||
port = require_env("KB_MYSQL_PORT")
|
||||
user = require_env("KB_MYSQL_USER")
|
||||
password = require_env("KB_MYSQL_PASSWORD")
|
||||
db = require_env("KB_MYSQL_DB")
|
||||
|
||||
backup_dir = Path(os.getenv("KB_BACKUP_DIR", "./backups")).resolve()
|
||||
keep_days = int(os.getenv("KB_KEEP_DAYS", "30"))
|
||||
|
||||
backup_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
ts = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
sql_path = backup_dir / f"kanboard_{db}_{ts}.sql"
|
||||
gz_path = backup_dir / f"{sql_path.name}.gz"
|
||||
|
||||
# Bezpečně: vytvoříme dočasný mysql config soubor, aby heslo nebylo v "ps"
|
||||
cnf_path = backup_dir / f".mysql_{db}_{ts}.cnf"
|
||||
cnf_path.write_text(
|
||||
"[client]\n"
|
||||
f"host={host}\n"
|
||||
f"port={port}\n"
|
||||
f"user={user}\n"
|
||||
f"password={password}\n",
|
||||
encoding="utf-8"
|
||||
)
|
||||
try:
|
||||
# omezit práva (na Linuxu/Unraid ok; na Windows to ignoruj)
|
||||
try:
|
||||
os.chmod(cnf_path, 0o600)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Mysqldump – kompletní pro obnovu
|
||||
dump_cmd = [
|
||||
str(MYSQLDUMP),
|
||||
f"--defaults-extra-file={str(cnf_path)}",
|
||||
"--single-transaction",
|
||||
"--routines",
|
||||
"--triggers",
|
||||
"--events",
|
||||
"--hex-blob",
|
||||
"--default-character-set=utf8mb4",
|
||||
"--set-gtid-purged=OFF",
|
||||
"--databases", db,
|
||||
]
|
||||
|
||||
print(f"[OK] Dělám dump DB '{db}' -> {gz_path}")
|
||||
# Dump do .sql a rovnou gzip
|
||||
with subprocess.Popen(dump_cmd, stdout=subprocess.PIPE) as proc:
|
||||
if proc.stdout is None:
|
||||
raise SystemExit("Nepodařilo se získat stdout z mysqldump.")
|
||||
with gzip.open(gz_path, "wb", compresslevel=6) as gz:
|
||||
shutil.copyfileobj(proc.stdout, gz)
|
||||
rc = proc.wait()
|
||||
if rc != 0:
|
||||
raise SystemExit(f"mysqldump selhal (exit={rc}).")
|
||||
|
||||
# Malá kontrola velikosti
|
||||
size = gz_path.stat().st_size
|
||||
if size < 200: # hodně hrubé
|
||||
raise SystemExit(f"Dump je podezřele malý ({size} B) – něco je špatně.")
|
||||
|
||||
# Retence
|
||||
cutoff = datetime.now() - timedelta(days=keep_days)
|
||||
for f in backup_dir.glob("kanboard_*_*.sql.gz"):
|
||||
try:
|
||||
if datetime.fromtimestamp(f.stat().st_mtime) < cutoff:
|
||||
f.unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f"[OK] Hotovo. Velikost: {size/1024/1024:.2f} MB")
|
||||
|
||||
finally:
|
||||
try:
|
||||
cnf_path.unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user