From d62b1a801c9eb6a0df4f290c7eeb31c594b673cf Mon Sep 17 00:00:00 2001 From: administrator Date: Sun, 24 May 2026 06:55:47 +0200 Subject: [PATCH] notebookVb --- 00 PictureCollector/clear_tables.py | 16 ++ 00 PictureCollector/collect_pictures.py | 284 ++++++++++++++++++++ 00 PictureCollector/create_tables.py | 44 +++ 00 PictureCollector/ssh_check_mount.py | 17 ++ 00 PictureCollector/ssh_check_tower1.py | 32 +++ 00 PictureCollector/ssh_deploy.py | 49 ++++ 00 PictureCollector/ssh_deploy_tower1.py | 49 ++++ 00 PictureCollector/ssh_find_userscripts.py | 18 ++ 00 PictureCollector/ssh_install_deps.py | 49 ++++ 00 PictureCollector/ssh_install_tower1.py | 22 ++ 00 PictureCollector/stats.py | 48 ++++ 00 PictureCollector/verify_tables.py | 27 ++ 12 files changed, 655 insertions(+) create mode 100644 00 PictureCollector/clear_tables.py create mode 100644 00 PictureCollector/collect_pictures.py create mode 100644 00 PictureCollector/create_tables.py create mode 100644 00 PictureCollector/ssh_check_mount.py create mode 100644 00 PictureCollector/ssh_check_tower1.py create mode 100644 00 PictureCollector/ssh_deploy.py create mode 100644 00 PictureCollector/ssh_deploy_tower1.py create mode 100644 00 PictureCollector/ssh_find_userscripts.py create mode 100644 00 PictureCollector/ssh_install_deps.py create mode 100644 00 PictureCollector/ssh_install_tower1.py create mode 100644 00 PictureCollector/stats.py create mode 100644 00 PictureCollector/verify_tables.py diff --git a/00 PictureCollector/clear_tables.py b/00 PictureCollector/clear_tables.py new file mode 100644 index 0000000..3a6deeb --- /dev/null +++ b/00 PictureCollector/clear_tables.py @@ -0,0 +1,16 @@ +import psycopg2 + +conn = psycopg2.connect(host="192.168.1.76", port=5432, user="vladimir.buzalka", + password="Vlado7309208104++", database="fotky_buzalkovi") +conn.autocommit = True +cur = conn.cursor() + +cur.execute("TRUNCATE TABLE zdrojove_soubory, zaloha_obrazku RESTART IDENTITY CASCADE") +print("Tabulky vyprazdneny.") + +cur.execute("SELECT COUNT(*) FROM zaloha_obrazku") +print(f"zaloha_obrazku: {cur.fetchone()[0]} radku") +cur.execute("SELECT COUNT(*) FROM zdrojove_soubory") +print(f"zdrojove_soubory: {cur.fetchone()[0]} radku") + +conn.close() diff --git a/00 PictureCollector/collect_pictures.py b/00 PictureCollector/collect_pictures.py new file mode 100644 index 0000000..4eb6ab2 --- /dev/null +++ b/00 PictureCollector/collect_pictures.py @@ -0,0 +1,284 @@ +""" +collect_pictures.py +------------------- +Prochází všechny shares pod /mnt/user/ (kromě ZalohaVsechObrazku), +najde JPG/JPEG soubory, zkopíruje je (deduplikace BLAKE3) do +/mnt/user/ZalohaVsechObrazku/ a zapíše záznamy do PostgreSQL. + +Tabulky: + zaloha_obrazku – jedna fyzická záloha na unikátní BLAKE3 hash + zdrojove_soubory – všechny nalezené zdrojové soubory (i duplikáty) + +Bezpečné pro opakované spuštění (pokračuje tam, kde skončilo). +""" + +import os +import sys +import shutil +import socket +import logging +from pathlib import Path +from datetime import datetime + +import blake3 +import psycopg2 +from psycopg2.extras import execute_values + +# ── Konfigurace ────────────────────────────────────────────────────────────── + +DB_CONFIG = { + "host": "192.168.1.76", + "port": 5432, + "user": "vladimir.buzalka", + "password": "Vlado7309208104++", + "database": "fotky_buzalkovi", +} + +SOURCE_BASE = Path("/mnt/user") +JPEG_EXTENSIONS = {".jpg", ".jpeg"} + +# Cílová zálohovací složka podle hostname — vždy fyzicky na Tower1 +# Klíč = socket.gethostname(), hodnota = lokální cesta k záloze +ZALOHA_DIR_MAP = { + "Tower1": Path("/mnt/user/ZalohaVsechObrazku"), # Tower1: lokální share + "tower": Path("/mnt/remotes/TOWER1.LAN_ZalohaVsechObrazku"), # tower: NFS mount na Tower1 +} +ZALOHA_DIR_DEFAULT = Path("/mnt/remotes/TOWER1.LAN_ZalohaVsechObrazku") + +# Adresáře které se NIKDY nepoužijí jako zdroj (porovnává se jméno složky, kdekoliv ve stromě) +EXCLUDED_DIR_NAMES = { + "ZalohaVsechObrazku", + "ZalohaVšechObrázků", + "zalohavsechobrazku", # lowercase varianta pro jistotu +} + +LOG_FILE = Path(__file__).parent / "collect_pictures.log" + +# ── Logging ─────────────────────────────────────────────────────────────────── + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)-7s %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + handlers=[ + logging.StreamHandler(sys.stdout), + logging.FileHandler(LOG_FILE, encoding="utf-8"), + ], +) +log = logging.getLogger(__name__) + +# ── SQL ─────────────────────────────────────────────────────────────────────── + +SQL_CREATE_TABLES = """ +CREATE TABLE IF NOT EXISTS zaloha_obrazku ( + id SERIAL PRIMARY KEY, + blake3_hash VARCHAR(64) UNIQUE NOT NULL, + cesta_zalohy TEXT NOT NULL, + nazev_souboru VARCHAR(512) NOT NULL, + velikost BIGINT, + datum_kopirovani TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS zdrojove_soubory ( + id SERIAL PRIMARY KEY, + hostname VARCHAR(255) NOT NULL, + cesta_zdroje TEXT NOT NULL, + nazev_souboru VARCHAR(512) NOT NULL, + velikost BIGINT, + datum_nalezeni TIMESTAMP DEFAULT NOW(), + blake3_hash VARCHAR(64) NOT NULL, + zaloha_id INTEGER REFERENCES zaloha_obrazku(id), + UNIQUE (hostname, cesta_zdroje) +); + +CREATE INDEX IF NOT EXISTS idx_zaloha_hash ON zaloha_obrazku (blake3_hash); +CREATE INDEX IF NOT EXISTS idx_zdroj_hash ON zdrojove_soubory (blake3_hash); +CREATE INDEX IF NOT EXISTS idx_zdroj_zaloha ON zdrojove_soubory (zaloha_id); +CREATE INDEX IF NOT EXISTS idx_zdroj_host ON zdrojove_soubory (hostname); +""" + +SQL_HASH_EXISTS = "SELECT id, cesta_zalohy FROM zaloha_obrazku WHERE blake3_hash = %s" +SQL_SOURCE_EXISTS = "SELECT id FROM zdrojove_soubory WHERE hostname = %s AND cesta_zdroje = %s" + +SQL_INSERT_ZALOHA = """ +INSERT INTO zaloha_obrazku (blake3_hash, cesta_zalohy, nazev_souboru, velikost) +VALUES (%s, %s, %s, %s) +RETURNING id +""" + +SQL_INSERT_ZDROJ = """ +INSERT INTO zdrojove_soubory (hostname, cesta_zdroje, nazev_souboru, velikost, blake3_hash, zaloha_id) +VALUES (%s, %s, %s, %s, %s, %s) +ON CONFLICT (hostname, cesta_zdroje) DO NOTHING +""" + +# ── Pomocné funkce ──────────────────────────────────────────────────────────── + +def compute_blake3(path: Path, chunk: int = 1 << 20) -> str: + h = blake3.blake3() + with open(path, "rb") as f: + while data := f.read(chunk): + h.update(data) + return h.hexdigest() + + +def dest_path_for(source: Path, hostname: str) -> Path: + """ + Záloha vždy na TOWER1 pod /mnt/user/ZalohaVsechObrazku/{hostname}/... + Příklad: + tower /mnt/user/Foto/2023/img.jpg → /mnt/user/ZalohaVsechObrazku/tower/Foto/2023/img.jpg + tower1 /mnt/user/Foto/2023/img.jpg → /mnt/user/ZalohaVsechObrazku/tower1/Foto/2023/img.jpg + """ + try: + relative = source.relative_to(SOURCE_BASE) + except ValueError: + relative = Path(source.name) + return ZALOHA_DIR / hostname / relative + + +def copy_to_backup(source: Path, dest: Path) -> None: + dest.parent.mkdir(parents=True, exist_ok=True) + if dest.exists(): + # Soubor na cílovém místě už je — nepřepisujeme, záloha platí + return + shutil.copy2(source, dest) + + +def is_excluded_dir(name: str) -> bool: + """Vrátí True pokud jméno adresáře patří do seznamu vyloučených (case-insensitive).""" + return name.lower() in {n.lower() for n in EXCLUDED_DIR_NAMES} + + +def iter_jpeg_files(base: Path): + """Generátor: vrací Path na každý JPG/JPEG v base (rekurzivně). + Přeskočí ZALOHA_DIR a jakýkoliv adresář jehož jméno je v EXCLUDED_DIR_NAMES. + """ + for root, dirs, files in os.walk(base, followlinks=False): + root_path = Path(root) + # Neprocházet zálohovací složku (podle plné cesty) + if ZALOHA_DIR in (root_path, *root_path.parents) or root_path == ZALOHA_DIR: + dirs.clear() + continue + # Odfiltrovat vyloučené adresáře ze subadresářů (podle jména, kdekoliv ve stromě) + dirs[:] = [ + d for d in dirs + if root_path / d != ZALOHA_DIR and not is_excluded_dir(d) + ] + for fname in files: + if Path(fname).suffix.lower() in JPEG_EXTENSIONS: + yield root_path / fname + +# ── Hlavní logika ───────────────────────────────────────────────────────────── + +def process(conn, hostname): + cur = conn.cursor() + log.info(f"Hostname zdroje: {hostname}") + + stats = {"nalezeno": 0, "kopirovano": 0, "duplicit": 0, "chyb": 0, "preskoceno": 0} + + for source in iter_jpeg_files(SOURCE_BASE): + stats["nalezeno"] += 1 + src_str = str(source) + + # Přeskočit zdroje, které už jsou v DB zpracovány (pro tento hostname) + cur.execute(SQL_SOURCE_EXISTS, (hostname, src_str)) + if cur.fetchone(): + stats["preskoceno"] += 1 + if stats["preskoceno"] % 500 == 0: + log.info(f"Přeskočeno (již v DB): {stats['preskoceno']}") + continue + + try: + velikost = source.stat().st_size + hash_val = compute_blake3(source) + except (OSError, PermissionError) as e: + log.warning(f"CHYBA čtení: {source} → {e}") + stats["chyb"] += 1 + continue + + # Existuje už záloha s tímto hashem? + cur.execute(SQL_HASH_EXISTS, (hash_val,)) + row = cur.fetchone() + + if row: + # Duplikát — jen zapíšeme zdroj, nekopírujeme + zaloha_id = row[0] + cur.execute(SQL_INSERT_ZDROJ, (hostname, src_str, source.name, velikost, hash_val, zaloha_id)) + conn.commit() + stats["duplicit"] += 1 + log.debug(f"DUPLIKÁT {source.name} (zaloha_id={zaloha_id})") + else: + # Nový unikátní soubor — zkopírovat a zapsat + dest = dest_path_for(source, hostname) + try: + copy_to_backup(source, dest) + except (OSError, shutil.Error) as e: + log.warning(f"CHYBA kopírování: {source} → {e}") + stats["chyb"] += 1 + continue + + cur.execute(SQL_INSERT_ZALOHA, (hash_val, str(dest), source.name, velikost)) + zaloha_id = cur.fetchone()[0] + cur.execute(SQL_INSERT_ZDROJ, (hostname, src_str, source.name, velikost, hash_val, zaloha_id)) + conn.commit() + stats["kopirovano"] += 1 + log.info(f"ZKOPÍROVÁNO [{stats['kopirovano']:>6}] {source}") + + if stats["nalezeno"] % 1000 == 0: + log.info( + f"Průběh: nalezeno={stats['nalezeno']} " + f"nových={stats['kopirovano']} duplikátů={stats['duplicit']} " + f"chyb={stats['chyb']} přeskočeno={stats['preskoceno']}" + ) + + cur.close() + return stats + + +def main(): + global ZALOHA_DIR + hostname = socket.gethostname() + ZALOHA_DIR = ZALOHA_DIR_MAP.get(hostname, ZALOHA_DIR_DEFAULT) + + log.info("=" * 60) + log.info(f"Spuštění: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.info(f"Hostname: {hostname}") + log.info(f"Zdroj: {SOURCE_BASE}") + log.info(f"Záloha: {ZALOHA_DIR}/{hostname}/") + + try: + conn = psycopg2.connect(**DB_CONFIG) + conn.autocommit = False + log.info("PostgreSQL: připojeno") + except Exception as e: + log.error(f"Nelze se připojit k DB: {e}") + sys.exit(1) + + # Vytvoř tabulky pokud neexistují + with conn.cursor() as cur: + cur.execute(SQL_CREATE_TABLES) + conn.commit() + log.info("Tabulky: OK") + + try: + stats = process(conn, hostname) + except KeyboardInterrupt: + log.warning("Přerušeno uživatelem (Ctrl+C) — dosavadní záznamy jsou uloženy.") + conn.rollback() + except Exception as e: + log.error(f"Neočekávaná chyba: {e}", exc_info=True) + conn.rollback() + finally: + conn.close() + + log.info("-" * 60) + log.info(f"Nalezeno JPG/JPEG: {stats['nalezeno']}") + log.info(f"Zkopírováno nových: {stats['kopirovano']}") + log.info(f"Duplikátů (hash): {stats['duplicit']}") + log.info(f"Přeskočeno (v DB): {stats['preskoceno']}") + log.info(f"Chyb: {stats['chyb']}") + log.info("Hotovo.") + + +if __name__ == "__main__": + main() diff --git a/00 PictureCollector/create_tables.py b/00 PictureCollector/create_tables.py new file mode 100644 index 0000000..2e16636 --- /dev/null +++ b/00 PictureCollector/create_tables.py @@ -0,0 +1,44 @@ +import psycopg2 + +DB_CONFIG = { + "host": "192.168.1.76", + "port": 5432, + "user": "vladimir.buzalka", + "password": "Vlado7309208104++", + "database": "fotky_buzalkovi", +} + +SQL = """ +CREATE TABLE IF NOT EXISTS zaloha_obrazku ( + id SERIAL PRIMARY KEY, + blake3_hash VARCHAR(64) UNIQUE NOT NULL, + cesta_zalohy TEXT NOT NULL, + nazev_souboru VARCHAR(512) NOT NULL, + velikost BIGINT, + datum_kopirovani TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS zdrojove_soubory ( + id SERIAL PRIMARY KEY, + hostname VARCHAR(255) NOT NULL, + cesta_zdroje TEXT NOT NULL, + nazev_souboru VARCHAR(512) NOT NULL, + velikost BIGINT, + datum_nalezeni TIMESTAMP DEFAULT NOW(), + blake3_hash VARCHAR(64) NOT NULL, + zaloha_id INTEGER REFERENCES zaloha_obrazku(id), + UNIQUE (hostname, cesta_zdroje) +); + +CREATE INDEX IF NOT EXISTS idx_zaloha_hash ON zaloha_obrazku (blake3_hash); +CREATE INDEX IF NOT EXISTS idx_zdroj_hash ON zdrojove_soubory (blake3_hash); +CREATE INDEX IF NOT EXISTS idx_zdroj_zaloha ON zdrojove_soubory (zaloha_id); +CREATE INDEX IF NOT EXISTS idx_zdroj_host ON zdrojove_soubory (hostname); +""" + +conn = psycopg2.connect(**DB_CONFIG) +conn.autocommit = True +with conn.cursor() as cur: + cur.execute(SQL) +conn.close() +print("Tabulky vytvoreny.") diff --git a/00 PictureCollector/ssh_check_mount.py b/00 PictureCollector/ssh_check_mount.py new file mode 100644 index 0000000..f2d3648 --- /dev/null +++ b/00 PictureCollector/ssh_check_mount.py @@ -0,0 +1,17 @@ +import paramiko + +client = paramiko.SSHClient() +client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +client.connect("192.168.1.76", username="root", password="7309208104", + look_for_keys=False, allow_agent=False) + +def run(cmd): + _, stdout, stderr = client.exec_command(cmd) + out = stdout.read().decode().strip() + if out: print(out) + +run("mount | grep -i zaloha") +run("ls /mnt/remotes/ 2>/dev/null | grep -i zaloha") +run("ls /mnt/user/ 2>/dev/null | grep -i zaloha") + +client.close() diff --git a/00 PictureCollector/ssh_check_tower1.py b/00 PictureCollector/ssh_check_tower1.py new file mode 100644 index 0000000..f82957d --- /dev/null +++ b/00 PictureCollector/ssh_check_tower1.py @@ -0,0 +1,32 @@ +import paramiko + +client = paramiko.SSHClient() +client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +client.connect("192.168.1.50", username="root", password="Vlado7309208104++", + look_for_keys=False, allow_agent=False) + +def run(cmd): + _, stdout, stderr = client.exec_command(cmd) + out = stdout.read().decode().strip() + err = stderr.read().decode().strip() + if out: print(out) + if err: print(f"[err] {err}") + +print("--- Hostname ---") +run("hostname") + +print("\n--- Python ---") +run("python3 --version") +run("pip3 --version") + +print("\n--- Balicky (blake3, psycopg2) ---") +run("pip3 list 2>/dev/null | grep -iE 'blake3|psycopg2'") + +print("\n--- ZalohaVsechObrazku ---") +run("ls /mnt/user/ | grep -i zaloha") +run("ls /mnt/remotes/ 2>/dev/null | grep -i zaloha") + +print("\n--- User Scripts ---") +run("ls /boot/config/plugins/user.scripts/scripts/ 2>/dev/null | head -5") + +client.close() diff --git a/00 PictureCollector/ssh_deploy.py b/00 PictureCollector/ssh_deploy.py new file mode 100644 index 0000000..f70beb5 --- /dev/null +++ b/00 PictureCollector/ssh_deploy.py @@ -0,0 +1,49 @@ +import paramiko +import stat + +HOST = "192.168.1.76" +USER = "root" +PASSWORD = "7309208104" +SCRIPT_NAME = "CollectPictures" +REMOTE_BASE = f"/boot/config/plugins/user.scripts/scripts/{SCRIPT_NAME}" +REMOTE_PY = f"{REMOTE_BASE}/collect_pictures.py" +REMOTE_SCRIPT = f"{REMOTE_BASE}/script" +LOCAL_SCRIPT = "00 PictureCollector/collect_pictures.py" + +# -u = unbuffered stdout, každý řádek se okamžitě zobrazí v konzoli Unraid +BASH_WRAPPER = """#!/bin/bash +python3 -u /boot/config/plugins/user.scripts/scripts/CollectPictures/collect_pictures.py +""" + +client = paramiko.SSHClient() +client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +client.connect(HOST, username=USER, password=PASSWORD, look_for_keys=False, allow_agent=False) + +def run(cmd): + _, stdout, stderr = client.exec_command(cmd) + out = stdout.read().decode().strip() + err = stderr.read().decode().strip() + if out: print(out) + if err: print(f"[err] {err}") + +sftp = client.open_sftp() + +sftp.put(LOCAL_SCRIPT, REMOTE_PY) +sftp.chmod(REMOTE_PY, stat.S_IRUSR | stat.S_IWUSR) +print(f"Python skript: {REMOTE_PY}") + +with sftp.open(REMOTE_SCRIPT, "w") as f: + f.write(BASH_WRAPPER) +sftp.chmod(REMOTE_SCRIPT, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP) +print(f"Bash wrapper: {REMOTE_SCRIPT}") + +with sftp.open(f"{REMOTE_BASE}/name", "w") as f: + f.write("CollectPictures") + +sftp.close() + +print("\n--- Výsledek ---") +run(f"cat {REMOTE_SCRIPT}") + +client.close() +print("Hotovo.") diff --git a/00 PictureCollector/ssh_deploy_tower1.py b/00 PictureCollector/ssh_deploy_tower1.py new file mode 100644 index 0000000..f7dd6bc --- /dev/null +++ b/00 PictureCollector/ssh_deploy_tower1.py @@ -0,0 +1,49 @@ +import paramiko +import stat + +HOST = "192.168.1.50" +USER = "root" +PASSWORD = "Vlado7309208104++" +SCRIPT_NAME = "CollectPictures" +REMOTE_BASE = f"/boot/config/plugins/user.scripts/scripts/{SCRIPT_NAME}" +REMOTE_PY = f"{REMOTE_BASE}/collect_pictures.py" +REMOTE_SCRIPT = f"{REMOTE_BASE}/script" +LOCAL_SCRIPT = "00 PictureCollector/collect_pictures.py" + +BASH_WRAPPER = """#!/bin/bash +python3 -u /boot/config/plugins/user.scripts/scripts/CollectPictures/collect_pictures.py +""" + +client = paramiko.SSHClient() +client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +client.connect(HOST, username=USER, password=PASSWORD, look_for_keys=False, allow_agent=False) + +def run(cmd): + _, stdout, stderr = client.exec_command(cmd) + out = stdout.read().decode().strip() + err = stderr.read().decode().strip() + if out: print(out) + if err: print(f"[err] {err}") + +run(f"mkdir -p {REMOTE_BASE}") + +sftp = client.open_sftp() +sftp.put(LOCAL_SCRIPT, REMOTE_PY) +sftp.chmod(REMOTE_PY, stat.S_IRUSR | stat.S_IWUSR) +print(f"Python skript: {REMOTE_PY}") + +with sftp.open(REMOTE_SCRIPT, "w") as f: + f.write(BASH_WRAPPER) +sftp.chmod(REMOTE_SCRIPT, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP) +print(f"Bash wrapper: {REMOTE_SCRIPT}") + +with sftp.open(f"{REMOTE_BASE}/name", "w") as f: + f.write("CollectPictures") +sftp.close() + +print("\n--- Vysledek ---") +run(f"ls -la {REMOTE_BASE}/") +run(f"cat {REMOTE_SCRIPT}") + +client.close() +print("Hotovo.") diff --git a/00 PictureCollector/ssh_find_userscripts.py b/00 PictureCollector/ssh_find_userscripts.py new file mode 100644 index 0000000..641587e --- /dev/null +++ b/00 PictureCollector/ssh_find_userscripts.py @@ -0,0 +1,18 @@ +import paramiko + +client = paramiko.SSHClient() +client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +client.connect("192.168.1.76", username="root", password="7309208104", + look_for_keys=False, allow_agent=False) + +def run(cmd): + _, stdout, stderr = client.exec_command(cmd) + out = stdout.read().decode().strip() + err = stderr.read().decode().strip() + if out: print(out) + if err: print(f"[err] {err}") + +run("find /boot/config/plugins/user.scripts/scripts -maxdepth 2 -type d 2>/dev/null || echo 'NENALEZENO'") +run("ls /boot/config/plugins/user.scripts/scripts/ 2>/dev/null || echo 'prazdne nebo neexistuje'") + +client.close() diff --git a/00 PictureCollector/ssh_install_deps.py b/00 PictureCollector/ssh_install_deps.py new file mode 100644 index 0000000..6d7fc69 --- /dev/null +++ b/00 PictureCollector/ssh_install_deps.py @@ -0,0 +1,49 @@ +import paramiko +import sys + +HOST = "192.168.1.76" +USER = "root" +PASSWORD = "7309208104" + +client = paramiko.SSHClient() +client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +client.connect(HOST, username=USER, password=PASSWORD, look_for_keys=False, allow_agent=False) +print(f"Připojeno na {HOST}") + +def run(cmd, show=True): + stdin, stdout, stderr = client.exec_command(cmd, get_pty=False) + out = stdout.read().decode().strip() + err = stderr.read().decode().strip() + if show: + if out: + print(out) + if err: + print(f"[stderr] {err}", file=sys.stderr) + return out, err + +# 1. Ověř Python +print("\n--- Python ---") +run("python3 --version") +run("pip3 --version || pip --version") + +# 2. Co už je nainstalováno +print("\n--- Nainstalované balíčky (blake3, psycopg2) ---") +out, _ = run("pip3 list 2>/dev/null || pip list 2>/dev/null") +for line in out.splitlines(): + if any(pkg in line.lower() for pkg in ("blake3", "psycopg2")): + print(f" FOUND: {line}") + +# 3. Instalace chybějících +print("\n--- Instalace blake3 ---") +run("pip3 install blake3 || pip install blake3") + +print("\n--- Instalace psycopg2-binary ---") +run("pip3 install psycopg2-binary || pip install psycopg2-binary") + +# 4. Ověření +print("\n--- Ověření importů ---") +run("python3 -c \"import blake3; print('blake3 OK:', blake3.__version__)\"") +run("python3 -c \"import psycopg2; print('psycopg2 OK:', psycopg2.__version__)\"") + +client.close() +print("\nHotovo.") diff --git a/00 PictureCollector/ssh_install_tower1.py b/00 PictureCollector/ssh_install_tower1.py new file mode 100644 index 0000000..1942b0f --- /dev/null +++ b/00 PictureCollector/ssh_install_tower1.py @@ -0,0 +1,22 @@ +import paramiko + +client = paramiko.SSHClient() +client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +client.connect("192.168.1.50", username="root", password="Vlado7309208104++", + look_for_keys=False, allow_agent=False) + +def run(cmd): + _, stdout, stderr = client.exec_command(cmd) + out = stdout.read().decode().strip() + err = stderr.read().decode().strip() + if out: print(out) + if err: print(f"[err] {err}") + +print("--- Instalace psycopg2-binary ---") +run("pip3 install psycopg2-binary") + +print("\n--- Overeni ---") +run("python3 -c \"import psycopg2; print('psycopg2 OK:', psycopg2.__version__)\"") +run("python3 -c \"import blake3; print('blake3 OK:', blake3.__version__)\"") + +client.close() diff --git a/00 PictureCollector/stats.py b/00 PictureCollector/stats.py new file mode 100644 index 0000000..f18ce39 --- /dev/null +++ b/00 PictureCollector/stats.py @@ -0,0 +1,48 @@ +import psycopg2 + +conn = psycopg2.connect(host="192.168.1.76", port=5432, user="vladimir.buzalka", + password="Vlado7309208104++", database="fotky_buzalkovi") +cur = conn.cursor() + +cur.execute(""" + SELECT + COUNT(*) AS unikatnich_souboru, + SUM(velikost) AS celkova_velikost, + AVG(velikost) AS prumerna_velikost, + MIN(velikost) AS nejmensi, + MAX(velikost) AS nejvetsi + FROM zaloha_obrazku +""") +z = cur.fetchone() + +cur.execute("SELECT COUNT(*), COUNT(DISTINCT hostname) FROM zdrojove_soubory") +s = cur.fetchone() + +cur.execute("SELECT hostname, COUNT(*) FROM zdrojove_soubory GROUP BY hostname ORDER BY hostname") +hosts = cur.fetchall() + +conn.close() + +def fmt(b): + if b is None: return "N/A" + for unit in ("B", "KB", "MB", "GB", "TB"): + if b < 1024: return f"{b:.1f} {unit}" + b /= 1024 + return f"{b:.1f} PB" + +print("=" * 45) +print(" ZÁLOHA — statistiky") +print("=" * 45) +print(f" Unikátních souborů (záloha): {z[0]:>10,}") +print(f" Celková velikost: {fmt(z[1]):>10}") +print(f" Průměrná velikost: {fmt(z[2]):>10}") +print(f" Nejmenší soubor: {fmt(z[3]):>10}") +print(f" Největší soubor: {fmt(z[4]):>10}") +print() +print(f" Zdrojových záznamů celkem: {s[0]:>10,}") +print(f" Počet zdrojových serverů: {s[1]:>10,}") +print() +print(" Záznamy podle hostname:") +for host, count in hosts: + print(f" {host:<20} {count:>8,} souborů") +print("=" * 45) diff --git a/00 PictureCollector/verify_tables.py b/00 PictureCollector/verify_tables.py new file mode 100644 index 0000000..e16d960 --- /dev/null +++ b/00 PictureCollector/verify_tables.py @@ -0,0 +1,27 @@ +import psycopg2 + +conn = psycopg2.connect(host="192.168.1.76", port=5432, user="vladimir.buzalka", + password="Vlado7309208104++", database="fotky_buzalkovi") +cur = conn.cursor() + +cur.execute("SELECT table_name FROM information_schema.tables WHERE table_schema='public' ORDER BY table_name") +print("Tabulky:") +for r in cur.fetchall(): + print(f" {r[0]}") + +cur.execute("SELECT indexname FROM pg_indexes WHERE schemaname='public' ORDER BY indexname") +print("Indexy:") +for r in cur.fetchall(): + print(f" {r[0]}") + +cur.execute(""" + SELECT column_name, data_type, is_nullable + FROM information_schema.columns + WHERE table_schema='public' AND table_name='zdrojove_soubory' + ORDER BY ordinal_position +""") +print("\nSloupce zdrojove_soubory:") +for r in cur.fetchall(): + print(f" {r[0]:20s} {r[1]:20s} nullable={r[2]}") + +conn.close()