✘✘ GRAYBYTE WORDPRESS FILE MANAGER ✘✘

​🇳​​🇦​​🇲​​🇪♯➤ server.blackpussy.asia ​🇻​♯➤ 5.14.0-611.20.1.el9_7.x86_64 #1 SMP 🇾​♯➤ 2026

𝗛𝗢𝗠𝗘 𝗜𝗗 ♯➤ 163.245.207.76 ♯➤ 𝗔𝗗𝗠𝗜𝗡 𝗜𝗗 216.73.216.243
𝗢𝗣𝗧𝗜𝗢𝗡𝗦 ♯ CRL ♯➤ 𝗢𝗞 ┃ WGT ♯➤ 𝗢𝗞 ┃ SDO ♯➤ 𝗢𝗞 ┃ PKEX ♯➤ 𝗢𝗙𝗙
𝗗𝗘𝗔𝗖𝗧𝗜𝗩𝗔𝗧𝗘𝗗 ♯➤ mail,mb_send_mail
𝗖𝗨𝗥𝗥𝗘𝗡𝗧 𝗙𝗜𝗟𝗘 : /tmp//wpmm-watchdog-monitoring-v3
#!/usr/bin/env python3
import argparse
import ipaddress
import json
import os
import re
import shutil
import subprocess
import time
from datetime import datetime
from pathlib import Path


STATE_DIR = Path("/var/lib/wpmm-watchdog")
STATUS_FILE = STATE_DIR / "status.json"
STATE_FILE = STATE_DIR / "state.json"
COUNTERS_FILE = STATE_DIR / "counters.json"
SECURITY_FILE = STATE_DIR / "security.json"
LOG_FILE = Path("/var/log/wpmm-watchdog.log")
CONFIG_FILE = Path("/etc/wpmm-watchdog.json")
HAWK_IP = "172.96.184.87"
BAD_CONNECTION_THRESHOLD = 800
CONNECTION_WARNING_THRESHOLD = 50
CONNECTION_CRITICAL_THRESHOLD = 200
CONCENTRATED_SOURCE_THRESHOLD = 30
DISTRIBUTED_SOURCE_THRESHOLD = 20
CONSECUTIVE_THRESHOLD = 3
GRACEFUL_COOLDOWN = 20 * 60


def run(command, timeout=60):
    proc = subprocess.run(
        command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
        universal_newlines=True, timeout=timeout,
    )
    return proc.returncode, (proc.stdout + proc.stderr).strip()


def log(event, **detail):
    record = {"time": datetime.now().astimezone().isoformat(timespec="seconds"), "event": event, **detail}
    with LOG_FILE.open("a", encoding="utf-8") as handle:
        handle.write(json.dumps(record, ensure_ascii=False) + "\n")


def load_json(path, default):
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return default


def save_json(path, data):
    temporary = path.with_suffix(path.suffix + ".tmp")
    temporary.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
    os.replace(temporary, path)


def service_status(name):
    code, output = run(f"systemctl is-active {name}", timeout=10)
    return output.splitlines()[0] if output else ("active" if code == 0 else "unknown")


def memory_metrics():
    values = {}
    for line in Path("/proc/meminfo").read_text().splitlines():
        key, raw = line.split(":", 1)
        values[key] = int(raw.strip().split()[0])
    return {
        "total_mb": values.get("MemTotal", 0) // 1024,
        "available_mb": values.get("MemAvailable", 0) // 1024,
        "swap_total_mb": values.get("SwapTotal", 0) // 1024,
        "swap_free_mb": values.get("SwapFree", 0) // 1024,
        "swap_used_mb": (values.get("SwapTotal", 0) - values.get("SwapFree", 0)) // 1024,
    }


def split_socket_endpoint(value):
    value = str(value or "")
    if value.startswith("[") and "]:" in value:
        end = value.rfind("]:")
        return value[1:end].split("%", 1)[0], value[end + 2:]
    if ":" in value:
        address, port = value.rsplit(":", 1)
        return address.split("%", 1)[0], port
    return value, ""


def class_c_network(value):
    try:
        address = ipaddress.ip_address(str(value))
        return str(ipaddress.ip_network(str(address) + "/24", strict=False)) if address.version == 4 else str(address)
    except Exception:
        return str(value or "未知")


def aggregate_class_c(source_items):
    networks = {}
    for ip, detail in source_items.items():
        network = class_c_network(ip)
        item = networks.setdefault(network, {"network": network, "count": 0, "source_count": 0, "states": {}, "ips": []})
        count = int(detail.get("count", 0) if isinstance(detail, dict) else detail)
        item["count"] += count
        item["source_count"] += 1
        item["ips"].append({"ip": ip, "count": count})
        if isinstance(detail, dict):
            for state, state_count in detail.get("states", {}).items():
                item["states"][state] = item["states"].get(state, 0) + int(state_count or 0)
    rows = sorted(networks.values(), key=lambda value: (-value["count"], value["network"]))
    for item in rows:
        item["ips"] = sorted(item["ips"], key=lambda value: (-value["count"], value["ip"]))[:12]
    return rows


def socket_metrics():
    code, output = run("ss -antH", timeout=20)
    counts = {}
    syn_sources = {}
    abnormal_sources = {}
    if code == 0:
        for line in output.splitlines():
            parts = line.split()
            state = parts[0] if parts else "UNKNOWN"
            counts[state] = counts.get(state, 0) + 1
            if state == "SYN-RECV" and len(parts) >= 5:
                peer, _ = split_socket_endpoint(parts[4])
                syn_sources[peer] = syn_sources.get(peer, 0) + 1
            if state in ("SYN-RECV", "CLOSE-WAIT", "LAST-ACK") and len(parts) >= 5:
                peer, _ = split_socket_endpoint(parts[4])
                abnormal_sources[peer] = abnormal_sources.get(peer, 0) + 1
    counts["top_syn_sources"] = [
        {"ip": ip, "count": count}
        for ip, count in sorted(syn_sources.items(), key=lambda item: (-item[1], item[0]))[:8]
    ]
    counts["abnormal_source_count"] = len(abnormal_sources)
    counts["top_abnormal_sources"] = [
        {"ip": ip, "count": count}
        for ip, count in sorted(abnormal_sources.items(), key=lambda item: (-item[1], item[0]))[:12]
    ]
    counts["top_abnormal_networks"] = aggregate_class_c(abnormal_sources)[:12]
    counts["csf_available"] = bool(shutil.which("csf"))
    counts["firewall_available"] = bool(shutil.which("csf") or (shutil.which("wpmm-firewall") and shutil.which("nft")))
    counts["firewall_backend"] = "csf" if shutil.which("csf") else ("wpmm_nft" if shutil.which("wpmm-firewall") and shutil.which("nft") else "none")
    return counts


def connection_details():
    config = load_json(CONFIG_FILE, {})
    whitelist = set(str(value).strip() for value in config.get("connection_whitelist_ips", []) if str(value).strip())
    labels = config.get("connection_whitelist_labels", {}) if isinstance(config.get("connection_whitelist_labels", {}), dict) else {}
    code, output = run("ss -antH", timeout=25)
    if code != 0:
        raise RuntimeError("無法讀取連線狀態")
    sources = {}
    ports = {}
    states = {}
    for line in output.splitlines():
        parts = line.split()
        if len(parts) < 5:
            continue
        state = parts[0]
        if state not in ("SYN-RECV", "CLOSE-WAIT", "LAST-ACK"):
            continue
        local_ip, local_port = split_socket_endpoint(parts[3])
        peer_ip, _ = split_socket_endpoint(parts[4])
        states[state] = states.get(state, 0) + 1
        ports[local_port] = ports.get(local_port, 0) + 1
        item = sources.setdefault(peer_ip, {"ip": peer_ip, "count": 0, "states": {}, "target_ports": {}, "target_addresses": {}})
        item["count"] += 1
        item["states"][state] = item["states"].get(state, 0) + 1
        item["target_ports"][local_port] = item["target_ports"].get(local_port, 0) + 1
        item["target_addresses"][local_ip] = item["target_addresses"].get(local_ip, 0) + 1
    network_rows = aggregate_class_c(sources)
    for network in network_rows:
        try:
            subnet = ipaddress.ip_network(network["network"], strict=False)
            protected = [ip for ip in whitelist if ipaddress.ip_address(ip) in subnet]
        except Exception:
            protected = []
        network["whitelisted"] = bool(protected)
        network["protected_ips"] = protected
    rows = []
    for item in sorted(sources.values(), key=lambda value: (-value["count"], value["ip"]))[:100]:
        item["whitelisted"] = item["ip"] in whitelist
        item["whitelist_label"] = labels.get(item["ip"], "")
        item["target_ports"] = [{"port": port, "count": count} for port, count in sorted(item["target_ports"].items(), key=lambda value: (-value[1], value[0]))[:8]]
        item["target_addresses"] = [{"address": address, "count": count} for address, count in sorted(item["target_addresses"].items(), key=lambda value: (-value[1], value[0]))[:8]]
        rows.append(item)
    csf_available = bool(shutil.which("csf"))
    wpmm_nft_available = bool(shutil.which("wpmm-firewall") and shutil.which("nft"))
    return {
        "checked_at": datetime.now().astimezone().isoformat(timespec="seconds"),
        "total": sum(states.values()), "states": states, "source_count": len(sources),
        "top_target_ports": [{"port": port, "count": count} for port, count in sorted(ports.items(), key=lambda value: (-value[1], value[0]))[:12]],
        "sources": rows, "networks": network_rows[:100],
        "csf_available": csf_available,
        "firewall_available": csf_available or wpmm_nft_available,
        "firewall_backend": "csf" if csf_available else ("wpmm_nft" if wpmm_nft_available else "none"),
    }


def disk_metrics():
    result = {}
    for label, mount in (("root", "/"), ("home", "/home")):
        try:
            stat = os.statvfs(mount)
            total = stat.f_blocks * stat.f_frsize
            available = stat.f_bavail * stat.f_frsize
            inode_total = stat.f_files
            inode_free = stat.f_favail
            result[label + "_total_gb"] = round(total / 1073741824.0, 2)
            result[label + "_used_percent"] = round((total - available) * 100.0 / total, 1) if total else None
            result[label + "_inode_used_percent"] = round((inode_total - inode_free) * 100.0 / inode_total, 1) if inode_total else None
        except Exception:
            result[label + "_total_gb"] = None
            result[label + "_used_percent"] = None
            result[label + "_inode_used_percent"] = None
    return result


def raw_counters():
    cpu = [int(value) for value in Path("/proc/stat").read_text().splitlines()[0].split()[1:]]
    cpu_total = sum(cpu)
    cpu_idle = (cpu[3] if len(cpu) > 3 else 0) + (cpu[4] if len(cpu) > 4 else 0)
    cpu_iowait = cpu[4] if len(cpu) > 4 else 0
    rx_bytes = tx_bytes = 0
    for line in Path("/proc/net/dev").read_text().splitlines()[2:]:
        interface, values = line.split(":", 1)
        if interface.strip() == "lo":
            continue
        fields = values.split()
        rx_bytes += int(fields[0])
        tx_bytes += int(fields[8])
    read_sectors = write_sectors = 0
    for device in Path("/sys/block").iterdir():
        if device.name.startswith(("loop", "ram", "fd")):
            continue
        try:
            fields = (device / "stat").read_text().split()
            read_sectors += int(fields[2])
            write_sectors += int(fields[6])
        except Exception:
            pass
    return {
        "time": time.time(), "cpu_total": cpu_total, "cpu_idle": cpu_idle,
        "cpu_iowait": cpu_iowait, "rx_bytes": rx_bytes, "tx_bytes": tx_bytes,
        "read_sectors": read_sectors, "write_sectors": write_sectors,
    }


def rate_metrics():
    current = raw_counters()
    previous = load_json(COUNTERS_FILE, {})
    save_json(COUNTERS_FILE, current)
    elapsed = max(0.001, current["time"] - float(previous.get("time", current["time"])))
    cpu_delta = current["cpu_total"] - int(previous.get("cpu_total", current["cpu_total"]))
    idle_delta = current["cpu_idle"] - int(previous.get("cpu_idle", current["cpu_idle"]))
    iowait_delta = current["cpu_iowait"] - int(previous.get("cpu_iowait", current["cpu_iowait"]))
    return {
        "cpu_percent": round(max(0.0, min(100.0, (cpu_delta - idle_delta) * 100.0 / cpu_delta)), 1) if cpu_delta > 0 else None,
        "io_wait_percent": round(max(0.0, iowait_delta * 100.0 / cpu_delta), 1) if cpu_delta > 0 else None,
        "network_rx_kbps": round(max(0, current["rx_bytes"] - int(previous.get("rx_bytes", current["rx_bytes"]))) / elapsed / 1024.0, 1),
        "network_tx_kbps": round(max(0, current["tx_bytes"] - int(previous.get("tx_bytes", current["tx_bytes"]))) / elapsed / 1024.0, 1),
        "disk_read_kbps": round(max(0, current["read_sectors"] - int(previous.get("read_sectors", current["read_sectors"]))) * 512.0 / elapsed / 1024.0, 1),
        "disk_write_kbps": round(max(0, current["write_sectors"] - int(previous.get("write_sectors", current["write_sectors"]))) * 512.0 / elapsed / 1024.0, 1),
    }


def top_processes(sort_key):
    command = "ps -eo pid=,user=,pcpu=,pmem=,etimes=,comm= --sort=-{} | head -10".format(sort_key)
    code, output = run(command, timeout=15)
    rows = []
    if code == 0:
        for line in output.splitlines():
            parts = line.split(None, 5)
            if len(parts) == 6:
                try:
                    rows.append({"pid": int(parts[0]), "user": parts[1], "cpu": float(parts[2]), "memory": float(parts[3]), "seconds": int(parts[4]), "process": parts[5][:80]})
                except Exception:
                    pass
    return rows


def root_security():
    _, password_output = run("chage -l root 2>/dev/null | head -1", timeout=10)
    password_changed = password_output.split(":", 1)[1].strip() if ":" in password_output else password_output
    keys = []
    key_path = Path("/root/.ssh/authorized_keys")
    if key_path.is_file():
        _, key_output = run("ssh-keygen -lf /root/.ssh/authorized_keys 2>/dev/null", timeout=10)
        keys = [line[:240] for line in key_output.splitlines() if line.strip()]
    _, login_output = run("last -n 5 -Fai root 2>/dev/null", timeout=10)
    logins = [line[:300] for line in login_output.splitlines() if line.startswith("root ")][:5]
    return {
        "password_changed": password_changed,
        "authorized_key_count": len(keys),
        "authorized_key_fingerprints": keys,
        "authorized_keys_mtime": datetime.fromtimestamp(key_path.stat().st_mtime).astimezone().isoformat(timespec="seconds") if key_path.exists() else "",
        "recent_root_logins": logins,
    }


def php_fpm_status():
    return "active" if run("pgrep -x php-fpm >/dev/null || pgrep -x lsphp >/dev/null", timeout=10)[0] == 0 else "inactive"


def quick_report():
    config = load_json(CONFIG_FILE, {})
    sockets = socket_metrics()
    whitelist_ips = set(str(value).strip() for value in config.get("connection_whitelist_ips", []) if str(value).strip())
    whitelist_ips.add(str(config.get("controller_ip", HAWK_IP)).strip())
    abnormal_total = sum(int(sockets.get(state, 0) or 0) for state in ("SYN-RECV", "CLOSE-WAIT", "LAST-ACK"))
    for source in sockets.get("top_syn_sources", []):
        source["whitelisted"] = source.get("ip") in whitelist_ips
    for source in sockets.get("top_abnormal_sources", []):
        source["whitelisted"] = source.get("ip") in whitelist_ips
    for network in sockets.get("top_abnormal_networks", []):
        try:
            subnet = ipaddress.ip_network(network.get("network", ""), strict=False)
            protected = [ip for ip in whitelist_ips if ipaddress.ip_address(ip) in subnet]
        except Exception:
            protected = []
        network["whitelisted"] = bool(protected)
        network["protected_ips"] = protected
    non_whitelist_sources = [source for source in sockets.get("top_abnormal_sources", []) if not source.get("whitelisted")]
    max_source_count = max([int(source.get("count", 0) or 0) for source in non_whitelist_sources] or [0])
    connection_pattern = "concentrated" if max_source_count >= CONCENTRATED_SOURCE_THRESHOLD else (
        "distributed" if abnormal_total > CONNECTION_WARNING_THRESHOLD and int(sockets.get("abnormal_source_count", 0) or 0) >= DISTRIBUTED_SOURCE_THRESHOLD else "none"
    )
    memory = memory_metrics()
    load1, load5, load15 = os.getloadavg()
    ssh_port = int(config.get("ssh_port", 22))
    _, listen_output = run(f"ss -lntH 'sport = :{ssh_port}'", timeout=10)
    rates = rate_metrics()
    services = {name: service_status(name) for name in ("sshd", "httpd", "mariadb", "cpanel")}
    services["php_fpm"] = php_fpm_status()
    _, whm_code = run("curl -k -sS -o /dev/null -w '%{http_code}' --max-time 8 https://127.0.0.1:2087/", timeout=12)
    report = {
        "checked_at": datetime.now().astimezone().isoformat(timespec="seconds"),
        "hostname": os.uname().nodename,
        "ssh_port": ssh_port,
        "ssh_listening": bool(listen_output.strip()),
        "services": services,
        "whm_http_status": int(whm_code) if whm_code.isdigit() else 0,
        "csf_available": bool(shutil.which("csf")),
        "cpu": {"cores": os.cpu_count() or 1, "usage_percent": rates["cpu_percent"], "io_wait_percent": rates["io_wait_percent"]},
        "load": {"1m": round(load1, 2), "5m": round(load5, 2), "15m": round(load15, 2)},
        "memory": memory,
        "sockets": sockets,
        "connection_analysis": {
            "abnormal_total": abnormal_total,
            "warning_threshold": CONNECTION_WARNING_THRESHOLD,
            "critical_threshold": CONNECTION_CRITICAL_THRESHOLD,
            "concentrated_source_threshold": CONCENTRATED_SOURCE_THRESHOLD,
            "distributed_source_threshold": DISTRIBUTED_SOURCE_THRESHOLD,
            "pattern": connection_pattern,
            "max_non_whitelist_source_count": max_source_count,
            "whitelist_ips": sorted(whitelist_ips),
        },
        "disk": dict(disk_metrics(), read_kbps=rates["disk_read_kbps"], write_kbps=rates["disk_write_kbps"]),
        "network": {"rx_kbps": rates["network_rx_kbps"], "tx_kbps": rates["network_tx_kbps"]},
        "top_cpu": top_processes("pcpu"),
        "top_memory": top_processes("pmem"),
        "root_security": root_security(),
        "wordpress_security": load_json(SECURITY_FILE, {"checked_at": "", "summary": {}}),
        "php_processes": int(run("pgrep -af 'php|lsphp' | wc -l", timeout=10)[1] or 0),
        "zombies": int(run("ps -eo stat= | grep -c '^Z' || true", timeout=10)[1] or 0),
    }
    warnings = []
    if report["load"]["1m"] > max(8, (os.cpu_count() or 1) * 2): warnings.append("load_high")
    if rates["cpu_percent"] is not None and rates["cpu_percent"] >= 90: warnings.append("cpu_high")
    if rates["io_wait_percent"] is not None and rates["io_wait_percent"] >= 30: warnings.append("io_wait_high")
    if memory["available_mb"] < 1024: warnings.append("memory_low")
    if memory["swap_total_mb"] and memory["swap_used_mb"] / memory["swap_total_mb"] >= 0.85: warnings.append("swap_high")
    if abnormal_total > CONNECTION_CRITICAL_THRESHOLD: warnings.append("abnormal_connections_critical")
    if connection_pattern == "concentrated": warnings.append("concentrated_attack_suspected")
    elif connection_pattern == "distributed": warnings.append("distributed_scan_suspected")
    if sockets.get("SYN-RECV", 0) >= CONNECTION_CRITICAL_THRESHOLD: warnings.append("syn_flood_suspected")
    if sockets.get("LAST-ACK", 0) + sockets.get("CLOSE-WAIT", 0) >= BAD_CONNECTION_THRESHOLD: warnings.append("http_connections_stuck")
    if not report["ssh_listening"]: warnings.append("ssh_not_listening")
    if any(value != "active" for value in report["services"].values()): warnings.append("service_inactive")
    if report["whm_http_status"] not in (200, 301, 302): warnings.append("whm_unhealthy")
    if report["disk"]["root_used_percent"] is not None and report["disk"]["root_used_percent"] >= 90: warnings.append("disk_high")
    report["warnings"] = warnings
    report["severity"] = "critical" if any(x in warnings for x in ("memory_low", "ssh_not_listening", "service_inactive", "abnormal_connections_critical")) else ("warning" if warnings else "healthy")
    return report


def discover_sites():
    result = {}
    path = Path("/etc/userdatadomains")
    if not path.exists():
        return result
    priority = {"main": 3, "addon": 2, "sub": 1}
    for raw in path.read_text(errors="replace").splitlines():
        parts = raw.split("==")
        if len(parts) < 5 or ":" not in parts[0]: continue
        domain = parts[0].split(":", 1)[0].strip()
        kind, root = parts[2].strip(), parts[4].strip()
        if root and (root not in result or priority.get(kind, 0) > priority.get(result[root][1], 0)):
            result[root] = (domain, kind)
    return result


def deep_scan():
    known = {"wp-log1n.php", "buy.php", "click.php", "1337.txt", "defaults.php", "networks.php"}
    summary = {"sites": 0, "known_files": 0, "random_dirs": 0, "uploads_php": 0, "config_missing": 0, "core_missing": 0}
    samples = []
    for root, (domain, _) in discover_sites().items():
        summary["sites"] += 1
        path = Path(root)
        finding = {"domain": domain, "known_files": [], "random_dirs": [], "uploads_php": [], "missing": []}
        if not (path / "wp-config.php").is_file(): finding["missing"].append("wp-config.php"); summary["config_missing"] += 1
        for name in ("wp-admin", "wp-includes", "wp-content"):
            if not (path / name).is_dir(): finding["missing"].append(name)
        if any(name in finding["missing"] for name in ("wp-admin", "wp-includes", "wp-content")): summary["core_missing"] += 1
        if path.is_dir():
            try:
                for child in path.iterdir():
                    if child.is_file() and child.name.lower() in known: finding["known_files"].append(str(child))
                    if child.is_dir() and re.fullmatch(r"[0-9a-fA-F]{5,6}", child.name): finding["random_dirs"].append(str(child))
            except Exception: pass
        uploads = path / "wp-content" / "uploads"
        sucuri_installed = (path / "wp-content" / "plugins" / "sucuri-scanner").is_dir()
        if uploads.is_dir():
            for current, _, files in os.walk(uploads):
                for name in files:
                    lower = name.lower()
                    if ".php" in lower and lower not in {"index.php", "locoy.php"}:
                        candidate = Path(current) / name
                        try:
                            relative = candidate.relative_to(uploads)
                            if sucuri_installed and relative.parts and relative.parts[0].lower() == "sucuri" and "exit(0);" in candidate.read_text(errors="replace")[:300]:
                                continue
                        except Exception:
                            pass
                        finding["uploads_php"].append(str(candidate))
                        if len(finding["uploads_php"]) >= 30: break
                if len(finding["uploads_php"]) >= 30: break
        for key in ("known_files", "random_dirs", "uploads_php"):
            summary[key] += len(finding[key])
        if any(finding.values()) and (finding["known_files"] or finding["random_dirs"] or finding["uploads_php"] or finding["missing"]):
            if len(samples) < 50: samples.append(finding)
    return {"summary": summary, "samples": samples}


def ensure_hawk_allow():
    if not shutil.which("csf"):
        return "csf_not_installed"
    allow_file = Path("/etc/csf/csf.allow")
    if allow_file.exists() and HAWK_IP in allow_file.read_text(errors="replace"):
        return "already_allowed"
    code, output = run(f"csf -a {HAWK_IP} 'Allow Hawk wp-MM-System watchdog'", timeout=30)
    return "allowed" if code == 0 else "failed: " + output[-300:]


def repair(emergency=False, automatic=False):
    before = quick_report()
    actions = []
    actions.append({"hawk_allow": ensure_hawk_allow()})
    ssh_port = before["ssh_port"]
    if before["services"]["sshd"] != "active" or not before["ssh_listening"]:
        code, check = run("sshd -t", timeout=20)
        if code == 0:
            actions.append({"restart_sshd": run("systemctl restart sshd", timeout=30)[0] == 0})
        else:
            actions.append({"restart_sshd": False, "reason": check[-300:]})
    for service in ("httpd", "mariadb"):
        if before["services"][service] != "active":
            actions.append({"start_" + service: run(f"systemctl start {service}", timeout=60)[0] == 0})
    stuck = before["sockets"].get("LAST-ACK", 0) + before["sockets"].get("CLOSE-WAIT", 0)
    if stuck >= BAD_CONNECTION_THRESHOLD or emergency:
        command = "/scripts/restartsrv_httpd --graceful" if Path("/scripts/restartsrv_httpd").exists() else "apachectl graceful"
        code, output = run(command, timeout=90)
        actions.append({"apache_graceful": code == 0, "stuck_before": stuck, "detail": output[-300:]})
    if emergency:
        php_command = "/scripts/restartsrv_apache_php_fpm --graceful"
        if Path("/scripts/restartsrv_apache_php_fpm").exists():
            code, output = run(php_command, timeout=120)
            actions.append({"php_fpm_graceful": code == 0, "detail": output[-300:]})
        memory = before["memory"]
        if memory["swap_used_mb"] > 0 and memory["available_mb"] >= memory["swap_used_mb"] + 2048:
            code, output = run("swapoff -a && swapon -a", timeout=180)
            actions.append({"swap_reset": code == 0, "detail": output[-300:]})
        else:
            actions.append({"swap_reset": False, "reason": "可用記憶體不足,為安全起見跳過"})
    time.sleep(2)
    after = quick_report()
    result = {"mode": "automatic" if automatic else ("emergency" if emergency else "safe"), "before": before, "actions": actions, "after": after}
    log("repair", mode=result["mode"], actions=actions, before_severity=before["severity"], after_severity=after["severity"])
    save_json(STATUS_FILE, after)
    return result


def automatic_check():
    state = load_json(STATE_FILE, {"pressure_runs": 0, "last_graceful": 0})
    report = quick_report()
    ensure_hawk_allow()
    for service in ("sshd", "httpd", "mariadb"):
        if report["services"][service] != "active":
            run(f"systemctl start {service}", timeout=60)
            log("service_self_heal", service=service)
    if not report["ssh_listening"] and run("sshd -t", timeout=20)[0] == 0:
        run("systemctl restart sshd", timeout=30)
        log("ssh_listener_self_heal", port=report["ssh_port"])
    stuck = report["sockets"].get("LAST-ACK", 0) + report["sockets"].get("CLOSE-WAIT", 0)
    state["pressure_runs"] = state.get("pressure_runs", 0) + 1 if stuck >= BAD_CONNECTION_THRESHOLD else 0
    now = int(time.time())
    if state["pressure_runs"] >= CONSECUTIVE_THRESHOLD and now - state.get("last_graceful", 0) >= GRACEFUL_COOLDOWN:
        command = "/scripts/restartsrv_httpd --graceful" if Path("/scripts/restartsrv_httpd").exists() else "apachectl graceful"
        code, output = run(command, timeout=90)
        state["last_graceful"] = now
        state["pressure_runs"] = 0
        log("automatic_apache_graceful", success=code == 0, stuck_connections=stuck, detail=output[-300:])
    security = load_json(SECURITY_FILE, {})
    if time.time() - float(security.get("checked_epoch", 0) or 0) >= 86400:
        scan = deep_scan()
        security = {"checked_at": datetime.now().astimezone().isoformat(timespec="seconds"), "checked_epoch": time.time(), "summary": scan.get("summary", {})}
        save_json(SECURITY_FILE, security)
        report["wordpress_security"] = security
    save_json(STATE_FILE, state)
    save_json(STATUS_FILE, report)
    log("check", severity=report["severity"], warnings=report["warnings"], stuck_connections=stuck)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--report", action="store_true")
    parser.add_argument("--deep", action="store_true")
    parser.add_argument("--repair", action="store_true")
    parser.add_argument("--emergency", action="store_true")
    parser.add_argument("--connections", action="store_true")
    args = parser.parse_args()
    STATE_DIR.mkdir(mode=0o700, parents=True, exist_ok=True)
    if args.connections:
        print(json.dumps(connection_details(), ensure_ascii=False, indent=2))
    elif args.repair or args.emergency:
        print(json.dumps(repair(emergency=args.emergency), ensure_ascii=False, indent=2))
    elif args.report:
        result = quick_report()
        if args.deep:
            result["wordpress_scan"] = deep_scan()
            security = {"checked_at": datetime.now().astimezone().isoformat(timespec="seconds"), "checked_epoch": time.time(), "summary": result["wordpress_scan"].get("summary", {})}
            save_json(SECURITY_FILE, security)
            result["wordpress_security"] = security
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        automatic_check()


if __name__ == "__main__":
    main()

Current_dir [ 𝗪𝗥𝗜𝗧𝗘𝗔𝗕𝗟𝗘 ] Document_root [ 𝗪𝗥𝗜𝗧𝗘𝗔𝗕𝗟𝗘 ]

Current_dir [ 𝗪𝗥𝗜𝗧𝗘𝗔𝗕𝗟𝗘 ] Document_root [ 𝗪𝗥𝗜𝗧𝗘𝗔𝗕𝗟𝗘 ]


[ Back ]
𝗡𝗔𝗠𝗘
𝗦𝗜𝗭𝗘
𝗟𝗔𝗦𝗧 𝗧𝗢𝗨𝗖𝗛
𝗨𝗦𝗘𝗥
𝗦𝗧𝗔𝗧𝗨𝗦
𝗙𝗨𝗡𝗖𝗧𝗜𝗢𝗡𝗦
..
--
12 Sep 2026 9.10 AM
root / root
0555
lost+found
--
17 Jan 2026 3.15 PM
root / root
0700
lshttpd
--
12 Sep 2026 4.57 AM
nobody / nobody
0751
systemd-private-ca77e09721aa4fcf9ffff7e345728903-chronyd.service-iRpFK7
--
20 Aug 2026 6.54 PM
root / root
0700
systemd-private-ca77e09721aa4fcf9ffff7e345728903-dbus-broker.service-mWwJQf
--
20 Aug 2026 6.54 PM
root / root
0700
systemd-private-ca77e09721aa4fcf9ffff7e345728903-ea-php82-php-fpm.service-q7F1D3
--
11 Sep 2026 3.28 AM
root / root
0700
systemd-private-ca77e09721aa4fcf9ffff7e345728903-ea-php82-php-fpm.service-ybHSbN
--
11 Sep 2026 3.28 AM
root / root
0700
systemd-private-ca77e09721aa4fcf9ffff7e345728903-irqbalance.service-Ciqla8
--
10 Sep 2026 4.57 AM
root / root
0700
systemd-private-ca77e09721aa4fcf9ffff7e345728903-irqbalance.service-KqdlIp
--
10 Sep 2026 4.57 AM
root / root
0700
systemd-private-ca77e09721aa4fcf9ffff7e345728903-kdump.service-B6YZbO
--
20 Aug 2026 6.55 PM
root / root
0700
systemd-private-ca77e09721aa4fcf9ffff7e345728903-kdump.service-KCT1LN
--
20 Aug 2026 6.55 PM
root / root
0700
systemd-private-ca77e09721aa4fcf9ffff7e345728903-pdns.service-qNCpXY
--
26 Aug 2026 4.57 AM
root / root
0700
systemd-private-ca77e09721aa4fcf9ffff7e345728903-pdns.service-xO0yHj
--
26 Aug 2026 4.57 AM
root / root
0700
systemd-private-ca77e09721aa4fcf9ffff7e345728903-systemd-logind.service-PDQ6FF
--
20 Aug 2026 6.54 PM
root / root
0700
diag-body
1.222 KB
11 Sep 2026 3.30 AM
root / root
0644
diagnose_setup_403_target.py
1.81 KB
10 Sep 2026 4.27 AM
root / root
0644
find_publisher_ip.py
2.185 KB
9 Sep 2026 11.00 AM
root / root
0700
install_auto_bclass_block_target.sh
3.461 KB
10 Sep 2026 4.28 AM
root / root
0644
install_dedi3_rpcbind_protection.sh
1.576 KB
10 Sep 2026 1.32 AM
root / root
0644
install_port111_guard_target.sh
2.617 KB
10 Sep 2026 4.27 AM
root / root
0644
install_wp_request_htaccess_protection.py
3.226 KB
10 Sep 2026 4.55 AM
root / root
0644
other-body
0 KB
11 Sep 2026 3.17 AM
root / root
0644
possibility-after-restart
1.222 KB
11 Sep 2026 3.22 AM
root / root
0644
possibility-final-_
7.181 KB
11 Sep 2026 3.29 AM
root / root
0644
possibility-final-_index_php
1.222 KB
11 Sep 2026 3.29 AM
root / root
0644
possibility-final-_wp-login_php
1.222 KB
11 Sep 2026 3.29 AM
root / root
0644
possibility-home-after-restart
7.181 KB
11 Sep 2026 3.22 AM
root / root
0644
possibility-local-body
5.937 KB
11 Sep 2026 3.15 AM
root / root
0644
possibility-permission-fix
7.233 KB
11 Sep 2026 3.35 AM
root / root
0644
possibility-php-body
1.222 KB
11 Sep 2026 3.16 AM
root / root
0644
possibility-title
1.222 KB
11 Sep 2026 3.32 AM
root / root
0644
possibility-verify-_
7.181 KB
11 Sep 2026 3.29 AM
root / root
0644
possibility-verify-_index_php
1.222 KB
11 Sep 2026 3.29 AM
root / root
0644
possibility-verify-_wp-login_php
1.222 KB
11 Sep 2026 3.29 AM
root / root
0644
validate_basic_capabilities_target.sh
2.356 KB
10 Sep 2026 4.54 AM
root / root
0644
verify_auto_bclass_safety.sh
1.676 KB
10 Sep 2026 5.00 AM
root / root
0644
verify_request_protection_target.py
2.292 KB
10 Sep 2026 4.56 AM
root / root
0644
verify_watchdog_extended_target.sh
1.146 KB
10 Sep 2026 4.41 AM
root / root
0644
wpmm-apache-configtest.out
0.01 KB
9 Sep 2026 11.29 PM
root / root
0644
wpmm-curl-body
10.178 KB
10 Sep 2026 4.27 AM
root / root
0644
wpmm-firewall-dedi3.candidate
7.54 KB
10 Sep 2026 1.32 AM
root / root
0644
wpmm-httpd-reload.out
30.742 KB
9 Sep 2026 11.29 PM
root / root
0644
wpmm-port111-guard.candidate
0.668 KB
10 Sep 2026 4.27 AM
root / root
0644
wpmm-port111-guard.service.candidate
0.249 KB
10 Sep 2026 4.27 AM
root / root
0644
wpmm-preflight.stderr
0 KB
10 Sep 2026 4.54 AM
root / root
0644
wpmm-preflight.stdout
44.845 KB
10 Sep 2026 4.54 AM
root / root
0644
wpmm-v3-report.json
17.37 KB
9 Sep 2026 6.09 PM
root / root
0644
wpmm-watchdog-dedi3.json
0.119 KB
9 Sep 2026 6.55 AM
root / root
0644
wpmm-watchdog-monitoring
18.875 KB
9 Sep 2026 8.35 AM
root / root
0644
wpmm-watchdog-monitoring-v2
18.954 KB
9 Sep 2026 8.55 AM
root / root
0644
wpmm-watchdog-monitoring-v3
27.3 KB
9 Sep 2026 6.09 PM
root / root
0644
wpmm-watchdog.autoblock
39.53 KB
10 Sep 2026 4.28 AM
root / root
0644
wpmm-watchdog.py36-candidate
11.718 KB
9 Sep 2026 6.55 AM
root / root
0644
wpmm-watchdog.service.candidate
0.222 KB
9 Sep 2026 6.55 AM
root / root
0644
wpmm-watchdog.sucuri-candidate
12.228 KB
9 Sep 2026 6.59 AM
root / root
0644
wpmm-watchdog.timer.autoblock
0.185 KB
10 Sep 2026 4.28 AM
root / root
0644
wpmm-watchdog.timer.candidate
0.191 KB
9 Sep 2026 6.55 AM
root / root
0644

✘✘ GRAYBYTE WORDPRESS FILE MANAGER @ 2026 CONTACT ME ✘✘
Static GIF Static GIF