Diagnostic snapshot and support bundle
What the appliance reports about itself, key by key, which parts need the host agent, and how to send a support bundle.
- Version: 0.4
- Role: admin_user, viewer
- Type: reference
The diagnostic snapshot is one typed JSON document describing this appliance: the machine it runs on, the state of every container, the health of every service it calls, the datastores, the licence, the certificate, what the network lets out, the security posture, and a list of findings evaluated over all of it. Every runbook check of the diagnostics kind reads a key from this document.
Endpoints
| Call | What it returns |
|---|---|
GET /admin/diagnostics/snapshot | The snapshot. Served from cache while it is under 60 seconds old. |
POST /admin/diagnostics/refresh | Drops the cache, asks the host agent for a fresh pass, and collects again. |
GET /admin/diagnostics/bundle | The snapshot, the effective configuration with secrets redacted, and the host agent's last status documents. |
Both GET calls need the admin_user role. The viewer role may read them and is refused the
refresh with READ_ONLY_ROLE, like every other mutating call.
Warning
A refresh returns before the host agent's next pass completes. The agent polls every few seconds and runs a full pass every five minutes, so the snapshot you get back usually still carries the previous host numbers. Compare the collection timestamp to see the new ones land.
What the snapshot contains
Top level
| Key | Meaning |
|---|---|
version | Schema version of the snapshot document, independent of the product version. |
generatedAt | When the snapshot was collected. A cached snapshot is still valid; you decide whether it is stale. |
host | Facts about the machine, or null when the host agent has never reported. |
containers | One entry per container in the deployment, running or not. |
services | Application-level health of every service the gateway calls. |
datastores | PostgreSQL and Redis. |
appliance | Version, setup state, licence, updates, TLS, proxy, DNS and egress. |
posture | Security-relevant settings, read as the code that enforces them reads them. |
findings | Rules that fired, each naming a runbook. |
host
Null when the host agent has never run or its output cannot be read. The gateway container cannot see the machine by itself, so this section is a file the agent writes and the gateway reads.
| Key | Meaning |
|---|---|
hypervisor | Normalised platform: vmware, proxmox, kvm, hyperv, none or unknown. none means bare metal was positively detected; unknown means detection failed and no conclusion may be drawn. |
hypervisorRaw | The raw detector and hardware strings, kept so a mis-normalisation is diagnosable. |
cpuCount, cpuModel | Logical CPUs and the model string. |
memTotalMb | Total RAM. Compared against the licensed package's minimum by the sizing rule. |
memFreeMb | Available RAM — the number that predicts the next out-of-memory kill, not free RAM. |
swapTotalMb | Total swap. Zero is normal and is not a finding on its own. |
disks | One entry per filesystem that matters, each with its mount point, total and free space. |
kernel, dockerVersion | Kernel release and Docker Engine version. |
timeSynced | Whether the clock is disciplined. False is a finding: licence verification, certificate validation and the update channel all fail confusingly on a skewed clock. |
gpu | Whether an adapter is present and its name. A GPU is recommended for document and image work and never required. |
Note
A default Proxmox guest is indistinguishable from any other QEMU guest, so it is reported as
kvm unless an operator has filled in the virtual machine's own hardware identity strings.
hypervisorRaw always carries what was actually read.
containers
One entry per container, whether running or not. A service that the release declares and that is absent here is itself the finding.
| Key | Meaning |
|---|---|
name, service | The container name, and the service name runbooks and findings use. |
state | running, restarting, exited, created, paused, dead, removing or unknown. |
restarts | Restart count since creation. A sustained climb is a crash loop, not recovery from one bad start. |
oomKilled | Whether the last termination was an out-of-memory kill. The single most useful bit on an undersized machine. |
memUsageMb, memLimitMb | Current usage, and the declared limit or null where the service declares none. |
imageDigest | The resolved image digest, compared against the release manifest to detect a half-applied update. Null for a locally built image. |
health | The container healthcheck verdict. none means the service declares no healthcheck, which is not a fault. |
services
Keyed by service name. This is each service's own health endpoint, not the container healthcheck — a container can be running while its model failed to load.
| Key | Meaning |
|---|---|
reachable | The health endpoint answered at all. False is a network or container problem, not a model problem. |
healthy | The service reported itself healthy. Reachable and unhealthy is the interesting case, and usually means a model that did not load. |
latencyMs | Round-trip time of the health call, including the timeout value when it timed out. Every row is measured with the same three-second cap, so the numbers are comparable. |
version | The version the service reports, where it reports one. |
error | Failure detail: the connection error, the HTTP status, or the body's own message. |
A service that is not deployed is absent from this map rather than reported unhealthy — the opt-in services are simply not there when they are not configured. See the health matrix for what each one does and what breaks while it is down.
datastores
| Key | Meaning |
|---|---|
postgres.reachable | Whether the gateway can reach the database. |
postgres.migrationsPending | Migration names present in the compiled chain and absent from the database. Empty means the schema is current; non-empty on a booted appliance means the boot-time run failed. |
postgres.dbSizeMb | On-disk size of the CID database. |
redis.reachable | Whether the gateway can reach Redis. |
redis.usedMemoryMb | Memory Redis reports in use. |
appliance
| Key | Meaning |
|---|---|
version | The installed product version. A placeholder value means the box cannot say what it runs. |
channel | The update channel it is pinned to. |
setupComplete | False while the first-boot wizard is unfinished; the setup guard then answers 423 on the locked routes. |
deploymentMode | appliance, docker, development or unknown. Several findings apply only to an appliance. |
license.state | The verification result for the installed licence. |
license.tier, license.expiresAt | The licensed tier, and the expiry or null for a perpetual licence. |
license.installationId | The id the licence is bound to. Null is itself a known field issue — no licence can be issued for a box that reports no id. |
license.trustAnchorPresent | Whether the licence-signing public key exists on the host. Without it no licence can verify and every upload fails. |
license.requireLicense | The effective value of CID_REQUIRE_LICENSE. True with an unverifiable licence is the 402 failure. |
update.lastCheck | When the channel was last polled successfully, or null for never. |
update.pendingIntent | The update or host-repair intent outstanding. One stuck in the queued state means the host updater is not picking work up. |
update.channelReachable | Whether the channel answered on the last attempt. Always false, and expected, on an air-gapped appliance. |
tls.mode | Which certificate is served: one from the appliance CA, an uploaded one, a bootstrap self-signed one, or none. |
tls.expiresAt, tls.daysLeft | Expiry, and whole days remaining — negative when already expired. |
tls.servedBy | What actually holds 443 right now. Nothing listening is the other half of the certificate runbook. |
tls.caPresent | Whether the appliance root CA exists, so clients can be made to trust the appliance. |
proxy.configured, proxy.url | Whether an outbound proxy is configured, and its URL with credentials removed. |
dns | One entry per hostname the appliance must resolve, with the result. |
egress | Outbound probes, each with its target, verdict and detail. |
Danger
The three egress probes are not interchangeable. Name resolution, a TCP connection on 443, and an authenticated HTTPS request through the configured proxy fail independently, and treating one as evidence of another is what makes "the network is open" wrong in good faith. Read each target's own detail line.
These probes are the only outbound requests the appliance makes on its own; it otherwise never phones home. They are bounded and can be switched off entirely, in which case the DNS and egress arrays come back empty and no egress finding fires:
posture
Facts read from the effective configuration. The judgement lives in the findings, not here.
| Key | Meaning |
|---|---|
dbSynchronize | The effective DB_SYNCHRONIZE. True outside development deletes the migrations' own indexes on every boot. |
jwtSecretPlaceholder | True when JWT_SECRET is a known placeholder or unset. Startup refuses known placeholders, so true here usually means unset — and sessions that die at every restart. |
corsOriginsSet | Whether CORS_ALLOWED_ORIGINS is set. False is correct for a single-origin appliance and broken when the dashboard or the extension lives elsewhere. |
inspectionSourceIpMode | How the ICAP listener admits callers. An explicit allowlist replaces the private-range default rather than extending it. |
resumableUploadPolicy | The effective CID_RESUMABLE_UPLOAD_POLICY. Chunked uploads cannot be inspected; block refuses them and is the safe default. |
seedSampleAccounts | True when the seeded sample tenants and their published passwords still exist. On a customer appliance this is a finding. |
findings
Each entry carries an id, a severity, one sentence in the operator's language, the id of the runbook that resolves it, and the snapshot values the rule fired on.
| Finding | Severity | Fires when | Runbook |
|---|---|---|---|
license-trust-anchor-missing | critical or warning | No licence-signing public key on the host. Critical when licence enforcement is on. | license-trust-anchor-missing |
license-expired-or-402 | critical or warning | The licence is expired or invalid, or is inside its grace or expiry window. | license-expired-or-402 |
setup-state-dir-not-writable | critical | The gateway cannot write the state directory setup, licensing and updates all persist into. | setup-state-dir-not-writable |
providers-catalog-empty | critical | No provider rows exist, so chat cannot resolve a model. | providers-catalog-empty |
update-version-unknown | warning | The appliance cannot say which release it runs. | update-version-unknown |
update-channel-unreachable | warning | The channel host did not answer. Expected on an air-gapped appliance. | update-channel-unreachable |
tls-cert-expiring | warning or critical | The certificate expires within the 30-day renewal window appliance/tls/ensure-tls.sh renews at, or has expired. | tls-cert-expiring |
tls-443-not-served | critical | Nothing holds port 443. | tls-443-not-served |
service-unhealthy-<service> | critical or warning | One service is unreachable, or answers and reports itself unhealthy. One finding per service. | service-unhealthy |
container-restarting | critical | A container has restarted more than three times. | container-restarting |
container-oom | critical | A container's last termination was an out-of-memory kill. | container-oom |
ram-below-package-minimum | critical | Total RAM is below the licensed package's minimum, with 5% slack. | ram-below-package-minimum |
disk-low | warning or critical | A filesystem of at least 2 GB is under 10% free, or under 3%. | disk-low |
time-not-synced | warning | The host clock is not disciplined. | time-not-synced |
db-synchronize-on | critical | Schema auto-synchronise is enabled. | db-synchronize-on |
jwt-placeholder | critical | The token signing secret is a known placeholder. | jwt-placeholder |
egress-blocked | warning | At least one outbound probe failed. | egress-blocked |
migrations-pending | critical | Compiled migrations are not recorded in the database. | migrations-pending |
datastore-unreachable | critical or warning | PostgreSQL is unreachable, or Redis is. | datastore-unreachable |
sample-accounts-seeded | info | The seeded sample tenants and their published passwords still exist. | sample-accounts-seeded |
A rule fires on a value and never on the absence of one, so a deployment that cannot measure something reports nothing about it rather than a critical it cannot act on.
What needs the host agent, and what does not
| Section | Compose deployment | Appliance |
|---|---|---|
version, generatedAt, posture, datastores, services | Always | Always |
containers | Yes, when the host agent container is running | Yes |
host | Null, with the reason stated. The host probe is off by default outside an appliance | Yes |
appliance.version, channel, update | Partial — the version falls back to the build-time value | Yes |
appliance.license | The trust-anchor bit is always answerable; the state reads as disabled without an anchor | Yes |
appliance.tls | Unavailable, and nothing is reported as serving 443 | Yes |
appliance.dns, egress | Yes, unless the probes are switched off | Yes |
The host agent measures the machine and writes two files under the shared state directory; the gateway reads them. A file is a one-way channel: the gateway learns a host fact without gaining the ability to run a host command, which is why the Docker socket is not mounted into the internet-facing API.
The agent runs a full pass every five minutes, and immediately when a refresh is requested. Past thirty minutes — six missed passes — both readers drop the data and report how old it was.
The support bundle
The bundle is the snapshot plus the effective configuration and the host agent's own status documents. Redaction works on the key, not the value: a secret becomes a redaction marker, an unset secret is marked as empty because "unset" is itself a diagnosis, and a key that matches neither the secret list nor the allowlist is marked unclassified rather than emitted — a token added next year is safe the day it appears.
To send one to support:
- Reproduce the problem, then call
GET /admin/diagnostics/bundleand save the response. - Note the installation id and the appliance version from Settings → License and Settings → System Updates.
- Say what you changed between the working state and now. The bundle shows the current state; it cannot show the previous one.
Warning
Collect the bundle before you start changing things. A bundle taken after three attempted fixes describes a state nobody has diagnosed, including you.
Related
- Health matrix — what each service does and what breaks while it is down.
- Guided runbooks — the runbook each finding names.
Last updated on