| 88 | } |
| 89 | |
| 90 | QString SupportBundle::createBundle(const RadioInfo& radio) |
| 91 | { |
| 92 | auto& logMgr = LogManager::instance(); |
| 93 | logMgr.flushLog(); |
| 94 | |
| 95 | // Create temp directory for bundle contents |
| 96 | QTemporaryDir tmpDir; |
| 97 | if (!tmpDir.isValid()) return {}; |
| 98 | tmpDir.setAutoRemove(false); |
| 99 | const QString tmp = tmpDir.path(); |
| 100 | |
| 101 | // 1. Copy log files — grab the 3 most recent timestamped logs. |
| 102 | // On Windows, aethersdr.log can be a .lnk shortcut (binary garbage). |
| 103 | // Bypasses symlink issues entirely by scanning for actual log files. |
| 104 | { |
| 105 | QDir logDir(QFileInfo(logMgr.logFilePath()).absolutePath()); |
| 106 | QStringList logs = logDir.entryList( |
| 107 | {"aethersdr-*.log", "aethersdr.log"}, QDir::Files, QDir::Time); |
| 108 | int copied = 0; |
| 109 | for (const auto& name : logs) { |
| 110 | if (copied >= 3) break; |
| 111 | QFileInfo fi(logDir.absoluteFilePath(name)); |
| 112 | // Skip shortcuts and tiny files |
| 113 | if (fi.isSymLink() || fi.size() < 100) continue; |
| 114 | QString dest = (copied == 0) ? "aethersdr.log" |
| 115 | : QString("aethersdr-%1.log").arg(copied); |
| 116 | QFile::copy(fi.absoluteFilePath(), tmp + "/" + dest); |
| 117 | ++copied; |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | // 2. System info JSON |
| 122 | { |
| 123 | auto sys = collectSystemInfo(); |
| 124 | QJsonObject obj; |
| 125 | obj["aetherVersion"] = sys.aetherVersion; |
| 126 | obj["qtVersion"] = sys.qtVersion; |
| 127 | obj["os"] = sys.osName; |
| 128 | obj["kernel"] = sys.kernelVersion; |
| 129 | obj["cpu"] = sys.cpuArch; |
| 130 | obj["buildDate"] = sys.buildDate; |
| 131 | QFile f(tmp + "/system-info.json"); |
| 132 | if (f.open(QIODevice::WriteOnly)) |
| 133 | f.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); |
| 134 | } |
| 135 | |
| 136 | // 3. Radio info JSON |
| 137 | { |
| 138 | QJsonObject obj; |
| 139 | obj["connected"] = radio.connected; |
| 140 | if (radio.connected) { |
| 141 | obj["model"] = radio.model; |
| 142 | // Serial and IP are PII per project policy — redact to match |
| 143 | // the form used in logs (****-****-****-XXXX, *.*.*. XXX) so |
| 144 | // support recipients can correlate but never see the cleartext |
| 145 | // values. Callsign is FCC public record; leave as-is. |
| 146 | // See GHSA-ccrg-j8cp-qhc4. |
| 147 | obj["serial"] = redactPii(radio.serial); |
nothing calls this directly
no test coverage detected