You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

87 lines
3.4 KiB

"""
support_bundle.py — Assemble a diagnostics zip for the Home dashboard.
Bundles status snapshot, configs (WiFi passwords masked), the newest dpworldapp
log, and OS diagnostics text. Intended to be emailed to support.
"""
import copy
import io
import json
import os
import zipfile
import system_status
MAX_LOG_BYTES = 5 * 1024 * 1024 # cap the bundled log at 5 MB
def mask_passwords(device_config):
"""Return a deep copy of device_config with every wifi_passwd masked.
Coerces a non-dict device_config (e.g. a corrupted board_config row that
decoded to a string, int, or list) to an empty dict rather than propagating
an AttributeError from .get().
"""
cfg = copy.deepcopy(device_config if isinstance(device_config, dict) else {})
for entry in cfg.get("WIFI_SSID", []) or []:
if isinstance(entry, dict) and "wifi_passwd" in entry:
entry["wifi_passwd"] = "***"
return cfg
def build_support_bundle(db):
"""Build the diagnostics zip. Returns the zip file content as bytes."""
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
# status snapshot
try:
status = system_status.get_system_status(db)
except Exception as e: # never let one failure abort the bundle
status = {"error": str(e)}
z.writestr("status.json", json.dumps(status, indent=2, ensure_ascii=False))
# configs (passwords masked)
device = mask_passwords(db.get_config("device_config") or {})
z.writestr("config-device.json",
json.dumps(device, indent=2, ensure_ascii=False))
protocol = db.get_config("protocol_config") or {}
z.writestr("config-protocol.json",
json.dumps(protocol, indent=2, ensure_ascii=False))
# newest dpworldapp log (capped)
log_path = system_status._find_newest_dpworld_log(system_status.LOG_DIR)
if log_path:
try:
with open(log_path, "rb") as f:
z.writestr("dpworldapp-latest.log", f.read(MAX_LOG_BYTES))
except OSError:
pass
# OS diagnostics text
z.writestr("ip-addr.txt",
system_status._run(["ip", "addr"]) +
"\n--- routes ---\n" +
system_status._run(["ip", "route"]))
z.writestr("systemctl-status.txt",
system_status._run(["systemctl", "status",
system_status.DPWORLDAPP_UNIT]) +
"\n--- web-configurator ---\n" +
system_status._run(["systemctl", "status",
"web-configurator.service"]))
z.writestr("dmesg.txt", system_status._run(["dmesg"])[-200000:])
# v1.6.0 (§9): 네트워크 저널 tail — 부재 시 항목 생략 (기존 fail-soft 패턴)
try:
from network.journal import Journal
journal_path = os.path.join(os.environ.get("LOG_DIR", "/opt/log/dpworldapp"),
"network_journal.jsonl")
sections_net = Journal(journal_path).tail(200)
z.writestr("network_journal_tail.json",
json.dumps(sections_net, indent=2, ensure_ascii=False))
except Exception: # noqa: BLE001
z.writestr("network_journal_tail.json", "[]")
buf.seek(0)
return buf.read()