CHAT_OUTPUT_BEGIN
COMMAND_ID=HOMELAB-SKLADCHIK-COOKIE-EXACT-LOGIC-419
STATUS=OK
RC=0
HOST=pve01
MODE=read-only
COMPONENT=cluster-audit
REFERENCE_REGISTER_CHECK=OK
REFERENCE_SHA256=5763f2f8edc75fcf6f3951f8c95896d1cc112ceccb6cf5b64a383f540a4deb8e
ERROR_REGISTER_CHECK=OK
ERROR_REGISTER_SHA256=08b3a769b8e38eaa1acec915352fb328180d70caa7ff6cd9d1ec0da426262752
COMMAND_SHA256=77896618b46e2f97362240deb743fb8e625075381e57377ee0314130be2b2026
DUPLICATE_FAILED_COMMAND_BLOCKED=false
EXECUTION_STARTED=true
CHANGES_MADE=false
SANITIZED=yes
SECRETS_INCLUDED=no
PRIVATE_ADDRESSES_INCLUDED=no
RAW_EVIDENCE_SHA256=63d14ea58a82bd34d78884b4db6d047e8fa64822200c7bc20a8587057f3f9e25
SANITIZED_OUTPUT_SHA256=702451cb7bfbc7d831b17b7e062a00f5215e5dc3729bd8e3331dfe4c3dc28851
OUTPUT_BEGIN
DETAIL=SCRIPT_SHA256=35c68ae0929ff3795be2bde40de334fc8e26413a3d8a620a137be90d1f2c164e
DETAIL=EXACT_COOKIE_LOGIC_BEGIN
DETAIL=SOURCE_BLOCK=TOP_LEVEL_CONSTANT|LINES=12-12
DETAIL=SOURCE_LINE=0012:EDGE = os.environ.get("SKLADCHIK_EDGE", "debian@[PRIVATE_IP]")
DETAIL=SOURCE_BLOCK=TOP_LEVEL_CONSTANT|LINES=22-22
DETAIL=SOURCE_LINE=0022:COOKIE_EXPIRY_WARN_SECONDS = int(os.environ.get("SKLADCHIK_COOKIE_EXPIRY_WARN_SECONDS", "604800"))
DETAIL=SOURCE_BLOCK=FUNCTION:gather|LINES=105-271
DETAIL=SOURCE_LINE=0105:def gather():
DETAIL=SOURCE_LINE=0106:    facts = {}
DETAIL=SOURCE_LINE=0107:    problems = []
DETAIL=SOURCE_LINE=0108:
DETAIL=SOURCE_LINE=0109:    edge_cmd = r'''
DETAIL=SOURCE_LINE=0110:set +e
DETAIL=SOURCE_LINE=0111:echo __MONITOR_HEALTH_BEGIN__
DETAIL=SOURCE_LINE=0112:cat /var/lib/skladchik-reports-monitor/health.txt 2>/dev/null || true
DETAIL=SOURCE_LINE=0113:echo __MONITOR_HEALTH_END__
DETAIL=SOURCE_LINE=0114:echo __ALERT_HEALTH_BEGIN__
DETAIL=SOURCE_LINE=0115:cat /var/lib/skladchik-reports-monitor/alert-health.txt 2>/dev/null || true
DETAIL=SOURCE_LINE=0116:echo __ALERT_HEALTH_END__
DETAIL=SOURCE_LINE=0117:echo __TIMERS_BEGIN__
DETAIL=SOURCE_LINE=0118:for u in skladchik-reports-monitor.timer skladchik-reports-monitor-watchdog.timer skladchik-reports-monitor-heartbeat.timer skladchik-reports-monitor-weekly-summary.timer skladchik-reports-monitor-age-alert.timer skladchik-reports-monitor-event-selftest.timer; do echo "$u enabled=$(systemctl is-enabled "$u" 2>/dev/null) active=$(systemctl is-active "$u" 2>/dev/null)"; done
DETAIL=SOURCE_LINE=0119:echo __TIMERS_END__
DETAIL=SOURCE_LINE=0120:echo __ROUTE_BEGIN__
DETAIL=SOURCE_LINE=0121:ip route get 1.1.1.1 2>&1 | head -n 1
DETAIL=SOURCE_LINE=0122:echo __ROUTE_END__
DETAIL=SOURCE_LINE=0123:echo __FILES_BEGIN__
DETAIL=SOURCE_LINE=0124:test -s /opt/skladchik-reports-monitor/monitor.py && echo monitor_py=yes || echo monitor_py=no
DETAIL=SOURCE_LINE=0125:test -s /etc/skladchik-reports-monitor/config.ini && echo config_ini=yes || echo config_ini=no
DETAIL=SOURCE_LINE=0126:test -s /etc/skladchik-reports-monitor/secrets.env && echo secrets_env=<REDACTED>
DETAIL=SOURCE_LINE=0127:test -s /var/lib/skladchik-reports-monitor/cookies.jar && echo cookies_jar=yes || echo cookies_jar=no
DETAIL=SOURCE_LINE=0128:[ -s /var/lib/skladchik-reports-monitor/cookies.jar ] && echo cookies_jar_bytes=$(stat -c %s /var/lib/skladchik-reports-monitor/cookies.jar 2>/dev/null) || echo cookies_jar_bytes=0
DETAIL=SOURCE_LINE=0129:test -s /opt/skladchik-reports-monitor/operational_digest.py && echo operational_digest_py=yes || echo operational_digest_py=no
DETAIL=SOURCE_LINE=0130:test -s /opt/skladchik-reports-monitor/event-selftest-runner.py && echo event_selftest_runner_py=yes || echo event_selftest_runner_py=no
DETAIL=SOURCE_LINE=0131:test -s /opt/skladchik-reports-monitor/event-selftest-core && echo event_selftest_core=yes || echo event_selftest_core=no
DETAIL=SOURCE_LINE=0132:test -s /var/lib/skladchik-reports-monitor/weekly-summary-health.txt && echo weekly_summary_health=yes || echo weekly_summary_health=no
DETAIL=SOURCE_LINE=0133:test -s /var/lib/skladchik-reports-monitor/age-alert-health.txt && echo age_alert_health=yes || echo age_alert_health=no
DETAIL=SOURCE_LINE=0134:test -s /var/lib/skladchik-reports-monitor/event-selftest-health.txt && echo event_selftest_health=yes || echo event_selftest_health=no
DETAIL=SOURCE_LINE=0135:python3 -m py_compile /opt/skladchik-reports-monitor/monitor.py /opt/skladchik-reports-monitor/operational_digest.py /opt/skladchik-reports-monitor/event-selftest-runner.py /opt/skladchik-reports-monitor/event-selftest-core >/dev/null 2>&1 && echo py_compile=yes || echo py_compile=no
DETAIL=SOURCE_LINE=0136:echo __FILES_END__
DETAIL=SOURCE_LINE=0137:'''
DETAIL=SOURCE_LINE=0138:    rc, out = ssh_edge(edge_cmd, timeout=45)
DETAIL=SOURCE_LINE=0139:    facts["edge_ssh_rc"] = str(rc)
DETAIL=SOURCE_LINE=0140:    if rc != 0:
DETAIL=SOURCE_LINE=0141:        problems.append("edge_ssh_failed")
DETAIL=SOURCE_LINE=0142:
DETAIL=SOURCE_LINE=0143:    def section(name):
DETAIL=SOURCE_LINE=0144:        m = re.search(rf"__{name}_BEGIN__\n(.*?)\n__{name}_END__", out, re.S)
DETAIL=SOURCE_LINE=0145:        return m.group(1).strip() if m else ""
DETAIL=SOURCE_LINE=0146:
DETAIL=SOURCE_LINE=0147:    mon = parse_env_text(section("MONITOR_HEALTH"))
DETAIL=SOURCE_LINE=0148:    facts["monitor_status"] = mon.get("STATUS", "MISSING")
DETAIL=SOURCE_LINE=0149:    facts["monitor_reports"] = mon.get("reports", "")
DETAIL=SOURCE_LINE=0150:    facts["monitor_open"] = mon.get("open", "")
DETAIL=SOURCE_LINE=0151:    facts["monitor_resolved"] = mon.get("resolved", "")
DETAIL=SOURCE_LINE=0152:    facts["monitor_smtp_configured"] = mon.get("smtp_configured", "")
DETAIL=SOURCE_LINE=0153:    facts["session_keepalive"] = mon.get("session_keepalive", "")
DETAIL=SOURCE_LINE=0154:    facts["cookie_jar"] = mon.get("cookie_jar", "")
DETAIL=SOURCE_LINE=0155:    facts["cookie_jar_bytes"] = mon.get("cookie_jar_bytes", "")
DETAIL=SOURCE_LINE=0156:    facts["cookie_expiry_status"] = mon.get("cookie_expiry_status", "")
DETAIL=SOURCE_LINE=0157:    facts["cookie_expiry_utc"] = mon.get("cookie_expiry_utc", "")
DETAIL=SOURCE_LINE=0158:    facts["cookie_expires_in_seconds"] = mon.get("cookie_expires_in_seconds", "")
DETAIL=SOURCE_LINE=0159:    facts["cookie_expires_in_days"] = mon.get("cookie_expires_in_days", "")
DETAIL=SOURCE_LINE=0160:    facts["cookie_persistent_count"] = mon.get("cookie_persistent_count", "")
DETAIL=SOURCE_LINE=0161:    facts["monitor_health_age_seconds"] = str(age_seconds_from_ts(mon.get("TS")))
DETAIL=SOURCE_LINE=0162:    if mon.get("STATUS") != "OK":
DETAIL=SOURCE_LINE=0163:        problems.append("monitor_status_not_ok")
DETAIL=SOURCE_LINE=0164:    if int(facts["monitor_health_age_seconds"]) > MONITOR_HEALTH_AGE_MAX:
DETAIL=SOURCE_LINE=0165:        problems.append("monitor_health_stale")
DETAIL=SOURCE_LINE=0166:    if mon.get("reports") in ("", "0"):
DETAIL=SOURCE_LINE=0167:        problems.append("monitor_reports_zero_or_missing")
DETAIL=SOURCE_LINE=0168:    if mon.get("session_keepalive") != "persistent_cookie_jar":
DETAIL=SOURCE_LINE=0169:        problems.append("session_keepalive_not_persistent")
DETAIL=SOURCE_LINE=0170:    if mon.get("cookie_jar") not in ("yes", "empty"):
DETAIL=SOURCE_LINE=0171:        problems.append("monitor_cookie_jar_bad")
DETAIL=SOURCE_LINE=0172:    expiry_status = mon.get("cookie_expiry_status", "")
DETAIL=SOURCE_LINE=0173:    try:
DETAIL=SOURCE_LINE=0174:        expires_in = int(mon.get("cookie_expires_in_seconds", ""))
DETAIL=SOURCE_LINE=0175:    except Exception:
DETAIL=SOURCE_LINE=0176:        expires_in = None
DETAIL=SOURCE_LINE=0177:    if expiry_status in ("", "NO_COOKIE_JAR", "NO_PERSISTENT_COOKIE", "ERROR"):
DETAIL=SOURCE_LINE=0178:        problems.append("cookie_expiry_missing")
DETAIL=SOURCE_LINE=0179:    elif expiry_status == "EXPIRED" or (expires_in is not None and expires_in <= 0):
DETAIL=SOURCE_LINE=0180:        problems.append("cookie_expiry_expired")
DETAIL=SOURCE_LINE=0181:    elif expiry_status == "WARN" or (expires_in is not None and expires_in < COOKIE_EXPIRY_WARN_SECONDS):
DETAIL=SOURCE_LINE=0182:        problems.append("cookie_expiry_warn")
DETAIL=SOURCE_LINE=0183:
DETAIL=SOURCE_LINE=0184:    timers_text = section("TIMERS")
DETAIL=SOURCE_LINE=0185:    for unit in ["skladchik-reports-monitor.timer", "skladchik-reports-monitor-watchdog.timer", "skladchik-reports-monitor-heartbeat.timer", "skladchik-reports-monitor-weekly-summary.timer", "skladchik-reports-monitor-age-alert.timer", "skladchik-reports-monitor-event-selftest.timer"]:
DETAIL=SOURCE_LINE=0186:        safe = unit.replace(".timer", "").replace("_", "-")
DETAIL=SOURCE_LINE=0187:        m = re.search(re.escape(unit) + r"\s+enabled=([^\s]+)\s+active=([^\s]+)", timers_text)
DETAIL=SOURCE_LINE=0188:        enabled = m.group(1) if m else "missing"
DETAIL=SOURCE_LINE=0189:        active = m.group(2) if m else "missing"
DETAIL=SOURCE_LINE=0190:        facts[f"edge_{safe}_timer_enabled"] = enabled
DETAIL=SOURCE_LINE=0191:        facts[f"edge_{safe}_timer_active"] = active
DETAIL=SOURCE_LINE=0192:        if enabled != "enabled" or active != "active":
DETAIL=SOURCE_LINE=0193:            problems.append(f"edge_{safe}_timer_bad")
DETAIL=SOURCE_LINE=0194:
DETAIL=SOURCE_LINE=0195:    route_lines = section("ROUTE").splitlines()
DETAIL=SOURCE_LINE=0196:    route = route_lines[0] if route_lines else ""
DETAIL=SOURCE_LINE=0197:    facts["netbird_route"] = route
DETAIL=SOURCE_LINE=0198:    if " wt0 " not in (" " + route + " "):
DETAIL=SOURCE_LINE=0199:        problems.append("netbird_route_not_wt0")
DETAIL=SOURCE_LINE=0200:
DETAIL=SOURCE_LINE=0201:    files = parse_env_text(section("FILES"))
DETAIL=SOURCE_LINE=0202:    for k in ["monitor_py", "config_ini", "secrets_env", "cookies_jar", "operational_digest_py", "event_selftest_runner_py", "event_selftest_core", "weekly_summary_health", "age_alert_health", "event_selftest_health", "py_compile"]:
DETAIL=SOURCE_LINE=0203:        facts[k] = files.get(k, "no")
DETAIL=SOURCE_LINE=0204:        if facts[k] != "yes":
DETAIL=SOURCE_LINE=0205:            problems.append(f"edge_{k}_bad")
DETAIL=SOURCE_LINE=0206:    facts["cookies_jar_bytes_file"] = files.get("cookies_jar_bytes", "0")
DETAIL=SOURCE_LINE=0207:
DETAIL=SOURCE_LINE=0208:    backup = read_env_file(PVE_BACKUP_HEALTH)
DETAIL=SOURCE_LINE=0209:    facts["backup_status"] = backup.get("STATUS", "MISSING")
DETAIL=SOURCE_LINE=0210:    facts["backup_run_id"] = backup.get("RUN_ID", "")
DETAIL=SOURCE_LINE=0211:    facts["backup_size_bytes"] = backup.get("SIZE_BYTES", "")
DETAIL=SOURCE_LINE=0212:    facts["backup_version"] = backup.get("BACKUP_VERSION", "")
DETAIL=SOURCE_LINE=0213:    facts["backup_cookie_jar_backed_up"] = backup.get("COOKIE_JAR_BACKED_UP", "")
DETAIL=SOURCE_LINE=0214:    facts["backup_extension_health_files_backed_up"] = backup.get("EXTENSION_HEALTH_FILES_BACKED_UP", "")
DETAIL=SOURCE_LINE=0215:    facts["backup_title_in_email_subject"] = backup.get("TITLE_IN_EMAIL_SUBJECT", "")
DETAIL=SOURCE_LINE=0216:    facts["backup_health_age_seconds"] = str(age_seconds_from_ts(backup.get("TS")))
DETAIL=SOURCE_LINE=0217:    if backup.get("STATUS") != "OK":
DETAIL=SOURCE_LINE=0218:        problems.append("backup_status_not_ok")
DETAIL=SOURCE_LINE=0219:    if backup.get("COOKIE_JAR_BACKED_UP") != "yes":
DETAIL=SOURCE_LINE=0220:        problems.append("backup_cookie_jar_not_marked")
DETAIL=SOURCE_LINE=0221:    if backup.get("BACKUP_VERSION") != "4":
DETAIL=SOURCE_LINE=0222:        problems.append("backup_version_not_4")
DETAIL=SOURCE_LINE=0223:    if backup.get("EXTENSION_HEALTH_FILES_BACKED_UP") != "yes":
DETAIL=SOURCE_LINE=0224:        problems.append("backup_extension_health_files_not_marked")
DETAIL=SOURCE_LINE=0225:    if backup.get("TITLE_IN_EMAIL_SUBJECT") != "yes":
DETAIL=SOURCE_LINE=0226:        problems.append("email_title_subject_not_marked")
DETAIL=SOURCE_LINE=0227:    if int(facts["backup_health_age_seconds"]) > BACKUP_HEALTH_AGE_MAX:
DETAIL=SOURCE_LINE=0228:        problems.append("backup_health_stale")
DETAIL=SOURCE_LINE=0229:    try:
DETAIL=SOURCE_LINE=0230:        if int(backup.get("SIZE_BYTES", "0")) <= 0:
DETAIL=SOURCE_LINE=0231:            problems.append("backup_size_bad")
DETAIL=SOURCE_LINE=0232:    except Exception:
DETAIL=SOURCE_LINE=0233:        problems.append("backup_size_bad")
DETAIL=SOURCE_LINE=0234:
DETAIL=SOURCE_LINE=0235:    restore = read_env_file(PVE_RESTORE_HEALTH)
DETAIL=SOURCE_LINE=0236:    facts["restore_status"] = restore.get("STATUS", "MISSING")
DETAIL=SOURCE_LINE=0237:    facts["restore_run_id"] = restore.get("RUN_ID", "")
DETAIL=SOURCE_LINE=0238:    facts["restore_health_age_seconds"] = str(age_seconds_from_ts(restore.get("TS")))
DETAIL=SOURCE_LINE=0239:    for key in ["CLOUD_1_STATUS", "CLOUD_2_STATUS", "SHA256_OK", "TAR_OK", "REQUIRED_FILES_OK", "COOKIE_JAR_OK", "EXTENSION_HEALTH_FILES_OK", "EXTENSION_SCRIPTS_OK", "EXTENSION_UNITS_OK", "SQLITE_OK", "PY_COMPILE_OK"]:
DETAIL=SOURCE_LINE=0240:        facts[key.lower()] = restore.get(key, "")
DETAIL=SOURCE_LINE=0241:    if restore.get("STATUS") != "OK":
DETAIL=SOURCE_LINE=0242:        problems.append("restore_status_not_ok")
DETAIL=SOURCE_LINE=0243:    if int(facts["restore_health_age_seconds"]) > RESTORE_HEALTH_AGE_MAX:
DETAIL=SOURCE_LINE=0244:        problems.append("restore_health_stale")
DETAIL=SOURCE_LINE=0245:    for key in ["CLOUD_1_STATUS", "CLOUD_2_STATUS"]:
DETAIL=SOURCE_LINE=0246:        if restore.get(key) != "OK":
DETAIL=SOURCE_LINE=0247:            problems.append(key.lower() + "_bad")
DETAIL=SOURCE_LINE=0248:    for key in ["SHA256_OK", "TAR_OK", "REQUIRED_FILES_OK", "COOKIE_JAR_OK", "EXTENSION_HEALTH_FILES_OK", "EXTENSION_SCRIPTS_OK", "EXTENSION_UNITS_OK", "SQLITE_OK", "PY_COMPILE_OK"]:
DETAIL=SOURCE_LINE=0249:        if restore.get(key) != "yes":
DETAIL=SOURCE_LINE=0250:            problems.append(key.lower() + "_bad")
DETAIL=SOURCE_LINE=0251:
DETAIL=SOURCE_LINE=0252:    local_units = [
DETAIL=SOURCE_LINE=0253:        "skladchik-reports-monitor-backup.timer",
DETAIL=SOURCE_LINE=0254:        "skladchik-reports-monitor-backup-restore-check.timer",
DETAIL=SOURCE_LINE=0255:        "skladchik-reports-monitor-supervisor.timer",
DETAIL=SOURCE_LINE=0256:        "skladchik-reports-monitor-monthly-selftest-watch.timer",
DETAIL=SOURCE_LINE=0257:    ]
DETAIL=SOURCE_LINE=0258:    props = systemctl_props(local_units)
DETAIL=SOURCE_LINE=0259:    for u, p in props.items():
DETAIL=SOURCE_LINE=0260:        base = u.replace(".timer", "").replace("-", "_")
DETAIL=SOURCE_LINE=0261:        facts[base + "_timer_enabled"] = p["enabled"]
DETAIL=SOURCE_LINE=0262:        facts[base + "_timer_active"] = p["active"]
DETAIL=SOURCE_LINE=0263:        if u != "skladchik-reports-monitor-supervisor.timer":
DETAIL=SOURCE_LINE=0264:            if p["enabled"] != "enabled" or p["active"] != "active":
DETAIL=SOURCE_LINE=0265:                problems.append(base + "_timer_bad")
DETAIL=SOURCE_LINE=0266:        else:
DETAIL=SOURCE_LINE=0267:            # Before first enable it can be disabled/inactive during test. After enable next runs will see enabled/active.
DETAIL=SOURCE_LINE=0268:            if p["enabled"] not in ("enabled", "disabled") or p["active"] not in ("active", "inactive"):
DETAIL=SOURCE_LINE=0269:                problems.append(base + "_timer_unknown")
DETAIL=SOURCE_LINE=0270:
DETAIL=SOURCE_LINE=0271:    return facts, problems
DETAIL=SOURCE_BLOCK=FUNCTION:render_body|LINES=273-321
DETAIL=SOURCE_LINE=0273:def render_body(status, facts, problems, mode):
DETAIL=SOURCE_LINE=0274:    ru_status = "OK" if status == "OK" else "АВАРИЯ"
DETAIL=SOURCE_LINE=0275:    lines = [
DETAIL=SOURCE_LINE=0276:        f"Статус надзора: {ru_status}",
DETAIL=SOURCE_LINE=0277:        f"Время UTC: {iso()}",
DETAIL=SOURCE_LINE=0278:        "",
DETAIL=SOURCE_LINE=0279:        "Монитор жалоб:",
DETAIL=SOURCE_LINE=0280:        f"- статус: {facts.get('monitor_status')}",
DETAIL=SOURCE_LINE=0281:        f"- всего жалоб на странице: {facts.get('monitor_reports')}",
DETAIL=SOURCE_LINE=0282:        f"- открытые/назначенные: {facts.get('monitor_open')}",
DETAIL=SOURCE_LINE=0283:        f"- закрытые: {facts.get('monitor_resolved')}",
DETAIL=SOURCE_LINE=0284:        f"- возраст health, сек: {facts.get('monitor_health_age_seconds')}",
DETAIL=SOURCE_LINE=0285:        f"- автопродление авторизации: {facts.get('session_keepalive')}",
DETAIL=SOURCE_LINE=0286:        f"- cookie jar: {facts.get('cookie_jar')} / {facts.get('cookie_jar_bytes')} байт",
DETAIL=SOURCE_LINE=0287:        f"- постоянная cookie действует до UTC: {facts.get('cookie_expiry_utc') or 'не определено'}",
DETAIL=SOURCE_LINE=0288:        f"- осталось дней по cookie: {facts.get('cookie_expires_in_days') or 'не определено'}",
DETAIL=SOURCE_LINE=0289:        f"- статус срока cookie: {facts.get('cookie_expiry_status') or 'не определено'}",
DETAIL=SOURCE_LINE=0290:        f"- NetBird route: {facts.get('netbird_route')}",
DETAIL=SOURCE_LINE=0291:        "",
DETAIL=SOURCE_LINE=0292:        "Бэкап:",
DETAIL=SOURCE_LINE=0293:        f"- статус: {facts.get('backup_status')}",
DETAIL=SOURCE_LINE=0294:        f"- последний RUN_ID: {facts.get('backup_run_id')}",
DETAIL=SOURCE_LINE=0295:        f"- размер архива, байт: {facts.get('backup_size_bytes')}",
DETAIL=SOURCE_LINE=0296:        f"- cookie jar в бэкапе: {facts.get('backup_cookie_jar_backed_up')}",
DETAIL=SOURCE_LINE=0297:        f"- health-файлы расширений в бэкапе: {facts.get('backup_extension_health_files_backed_up')}",
DETAIL=SOURCE_LINE=0298:        f"- название жалобы в теме письма: {facts.get('backup_title_in_email_subject')}",
DETAIL=SOURCE_LINE=0299:        f"- возраст health, сек: {facts.get('backup_health_age_seconds')}",
DETAIL=SOURCE_LINE=0300:        "",
DETAIL=SOURCE_LINE=0301:        "Restore-check:",
DETAIL=SOURCE_LINE=0302:        f"- статус: {facts.get('restore_status')}",
DETAIL=SOURCE_LINE=0303:        f"- последний RUN_ID: {facts.get('restore_run_id')}",
DETAIL=SOURCE_LINE=0304:        f"- cloud1/cloud2: {facts.get('cloud_1_status')}/{facts.get('cloud_2_status')}",
DETAIL=SOURCE_LINE=0305:        f"- sha256/tar/cookie/sqlite/python: {facts.get('sha256_ok')}/{facts.get('tar_ok')}/{facts.get('cookie_jar_ok')}/{facts.get('sqlite_ok')}/{facts.get('py_compile_ok')}",
DETAIL=SOURCE_LINE=0306:        f"- health/scripts/units расширений: {facts.get('extension_health_files_ok')}/{facts.get('extension_scripts_ok')}/{facts.get('extension_units_ok')}",
DETAIL=SOURCE_LINE=0307:        "",
DETAIL=SOURCE_LINE=0308:        "Проблемы:",
DETAIL=SOURCE_LINE=0309:    ]
DETAIL=SOURCE_LINE=0310:    if problems:
DETAIL=SOURCE_LINE=0311:        lines.extend([f"- {p}" for p in problems])
DETAIL=SOURCE_LINE=0312:    else:
DETAIL=SOURCE_LINE=0313:        lines.append("- нет")
DETAIL=SOURCE_LINE=0314:    lines.extend([
DETAIL=SOURCE_LINE=0315:        "",
DETAIL=SOURCE_LINE=0316:        "Авторизация:",
DETAIL=SOURCE_LINE=0317:        "- ручной ежемесячный перелогин обычно не нужен; монитор сам продлевает сессию постоянным cookie jar при каждом опросе;",
DETAIL=SOURCE_LINE=0318:        "- supervisor теперь отдельно следит за сроком постоянной cookie и заранее предупредит, если до истечения останется меньше 7 дней;",
DETAIL=SOURCE_LINE=0319:        "- если форум принудительно сбросит сессию, придёт отдельная тревога AUTH_REQUIRED; тогда нужно будет разово обновить вход, но это аварийный сценарий, а не ежемесячная рутина.",
DETAIL=SOURCE_LINE=0320:    ])
DETAIL=SOURCE_LINE=0321:    return "\n".join(lines)
DETAIL=EXACT_COOKIE_LOGIC_END
DETAIL=PVE01_TIMER_CORRELATION_BEGIN
DETAIL=PVE01_TIMER=Fri 2026-07-24 12:08:38 MSK         1min 48s Fri 2026-07-24 11:58:38 MSK     8min ago skladchik-reports-monitor-supervisor.timer             skladchik-reports-monitor-supervisor.service
DETAIL=PVE01_TIMER=Sat 2026-07-25 04:39:05 MSK              16h Fri 2026-07-24 04:28:08 MSK       7h ago skladchik-reports-monitor-backup.timer                 skladchik-reports-monitor-backup.service
DETAIL=PVE01_TIMER=Sat 2026-07-25 10:15:00 MSK              22h Fri 2026-07-24 10:15:04 MSK 1h 51min ago skladchik-reports-monitor-monthly-selftest-watch.timer skladchik-reports-monitor-monthly-selftest-watch.service
DETAIL=PVE01_TIMER=Sun 2026-07-26 05:15:13 MSK        1 day 17h Sun 2026-07-19 05:11:25 MSK            - skladchik-reports-monitor-backup-restore-check.timer   skladchik-reports-monitor-backup-restore-check.service
DETAIL=PVE01_TIMER_CORRELATION_END
DETAIL=EDGE_TIMER_CORRELATION_BEGIN
DETAIL=EDGE_TIMER=Fri 2026-07-24 12:06:51 MSK            1s Fri 2026-07-24 12:06:21 MSK      28s ago skladchik-reports-monitor.timer                skladchik-reports-monitor.service
DETAIL=EDGE_TIMER=Fri 2026-07-24 12:08:35 MSK      1min 45s Fri 2026-07-24 12:03:35 MSK 3min 14s ago skladchik-reports-monitor-watchdog.timer       skladchik-reports-monitor-watchdog.service
DETAIL=EDGE_TIMER=Sat 2026-07-25 09:16:28 MSK           21h Fri 2026-07-24 09:18:35 MSK 2h 48min ago skladchik-reports-monitor-heartbeat.timer      skladchik-reports-monitor-heartbeat.service
DETAIL=EDGE_TIMER=Sat 2026-07-25 09:45:00 MSK           21h Fri 2026-07-24 09:45:03 MSK 2h 21min ago skladchik-reports-monitor-age-alert.timer      skladchik-reports-monitor-age-alert.service
DETAIL=EDGE_TIMER=Mon 2026-07-27 09:35:00 MSK        2 days Mon 2026-07-20 09:35:06 MSK            - skladchik-reports-monitor-weekly-summary.timer skladchik-reports-monitor-weekly-summary.service
DETAIL=EDGE_TIMER=Sat 2026-08-01 09:25:00 MSK 1 week 0 days Fri 2026-07-10 10:28:28 MSK            - skladchik-reports-monitor-event-selftest.timer skladchik-reports-monitor-event-selftest.service
DETAIL=EDGE_TIMER_CORRELATION_END
DETAIL=SECRET_VALUES_OUTPUT=NONE
DETAIL=COOKIE_CONTENT_OUTPUT=NONE
DETAIL=COOKIE_EXACT_LOGIC_AUDIT=PASS
CHANGES_MADE=NO

OUTPUT_END
CHAT_OUTPUT_END
