feat(debug-info): add storage, docker, GPU health, and auto-update diagnostics
The Debug Info bundle carried no storage-path info, which stalled diagnosis of relocation issues like #1050 (moved data not seen by the admin). Add the fields support actually asks for, all best-effort so one failure never blanks the bundle: - Storage: resolved host storage root (#938), container path, whether NOMAD_STORAGE_PATH is set, and the Kiwix library book count (0 books is the tell for an empty/wrong-path library). - Docker Engine version (reporters currently paste it by hand; needed for container/updater issues). - GPU passthrough health (gpuHealth.status + detected gpu.type) for the passthrough-lost-after-update class (#755/#878). - Auto-update status for core/apps/content plus any auto-disabled reason, since the auto-update trilogy shipped. Adds a public DockerService.getHostStorageRoot() wrapper over the existing #938 resolver. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6a4f02dd46
commit
9b7fe521d1
|
|
@ -512,6 +512,15 @@ export class DockerService {
|
|||
* Falls back to NOMAD_STORAGE_PATH / the production default if the admin
|
||||
* container or its storage mount can't be inspected.
|
||||
*/
|
||||
/**
|
||||
* Public accessor for the resolved host path backing `/app/storage`. Used by the
|
||||
* Debug Info bundle so support can see where content actually lives on the host
|
||||
* (the #1050 class of "moved my data, admin doesn't see it" reports).
|
||||
*/
|
||||
async getHostStorageRoot(): Promise<string> {
|
||||
return this._resolveHostStorageRoot()
|
||||
}
|
||||
|
||||
private async _resolveHostStorageRoot(): Promise<string> {
|
||||
if (this._hostStorageRoot) return this._hostStorageRoot
|
||||
const fallback = env.get('NOMAD_STORAGE_PATH', DockerService.DEFAULT_HOST_STORAGE_ROOT)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import KVStore from '#models/kv_store'
|
|||
import { KV_STORE_SCHEMA, KVStoreKey } from '../../types/kv_store.js'
|
||||
import { isNewerVersion } from '../utils/version.js'
|
||||
import { invalidateAssistantNameCache } from '../../config/inertia.js'
|
||||
import { KiwixLibraryService } from '#services/kiwix_library_service'
|
||||
|
||||
@inject()
|
||||
export class SystemService {
|
||||
|
|
@ -757,6 +758,28 @@ export class SystemService {
|
|||
this.checkLatestVersion().catch(() => null),
|
||||
])
|
||||
|
||||
// Diagnostics tied to common support cases: storage relocation (#1050),
|
||||
// container/updater issues (#858), GPU passthrough (#755/#878), and the
|
||||
// auto-update trilogy. All best-effort so a single failure never blanks the
|
||||
// whole bundle.
|
||||
const [dockerVersion, hostStorageRoot, kiwixBookCount, gpuType] = await Promise.all([
|
||||
this.dockerService.docker
|
||||
.version()
|
||||
.then((v: any) => v?.Version ?? null)
|
||||
.catch(() => null),
|
||||
this.dockerService.getHostStorageRoot().catch(() => null),
|
||||
new KiwixLibraryService().getBookCount().catch(() => null),
|
||||
KVStore.getValue('gpu.type').catch(() => null),
|
||||
])
|
||||
const [autoUpdateCore, autoUpdateApps, autoUpdateContent, autoDisabledReason] =
|
||||
await Promise.all([
|
||||
KVStore.getValue('autoUpdate.enabled').catch(() => null),
|
||||
KVStore.getValue('appAutoUpdate.enabled').catch(() => null),
|
||||
KVStore.getValue('contentAutoUpdate.enabled').catch(() => null),
|
||||
KVStore.getValue('autoUpdate.autoDisabledReason').catch(() => null),
|
||||
])
|
||||
const isEnabled = (v: any) => v === true || v === 'true'
|
||||
|
||||
const lines: string[] = [
|
||||
'Project NOMAD Debug Info',
|
||||
'========================',
|
||||
|
|
@ -765,7 +788,7 @@ export class SystemService {
|
|||
]
|
||||
|
||||
if (systemInfo) {
|
||||
const { cpu, mem, os, disk, fsSize, uptime, graphics } = systemInfo
|
||||
const { cpu, mem, os, disk, fsSize, uptime, graphics, gpuHealth } = systemInfo
|
||||
|
||||
lines.push('')
|
||||
lines.push('System:')
|
||||
|
|
@ -773,6 +796,7 @@ export class SystemService {
|
|||
if (os.hostname) lines.push(` Hostname: ${os.hostname}`)
|
||||
if (os.kernel) lines.push(` Kernel: ${os.kernel}`)
|
||||
if (os.arch) lines.push(` Architecture: ${os.arch}`)
|
||||
if (dockerVersion) lines.push(` Docker Engine: ${dockerVersion}`)
|
||||
if (uptime?.uptime) lines.push(` Uptime: ${this._formatUptime(uptime.uptime)}`)
|
||||
|
||||
lines.push('')
|
||||
|
|
@ -794,6 +818,10 @@ export class SystemService {
|
|||
} else {
|
||||
lines.push(' GPU: None detected')
|
||||
}
|
||||
if (gpuHealth?.status) {
|
||||
const vendor = gpuType || gpuHealth.gpuVendor
|
||||
lines.push(` GPU Passthrough: ${gpuHealth.status}${vendor ? ` (${vendor})` : ''}`)
|
||||
}
|
||||
|
||||
// Disk info — try disk array first, fall back to fsSize
|
||||
const diskEntries = disk.filter((d) => d.totalSize > 0)
|
||||
|
|
@ -814,6 +842,20 @@ export class SystemService {
|
|||
}
|
||||
}
|
||||
|
||||
lines.push('')
|
||||
lines.push('Storage:')
|
||||
lines.push(` Host storage root: ${hostStorageRoot ?? 'unknown'}`)
|
||||
lines.push(` Container path: ${DockerService.ADMIN_STORAGE_DEST}`)
|
||||
const storageEnv = process.env.NOMAD_STORAGE_PATH
|
||||
lines.push(
|
||||
` NOMAD_STORAGE_PATH: ${storageEnv ? storageEnv : 'not set (auto-detected from admin mount)'}`
|
||||
)
|
||||
if (kiwixBookCount !== null) {
|
||||
lines.push(
|
||||
` Kiwix library: ${kiwixBookCount === 0 ? 'empty (0 books)' : `${kiwixBookCount} book(s)`}`
|
||||
)
|
||||
}
|
||||
|
||||
const installed = services.filter((s) => s.installed)
|
||||
lines.push('')
|
||||
if (installed.length > 0) {
|
||||
|
|
@ -837,6 +879,15 @@ export class SystemService {
|
|||
lines.push(`Update Available: ${updateMsg}`)
|
||||
}
|
||||
|
||||
lines.push('')
|
||||
lines.push('Auto-Update:')
|
||||
lines.push(` Core: ${isEnabled(autoUpdateCore) ? 'Enabled' : 'Disabled'}`)
|
||||
lines.push(` Apps: ${isEnabled(autoUpdateApps) ? 'Enabled' : 'Disabled'}`)
|
||||
lines.push(` Content: ${isEnabled(autoUpdateContent) ? 'Enabled' : 'Disabled'}`)
|
||||
if (autoDisabledReason) {
|
||||
lines.push(` Auto-disabled reason: ${autoDisabledReason}`)
|
||||
}
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue