Problem reports¶
Written 2026-10-17, when the plugin gained a problem reporter, and the day the XGS 3300 restarted twice with nothing in its logs to say why.
An appliance in somebody else's rack fails where nobody from the project can see it. The problem reporter writes down what went wrong, takes out of it everything that identifies the machine or its networks, and files it as a GitHub issue in the repository of the part it belongs to, so that it can be fixed. It works like OPNsense's own crash reporter (System: Firmware: Reporter), and beside it: that one sends OPNsense's crashes to OPNsense, this one sends the coprocessor's and the appliance plugins' problems to this project.
| the page | XGS: Problem Reports (/ui/xgs/reports) |
| the script | src/opnsense/scripts/xgs/reporter.py |
| when it looks | at boot (rc.syshook.d/start/65-xgs-report) and every five minutes (xgs_cron()) |
| where it keeps its queue | /var/db/os-xgs-npu/reports |
| where reports go | the project's relay, https://reports.npuforge.org/v1/report (relay/README.md) |
| the tests | tools/test-reporter.py, node relay/test.mjs - both run by the checks |
The owner decides¶
Three modes, chosen on the page:
- Automatic, the default: each report is sent as it is found - a new problem at once, the same problem again at most once a day.
- Manual: each report waits in the list until its Send is pressed. A waiting report is a notice in OPNsense's status, in the header.
- Off: nothing is looked at and nothing is sent.
Automatic is the default by the project's decision, and it is never left for somebody to discover:
until the owner has applied a choice on the page or closed it, a banner on every page of the GUI a
notice in OPNsense's status, in the header, says that reports are sent and where to change it. The
notice opens the page, and its X closes it for good; a banner could do neither (2026-10-19). View on any report shows exactly what is sent, and Report
a problem writes one by hand, which goes the same way.
Nobody needs a GitHub account. The relay files the issue as the project's GitHub App; an optional contact address goes to the relay with the report and is kept beside the issue's number, never written into the issue.
What is looked at¶
Only the parts that are installed, each one's reports going to its own repository:
| part | its programs in the logs | its paths |
|---|---|---|
| os-xgs-npu | octep, octep-follow, octep-stop, npuep, npuctl, xgs-assign, xgs-switch, and the kernel lines of octep*, npuep*, oxp*, npup*, mvmgmt* |
OPNsense/XGS, scripts/xgs, scripts/octep, scripts/npuctl |
| os-fanctl | fanctl |
OPNsense/FanCtl, scripts/fanctl |
| os-frontpanel | frontpanel, LCDd |
OPNsense/FrontPanel, scripts/frontpanel |
| os-linkhealth | linkhealth |
OPNsense/LinkHealth, scripts/linkhealth |
| os-netreport | netreport |
OPNsense/NetReport, scripts/netreport |
| kind | found in | is a problem when |
|---|---|---|
| logged error | the system log, /var/log/xgs, configd's log |
a line of severity error or worse from one of the parts' programs, or a configd action of one of them that failed; lines one program logs within three seconds of each other are one report, its first line the title and every line its evidence - since 2026-10-19, when the kernel follower's four-line account of one failed build became four issues (#509) |
| driver | the same logs, kernel lines | a line of one of the coprocessor's devices that says something failed, timed out, was refused or did not complete - device_printf carries no severity, so the words are what there is |
| page error | PHP's error log | an error in one of the parts' pages or scripts |
| driver missing | board.sh, sysctl |
the board has a coprocessor and no driver is attached ten minutes after boot |
| driver not ready | dev.octep.0.ready |
attached, and the handshake not done ten minutes after boot |
| module and kernel | /boot/modules/*.ko.kernel, kern.version |
the driver module was built for another kernel than the running one: the kernel follower did not rebuild it |
| update | /var/db/os-xgs-npu/update/apply.json |
an update from XGS: Updates failed |
| fan incident | /var/db/fanctl/incident.json |
fan control recorded an incident |
| kernel panic | /var/crash/info.N, textdump.tar.N |
a crash dump was saved; the panic string, the backtrace and the end of the message buffer go into the report. Two crashes are the same problem when the panic and the first frames after the debugger's, the panic's, the trap's and the lock code's are the same - info.last, savecore's link to the newest dump, is not a dump of its own. Nothing in /var/crash is removed: it is OPNsense's crash reporter's |
| restart without shutdown | at boot | the boot before this one ended without an orderly shutdown - see below |
The logs are read from where the last look stopped; the first look after installing reads none of what was there before.
A restart without a shutdown¶
rc.reboot, rc.halt and rc.shutdown all run rc.syshook stop, and stop/90-xgs-report writes the
time down there. At boot, a boot before this one that left no such line, and no shutdown record in
the login accounting (reboot(8) at a shell bypasses the hook but not that), did not shut down. The
report says what is known about why:
- a crash dump on the dump device (
savecore -C): the kernel panicked, and the dump is reported on its own once savecore has saved it; - no dump, and the message buffer kept the boot before: a reset without a loss of power - a watchdog, a reset line or a button;
- no dump, the message buffer cleared, and a dump device configured: memory was cleared - a loss of power or a reset by the hardware; a panic would have written a dump;
and the last twenty lines the system log had before the boot.
What the host's chipset recorded goes in too, since 2026-10-18. The AMD FCH beside the host's
processor keeps the cause of the last reset in FCH::PM::S5_RESET_STATUS - power-management register
0xC0, on family 17h and later - one bit each for the watchdog, a sync flood after an uncorrected
error, a thermal trip, the reset pin, the power button, a software reset through port 0xCF9 and
others. The start hook 06-xgs-reset-reason reads it early in every boot
(scripts/xgs/reset_reason.py), logs it under xgs-reset-reason, keeps it in
/var/db/os-xgs-npu/reset-reason.json and writes it back, which clears it for the next boot. Its
named bits go into the report's fingerprint, so a watchdog reset and a sync flood are two problems,
not one. The support bundle carries it as reset-reason.json.
Why it was needed: the XGS 3300 reset itself on 2026-10-17 under a minute after its last log line,
with the watchdog armed at 128 s, no dump and no machine-check record, and nothing on the appliance
could say what had reset it (#454). Read by hand the next day it held 0x00080800: bit 19, software
wrote 0x6 to port 0xCF9 - an ordinary reboot had ended that way - and bit 11, which has no published
meaning. Nothing of the reset before; several boots had passed. Written back, bit 19 cleared and bit
11 stayed: bit 11 is a state of this chipset, not a cause, and is left out of the fingerprint. On the
XGS 136 it has not been read yet. Through the hook itself, at the boots after the update to
v0.1.0-alpha.1.1 on 2026-10-18 (an ordinary reboot each), the first XGS 3300 and the XGS 136 both kept
0x00080800, and both had 0x00000800 left after clearing: the 136's host has the register, and
bit 11 stays there too.
This is what was established by hand on 2026-10-17 on the XGS 3300, which restarted twice within ten
minutes: no reboot was requested (the audit log has three that day, none then), there was no shutdown
record, savecore -C /dev/ada0p3 found no dump on a configured dump device, and the message buffer
held one boot. The machine lost power or was reset by its hardware; it did not panic.
What leaves the machine¶
The text View shows, and nothing else: built on the appliance, cleaned there, posted as it is.
Every report carries the appliance block: the model (smbios.system.*), the board's assembly number,
vendor and family, the board revision on a line of its own (with SMBIOS's value beside it when the
two disagree), the BIOS, the processor and memory, the host's network controllers and the coprocessor
as PCI names them (device, ids, revision), the coprocessor's device and switch chip and the driver's
description, the expansion module in each bay with its revision and vendor, OPNsense's version, the
kernel and its build, whether the driver module was built for that kernel, the installed commit of this
plugin and of each appliance plugin, the plugin's files changed by hand since it was installed (by name,
against the digests install.sh writes to share/os-xgs-npu/manifest.sha256), and the uptime. The
appliance itself is named by twelve hex digits of a hash of its board serial and UUID, which cannot be
turned back into either.
The board's revision, its vendor and the management port's chip are there because they are what told
two XGS 3300 apart when it mattered: the first is board revision 17 from vendor NEA with an I210 on its
management port, the second revision 09 from LAA with an I211, and os-linkhealth, which knew the first
by its revision, drew no faceplate for the second until it matched the model (smbios.system.version)
as well. A report from a third board says at once which of these it shares. The vendor is the first
three letters of the board's serial, or the coprocessor's own record of it; the rest of the serial is
never read into a report.
Taken out of every report before it is even stored: IPv4 and IPv6 addresses (loopback, unspecified and link-local ones say nothing about anybody and stay), MAC addresses, e-mail addresses, the SMBIOS serials and UUID, the coprocessor's serials, the host name and the domain, key blocks, key fingerprints, and any long run of hex digits.
At most five new reports and twenty repeats a day leave one appliance, and the relay holds each appliance to twenty and itself to five hundred; the owner's own Send is never refused.
For a test¶
One line in /usr/local/etc/os-xgs-npu/report-relay sends the reports to another relay, https://
only. reporter.py collect, list, show <id>, send <id> and system work at a shell as on the
page.
Tested¶
On 2026-10-17, on the XGS 3300:
reporter.pyrun from/tmpagainst the day's whole logs, sending nothing (XGS_REPORT_NO_SEND), with the boot before the last one given as the one to judge: it found the 08:52 restart as a restart without a shutdown, "lost power or was reset by its hardware", the appliance block complete (model, board AMDA0202-0004 rev 09, CN8365 and 88E6193X, bay A: AMDA0207-0001 rev 02, the module built for the running kernel, every plugin's commit) and every address and key fingerprint taken out. It also took octep's "NPU not ready yet", which it prints while it waits at every boot, for a failure - twelve times in eight days of logs - so "not ready" is no longer one of the words; after that, the day's logs gave nothing.- Installed by hand (the reporter's files only; the driver module untouched): the banner on every
page, the page and its menu entry, the form saving the reporting section into config.xml, Apply
taking the banner away, a report written by hand, its View with the address in it replaced, its
Send failing plainly ("the relay could not be reached") while the relay does not exist yet, its
Dismiss, the cron entry in the crontab, and
configctl xgs report collect.
Not yet: the relay itself (it waits for the GitHub App and the Worker, relay/README.md), a boot with the start and stop hooks, and the ARMADA family.
The support bundle¶
Since 2026-10-17 the Problem Reports page has Download a support bundle: one .tar.gz with what
somebody helping with the appliance needs, made on the spot by scripts/xgs/bundle.py and handed
straight to the browser - kept nowhere on the appliance and sent nowhere.
| file | what it holds |
|---|---|
versions.txt |
OPNsense's version, the kernel, the plugin's installed record |
system.json |
the same machine description every report carries (names and versions, no serials) |
status.json, ports.json, transceivers.json, ipsec.json, updates.json, reports.json |
what the coprocessor's pages read, as they read it |
watchdog.txt, sysctl-driver.txt, kldstat.txt |
the hardware watchdog, the driver's sysctl tree and tunables, the loaded modules |
dmesg.txt, ifconfig.txt, log-plugin-lines.txt |
the boot messages, the interfaces, and the plugins' own lines from the system log |
Every file goes through the same cleaner as a report before it is written: addresses, MACs, serial
numbers, the host and domain names, e-mail addresses, keys and fingerprints are replaced. The modules'
own serial numbers are taken out of transceivers.json first, because they look like nothing the
cleaner knows. Nothing is read from /conf but the two names the cleaner removes.
Made on the XGS 3300 on 2026-10-17: 14 files, 45 KB; searched afterwards for the lab's addresses, any MAC address and the modules' serial numbers - none found.
The discussion of this page is held in the project's GitHub Discussions. Showing it here loads giscus from giscus.app and GitHub, and writing in it needs a GitHub account - what that means.