This commit is contained in:
@@ -189,7 +189,7 @@ function discordPrettyValue(key, val) {
|
||||
}
|
||||
if (typeof val === 'string') {
|
||||
const t = discordCmdRedact(val)
|
||||
if (/^[0-9a-f]{24,}$/i.test(t)) return t.slice(0, 12) + '…'
|
||||
if (/^[0-9a-f]{24,}$/i.test(t)) return '`' + t.slice(0, 16) + (t.length > 16 ? '…' : '') + '`'
|
||||
return t.length > 180 ? t.slice(0, 177) + '…' : t
|
||||
}
|
||||
if (Array.isArray(val)) {
|
||||
@@ -207,36 +207,86 @@ function discordPrettyValue(key, val) {
|
||||
}
|
||||
|
||||
function discordSkipPrettyKey(k) {
|
||||
return /TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|API_KEY/i.test(k)
|
||||
return /TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|API_KEY/i.test(k) ||
|
||||
k === 'note' ||
|
||||
k === 'schema' ||
|
||||
k === 'schemaVersion'
|
||||
}
|
||||
|
||||
function discordValueEmpty(val) {
|
||||
if (val == null) return true
|
||||
if (val === '') return true
|
||||
if (val === '—') return true
|
||||
if (Array.isArray(val) && !val.length) return true
|
||||
if (typeof val === 'object' && !Array.isArray(val) && !Object.keys(val).length) return true
|
||||
return false
|
||||
}
|
||||
|
||||
function discordHumanLabel(key) {
|
||||
const known = {
|
||||
schemaVersion: 'Schema',
|
||||
topicHex: 'Topic',
|
||||
peerCount: 'Peers',
|
||||
seedHandshakeError: 'Handshake',
|
||||
seedRole: 'Role',
|
||||
replicationQueueDepth: 'Queue depth',
|
||||
replicationQueue: 'Replication',
|
||||
stagingSlot: 'Staging',
|
||||
snapshotHints: 'Snapshot',
|
||||
peerFirewallStats: 'Firewall',
|
||||
peerFirewallAcceptedTotal: 'Accepted',
|
||||
peerFirewallRejectedTotal: 'Rejected',
|
||||
peerFirewallInboundTotal: 'Inbound',
|
||||
peerFirewallOutboundTotal: 'Outbound',
|
||||
peerFirewallE2e: 'Firewall e2e',
|
||||
manifestPathCount: 'Manifests',
|
||||
localRamBlockCount: 'RAM blocks',
|
||||
hypercoreLengthHint: 'Core length',
|
||||
queueDepthEstimate: 'Queue estimate',
|
||||
snapshotWorkflowNote: 'Note',
|
||||
activeSlot: 'Active slot',
|
||||
pendingSlot: 'Pending',
|
||||
previousSlot: 'Previous',
|
||||
canarySlot: 'Canary',
|
||||
drainDeadlineMs: 'Drain',
|
||||
atMs: 'Updated'
|
||||
}
|
||||
if (known[key]) return known[key]
|
||||
const s = String(key || '')
|
||||
.replace(/[_-]+/g, ' ')
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
if (!s) return String(key || '')
|
||||
return s.charAt(0).toUpperCase() + s.slice(1)
|
||||
}
|
||||
|
||||
function discordCollectFields(obj, prefix, out, depth) {
|
||||
if (!obj || typeof obj !== 'object' || Array.isArray(obj) || depth > 2) return
|
||||
const keys = Object.keys(obj)
|
||||
for (let i = 0; i < keys.length && out.length < 25; i++) {
|
||||
for (let i = 0; i < keys.length && out.length < 18; i++) {
|
||||
const k = keys[i]
|
||||
if (discordSkipPrettyKey(k)) continue
|
||||
if (k === 'note' || k === 'schema') continue
|
||||
const val = obj[k]
|
||||
const label = prefix ? prefix + ' · ' + k : k
|
||||
if (discordValueEmpty(val)) continue
|
||||
const label = prefix ? prefix + ' · ' + discordHumanLabel(k) : discordHumanLabel(k)
|
||||
if (val && typeof val === 'object' && !Array.isArray(val) && depth < 2) {
|
||||
const lines = []
|
||||
const sub = Object.keys(val)
|
||||
const flat = sub.every(function (sk) {
|
||||
const sv = val[sk]
|
||||
return sv == null || typeof sv !== 'object'
|
||||
})
|
||||
if (flat && sub.length && sub.length <= 5) {
|
||||
const parts = []
|
||||
for (let j = 0; j < sub.length; j++) {
|
||||
if (discordSkipPrettyKey(sub[j])) continue
|
||||
parts.push(sub[j] + ': ' + discordPrettyValue(sub[j], val[sub[j]]))
|
||||
}
|
||||
if (parts.length) out.push(discordField(label, parts.join('\n'), true))
|
||||
} else {
|
||||
discordCollectFields(val, label, out, depth + 1)
|
||||
for (let j = 0; j < sub.length && lines.length < 8; j++) {
|
||||
if (discordSkipPrettyKey(sub[j])) continue
|
||||
const sv = val[sub[j]]
|
||||
if (discordValueEmpty(sv)) continue
|
||||
if (sv && typeof sv === 'object') continue
|
||||
const pretty = discordPrettyValue(sub[j], sv)
|
||||
if (pretty === '—' || pretty === '(none)') continue
|
||||
lines.push('**' + discordHumanLabel(sub[j]) + '** ' + pretty)
|
||||
}
|
||||
if (lines.length) out.push(discordField(discordHumanLabel(k), lines.join('\n'), false))
|
||||
} else {
|
||||
out.push(discordField(label, discordPrettyValue(k, val), true))
|
||||
const pretty = discordPrettyValue(k, val)
|
||||
if (pretty === '—' || pretty === '(none)') continue
|
||||
out.push(discordField(label, pretty, true))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,12 +352,12 @@ function discordPrettySnapshot(title, raw, extras) {
|
||||
}
|
||||
const fields = []
|
||||
discordCollectFields(parsed, '', fields, 0)
|
||||
const note = typeof parsed.note === 'string' ? parsed.note.slice(0, 350) : ''
|
||||
const note = typeof parsed.note === 'string' ? parsed.note.slice(0, 280) : ''
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: title,
|
||||
desc: note,
|
||||
fields: fields.slice(0, 25),
|
||||
fields: fields.slice(0, 18),
|
||||
color: extras && extras.color
|
||||
}),
|
||||
extras
|
||||
@@ -437,7 +487,17 @@ function discordEmbed(opts) {
|
||||
}
|
||||
if (o.title) e.title = String(o.title).slice(0, 256)
|
||||
if (o.desc) e.description = discordCmdClip(discordCmdRedact(o.desc), 1800)
|
||||
if (o.fields && o.fields.length) e.fields = o.fields
|
||||
if (o.fields && o.fields.length) {
|
||||
const fields = []
|
||||
for (let i = 0; i < o.fields.length && fields.length < 25; i++) {
|
||||
const f = o.fields[i]
|
||||
if (!f) continue
|
||||
const v = String(f.value == null ? '' : f.value).trim()
|
||||
if (!v || v === '—') continue
|
||||
fields.push(f)
|
||||
}
|
||||
if (fields.length) e.fields = fields
|
||||
}
|
||||
if (o.author) e.author = o.author
|
||||
return e
|
||||
}
|
||||
@@ -1026,7 +1086,7 @@ async function discordFmView(ctx, interaction) {
|
||||
if (listing.err) desc += '\n' + listing.err
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'Files · ' + discordSessionUser(ctx),
|
||||
title: 'Files',
|
||||
desc: desc,
|
||||
fields: fields.slice(0, 20),
|
||||
color: rec.confirm ? BARE_OS_DISCORD_COLOR_ERR : BARE_OS_DISCORD_COLOR,
|
||||
@@ -1122,17 +1182,17 @@ async function discordCmdHandleBare(ctx, sub) {
|
||||
if (sub === 'help') {
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'Command map',
|
||||
title: 'Commands',
|
||||
desc: 'Running as **' + user + '**. Use the buttons below, or slash commands.',
|
||||
fields: [
|
||||
discordField('/bare', 'status · whoami · hostname · date · uptime · motd · uname · help'),
|
||||
discordField('/sys', 'df · mem · ps · env · doctor · features · rlimits'),
|
||||
discordField('/svc', 'list · status · start · stop · restart · logs *(unit autocomplete)*'),
|
||||
discordField('/fs', 'ls · cat · stat · head *(path autocomplete)*'),
|
||||
discordField('/net', 'peers · swarm · summary'),
|
||||
discordField(
|
||||
'/files /edit /create /settings /man /run /journal /say /panel /ping',
|
||||
'Files, settings editor, edit/create, lookups, run, logs'
|
||||
)
|
||||
discordField('/bare', 'Session — status, whoami, hostname, date, uptime, motd, uname'),
|
||||
discordField('/sys', 'Host — disk, memory, processes, env, doctor, features, limits'),
|
||||
discordField('/svc', 'Services — list, start, stop, restart, logs'),
|
||||
discordField('/fs', 'Read-only VFS — ls, cat, stat, head'),
|
||||
discordField('/net', 'Swarm — peers, summary'),
|
||||
discordField('/files /edit /create', 'Browse and edit files under `~/` and `/tmp`'),
|
||||
discordField('/settings', 'Live session knobs (theme, shell, Discord, agent, aliases)'),
|
||||
discordField('/run /journal /say /man', 'Allowlisted command, logs, speak, man pages')
|
||||
]
|
||||
})
|
||||
)
|
||||
@@ -1142,12 +1202,12 @@ async function discordCmdHandleBare(ctx, sub) {
|
||||
if (sub === 'whoami') {
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'whoami',
|
||||
title: 'Whoami',
|
||||
color: BARE_OS_DISCORD_COLOR_OK,
|
||||
fields: [
|
||||
discordField('User', user, true),
|
||||
discordField('Home', e.HOME || '/home/' + user, true),
|
||||
discordField('Identity', e.BARE_OS_IDENTITY || (ctx.identity && ctx.identity.state) || '—', true)
|
||||
discordField('Home', '`' + (e.HOME || '/home/' + user) + '`', true),
|
||||
discordField('Identity', e.BARE_OS_IDENTITY || (ctx.identity && ctx.identity.state) || '', true)
|
||||
]
|
||||
})
|
||||
)
|
||||
@@ -1155,7 +1215,7 @@ async function discordCmdHandleBare(ctx, sub) {
|
||||
if (sub === 'hostname') {
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'hostname',
|
||||
title: 'Hostname',
|
||||
desc: '`' + String(e.HOSTNAME || e.NAME || 'bare-os') + '`'
|
||||
})
|
||||
)
|
||||
@@ -1163,8 +1223,8 @@ async function discordCmdHandleBare(ctx, sub) {
|
||||
if (sub === 'date') {
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'date',
|
||||
desc: new Date().toISOString() + '\n' + String(Date())
|
||||
title: 'Date',
|
||||
desc: '`' + new Date().toISOString() + '`'
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -1281,17 +1341,33 @@ function discordFormatRlimits(obj) {
|
||||
const lim = obj && obj.rlimits && typeof obj.rlimits === 'object' ? obj.rlimits : obj
|
||||
const fields = []
|
||||
if (!lim || typeof lim !== 'object') return fields
|
||||
const names = {
|
||||
NOFILE: 'Open files',
|
||||
NPROC: 'Processes',
|
||||
AS: 'Address space',
|
||||
DATA: 'Data',
|
||||
STACK: 'Stack',
|
||||
CORE: 'Core dump',
|
||||
RSS: 'RSS',
|
||||
CPU: 'CPU time',
|
||||
FSIZE: 'File size',
|
||||
NOVM: 'No VM',
|
||||
MEMLOCK: 'Locked mem'
|
||||
}
|
||||
const keys = Object.keys(lim)
|
||||
for (let i = 0; i < keys.length && fields.length < 20; i++) {
|
||||
const v = lim[keys[i]]
|
||||
for (let i = 0; i < keys.length && fields.length < 18; i++) {
|
||||
const raw = keys[i]
|
||||
const v = lim[raw]
|
||||
if (v && typeof v === 'object' && (v.cur != null || v.max != null)) {
|
||||
const cur = discordIsByteishKey(keys[i]) ? discordPrettyBytes(v.cur) : String(v.cur)
|
||||
const max = discordIsByteishKey(keys[i]) ? discordPrettyBytes(v.max) : String(v.max)
|
||||
fields.push(discordField(keys[i].replace(/^RLIMIT_/, ''), cur + ' / ' + max, true))
|
||||
const short = raw.replace(/^RLIMIT_/, '')
|
||||
const label = names[short] || discordHumanLabel(short)
|
||||
const cur = discordIsByteishKey(raw) ? discordPrettyBytes(v.cur) : String(v.cur)
|
||||
const max = discordIsByteishKey(raw) ? discordPrettyBytes(v.max) : String(v.max)
|
||||
fields.push(discordField(label, cur + ' / ' + max, true))
|
||||
}
|
||||
}
|
||||
if (obj && obj.bareOsExecMaxDepth != null) {
|
||||
fields.push(discordField('exec depth', String(obj.bareOsExecMaxDepth), true))
|
||||
fields.push(discordField('Exec depth', String(obj.bareOsExecMaxDepth), true))
|
||||
}
|
||||
return fields
|
||||
}
|
||||
@@ -1305,15 +1381,60 @@ function discordFormatFeatures(obj) {
|
||||
const keys = Object.keys(feat).sort()
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const v = feat[keys[i]]
|
||||
if (v === true || v === 1 || v === '1') on.push(keys[i])
|
||||
else if (v === false || v === 0 || v === '0') off.push(keys[i])
|
||||
if (v === true || v === 1 || v === '1') on.push('`' + keys[i] + '`')
|
||||
else if (v === false || v === 0 || v === '0') off.push('`' + keys[i] + '`')
|
||||
}
|
||||
const fields = []
|
||||
if (on.length) fields.push(discordField('Enabled', on.slice(0, 30).join(', ')))
|
||||
if (off.length) fields.push(discordField('Off', off.slice(0, 20).join(', ')))
|
||||
if (on.length) fields.push(discordField('On', on.slice(0, 24).join(' ')))
|
||||
if (off.length) fields.push(discordField('Off', off.slice(0, 16).join(' ')))
|
||||
return fields
|
||||
}
|
||||
|
||||
function discordFormatDoctor(obj) {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return { fields: [], desc: '', color: BARE_OS_DISCORD_COLOR_WARN }
|
||||
}
|
||||
const pa = obj.peerAdmission && typeof obj.peerAdmission === 'object' ? obj.peerAdmission : {}
|
||||
const hn = obj.hostnameMutation && typeof obj.hostnameMutation === 'object' ? obj.hostnameMutation : {}
|
||||
const kh = obj.keyHandlePolicy && typeof obj.keyHandlePolicy === 'object' ? obj.keyHandlePolicy : {}
|
||||
const fields = []
|
||||
if (pa.allowlistConfigured != null) {
|
||||
fields.push(discordField('Peer allowlist', pa.allowlistConfigured ? 'configured' : 'open', true))
|
||||
}
|
||||
if (pa.denylistConfigured != null) {
|
||||
fields.push(discordField('Peer denylist', pa.denylistConfigured ? 'configured' : 'none', true))
|
||||
}
|
||||
if (pa.requireCapsConfigured != null) {
|
||||
const n = pa.requireCapsTokenCount
|
||||
fields.push(
|
||||
discordField(
|
||||
'Require caps',
|
||||
pa.requireCapsConfigured ? (n ? n + ' tokens' : 'yes') : 'no',
|
||||
true
|
||||
)
|
||||
)
|
||||
}
|
||||
if (hn.enabled != null) {
|
||||
fields.push(discordField('Hostname set', hn.enabled ? 'allowed' : 'locked', true))
|
||||
}
|
||||
if (kh.defaultTtlMs) {
|
||||
fields.push(discordField('Key TTL', discordPrettyValue('defaultTtlMs', kh.defaultTtlMs), true))
|
||||
}
|
||||
if (Array.isArray(obj.mfaExtensionPoints) && obj.mfaExtensionPoints.length) {
|
||||
fields.push(discordField('MFA hooks', obj.mfaExtensionPoints.join(' · '), false))
|
||||
}
|
||||
const bits = []
|
||||
if (pa.allowlistConfigured) bits.push('peer allowlist on')
|
||||
else if (pa.allowlistConfigured === false) bits.push('peer allowlist **open**')
|
||||
if (hn.enabled === true) bits.push('hostname mutation on')
|
||||
else if (hn.enabled === false) bits.push('hostname locked')
|
||||
return {
|
||||
fields: fields,
|
||||
desc: bits.join(' · '),
|
||||
color: pa.allowlistConfigured ? BARE_OS_DISCORD_COLOR_OK : BARE_OS_DISCORD_COLOR_WARN
|
||||
}
|
||||
}
|
||||
|
||||
async function discordCmdHandleSys(ctx, sub) {
|
||||
if (sub === 'df') {
|
||||
const t =
|
||||
@@ -1346,42 +1467,65 @@ async function discordCmdHandleSys(ctx, sub) {
|
||||
const table = await discordCmdReadJson(ctx, '/proc/bare_os/process_table.json')
|
||||
const rows = table && Array.isArray(table.processes) ? table.processes : []
|
||||
const fields = []
|
||||
for (let i = 0; i < Math.min(rows.length, 20); i++) {
|
||||
for (let i = 0; i < Math.min(rows.length, 18); i++) {
|
||||
const r = rows[i] || {}
|
||||
const name = String(r.name || r.comm || r.cmd || r.id || 'proc')
|
||||
const st = String(r.state || r.status || '—')
|
||||
const st = String(r.state || r.status || '')
|
||||
const pid = String(r.pid || r.id || i)
|
||||
fields.push(discordField('#' + pid + ' ' + name, st, true))
|
||||
fields.push(discordField(name, 'pid ' + pid + (st ? ' · ' + st : ''), true))
|
||||
}
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'Processes · ' + rows.length,
|
||||
desc: rows.length ? '' : 'No logical processes in the session table.',
|
||||
desc: rows.length ? rows.length + ' logical processes in this session.' : 'No logical processes in the session table.',
|
||||
fields: fields
|
||||
})
|
||||
)
|
||||
}
|
||||
if (sub === 'env') {
|
||||
const e = discordCmdEnv(ctx)
|
||||
const ident = [
|
||||
discordField('USER', e.USER, true),
|
||||
discordField('HOME', e.HOME, true),
|
||||
discordField('SHELL', e.SHELL, true),
|
||||
discordField('PWD', e.PWD, true),
|
||||
const fields = [
|
||||
discordField('User', e.USER, true),
|
||||
discordField('Home', e.HOME, true),
|
||||
discordField('Identity', e.BARE_OS_IDENTITY, true),
|
||||
discordField('UID/GID', String(e.UID || '—') + ' / ' + String(e.GID || '—'), true)
|
||||
discordField('Shell', e.SHELL, true),
|
||||
discordField('Pwd', e.PWD, true),
|
||||
discordField(
|
||||
'UID / GID',
|
||||
e.UID || e.GID ? String(e.UID || '—') + ' / ' + String(e.GID || '—') : '',
|
||||
true
|
||||
)
|
||||
]
|
||||
const prefer = [
|
||||
'HOSTNAME',
|
||||
'PATH',
|
||||
'TERM',
|
||||
'TZ',
|
||||
'LANG',
|
||||
'EDITOR',
|
||||
'PAGER',
|
||||
'BARE_OS_THEME',
|
||||
'BARE_OS_COLOR_DEPTH'
|
||||
]
|
||||
const skip = /TOKEN|SECRET|PASSWORD|PASSWD|KEY|PRIVATE|CREDENTIAL|^USER$|^HOME$|^SHELL$|^PWD$|^LOGNAME$|^UID$|^GID$|^GROUP$|^BARE_OS_IDENTITY$/i
|
||||
const rest = []
|
||||
const seen = Object.create(null)
|
||||
for (let i = 0; i < prefer.length; i++) {
|
||||
const k = prefer[i]
|
||||
seen[k] = 1
|
||||
if (e[k] == null || e[k] === '') continue
|
||||
fields.push(discordField(discordHumanLabel(k.replace(/^BARE_OS_/, '')), String(e[k]).slice(0, 80), true))
|
||||
}
|
||||
const keys = Object.keys(e).sort()
|
||||
for (let i = 0; i < keys.length && rest.length < 16; i++) {
|
||||
if (skip.test(keys[i])) continue
|
||||
rest.push(discordField(keys[i], String(e[keys[i]]).slice(0, 80), true))
|
||||
for (let i = 0; i < keys.length && fields.length < 15; i++) {
|
||||
if (seen[keys[i]] || skip.test(keys[i])) continue
|
||||
if (e[keys[i]] == null || e[keys[i]] === '') continue
|
||||
fields.push(discordField(keys[i], String(e[keys[i]]).slice(0, 72), true))
|
||||
}
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'Environment · ' + discordSessionUser(ctx),
|
||||
fields: ident.concat(rest)
|
||||
title: 'Environment',
|
||||
desc: 'Session env for **' + discordSessionUser(ctx) + '** (secrets omitted).',
|
||||
fields: fields
|
||||
}),
|
||||
{ ephemeral: true }
|
||||
)
|
||||
@@ -1391,10 +1535,18 @@ async function discordCmdHandleSys(ctx, sub) {
|
||||
(await discordCmdReadText(ctx, '/proc/bare_os/security_posture.json')) ||
|
||||
(await discordCmdReadText(ctx, '/proc/bare_os/debug.json'))
|
||||
const obj = discordTryJson(t)
|
||||
const pa = obj && obj.peerAdmission
|
||||
const color =
|
||||
pa && pa.allowlistConfigured ? BARE_OS_DISCORD_COLOR_OK : BARE_OS_DISCORD_COLOR_WARN
|
||||
return discordPrettySnapshot('Doctor', t, { color: color })
|
||||
const fmt = obj ? discordFormatDoctor(obj) : null
|
||||
if (fmt && fmt.fields.length) {
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'Doctor',
|
||||
desc: fmt.desc,
|
||||
fields: fmt.fields,
|
||||
color: fmt.color
|
||||
})
|
||||
)
|
||||
}
|
||||
return discordPrettySnapshot('Doctor', t, { color: fmt && fmt.color })
|
||||
}
|
||||
if (sub === 'features') {
|
||||
const t =
|
||||
@@ -1404,7 +1556,13 @@ async function discordCmdHandleSys(ctx, sub) {
|
||||
const obj = discordTryJson(t)
|
||||
const fields = obj ? discordFormatFeatures(obj) : []
|
||||
if (fields.length) {
|
||||
return discordResult(discordEmbed({ title: 'Features', fields: fields }))
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'Features',
|
||||
desc: 'Guest capability flags on this booter.',
|
||||
fields: fields
|
||||
})
|
||||
)
|
||||
}
|
||||
return discordPrettySnapshot('Features', t)
|
||||
}
|
||||
@@ -1416,7 +1574,7 @@ async function discordCmdHandleSys(ctx, sub) {
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'Resource limits',
|
||||
desc: 'soft / hard · Bare OS runtime caps (not Linux rlimits)',
|
||||
desc: 'Soft / hard · Bare OS runtime caps (not Linux rlimits).',
|
||||
fields: fields
|
||||
})
|
||||
)
|
||||
@@ -1439,14 +1597,11 @@ async function discordCmdHandleSvc(ctx, sub, unit) {
|
||||
const fields = []
|
||||
for (let i = 0; i < parsed.length && fields.length < 20; i++) {
|
||||
const r = parsed[i]
|
||||
const st = (r.active || '—') + (r.sub ? ' / ' + r.sub : '')
|
||||
fields.push(
|
||||
discordField(
|
||||
r.unit,
|
||||
st + (r.preset && r.preset !== 'enabled' ? ' · ' + r.preset : ''),
|
||||
true
|
||||
)
|
||||
)
|
||||
const bits = []
|
||||
if (r.active) bits.push('**' + r.active + '**')
|
||||
if (r.sub && r.sub !== r.active) bits.push(r.sub)
|
||||
if (r.preset && r.preset !== 'enabled') bits.push(r.preset)
|
||||
fields.push(discordField(r.unit, bits.join(' · ') || 'unknown', true))
|
||||
}
|
||||
const units = await discordSuggestUnits(ctx, '')
|
||||
const sel = discordSelect(
|
||||
@@ -1459,7 +1614,11 @@ async function discordCmdHandleSvc(ctx, sub, unit) {
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'Services · ' + (parsed.length || 0),
|
||||
desc: parsed.length ? '' : out ? discordCmdClip(out, 800) : '(no units)',
|
||||
desc: parsed.length
|
||||
? 'Pick a unit to inspect, start, or stop.'
|
||||
: out
|
||||
? discordCmdClip(out, 800)
|
||||
: 'No units registered in this session.',
|
||||
fields: fields,
|
||||
color: BARE_OS_DISCORD_COLOR
|
||||
}),
|
||||
@@ -1491,8 +1650,11 @@ async function discordCmdHandleSvc(ctx, sub, unit) {
|
||||
discordEmbed({
|
||||
title: name,
|
||||
desc: out
|
||||
? '**systemctl ' + sub + '**\n' + discordCmdClip(discordCmdRedact(out), 1200)
|
||||
: '(no output)',
|
||||
? '`' +
|
||||
sub +
|
||||
'`\n' +
|
||||
discordCmdFence(discordCmdClip(discordCmdRedact(out), 1100))
|
||||
: 'No output from `systemctl ' + sub + '`.',
|
||||
color: color
|
||||
}),
|
||||
{ components: act ? [act] : [] }
|
||||
@@ -1522,11 +1684,18 @@ async function discordCmdHandleFs(ctx, sub, rawPath, nlines) {
|
||||
: '(empty)'
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'ls ' + p,
|
||||
title: 'Listing',
|
||||
desc:
|
||||
'`' +
|
||||
p +
|
||||
'` · ' +
|
||||
list.length +
|
||||
' ' +
|
||||
(list.length === 1 ? 'entry' : 'entries') +
|
||||
'\n' +
|
||||
desc +
|
||||
(list.length > shown.length ? '\n+' + (list.length - shown.length) + ' more' : ''),
|
||||
footer: list.length + ' entries · ' + discordSessionUser(ctx)
|
||||
footer: discordSessionUser(ctx)
|
||||
})
|
||||
)
|
||||
} catch (err) {
|
||||
@@ -1555,14 +1724,12 @@ async function discordCmdHandleFs(ctx, sub, rawPath, nlines) {
|
||||
discordField('Modified', Number.isFinite(ms) ? new Date(ms).toISOString() : String(st.mtime), true)
|
||||
)
|
||||
}
|
||||
if (st.path) fields.push(discordField('Path', String(st.path), false))
|
||||
if (fields.length < 4) {
|
||||
discordCollectFields(st, '', fields, 0)
|
||||
}
|
||||
if (st.path && st.path !== p) fields.push(discordField('Resolved', String(st.path), false))
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'stat ' + p,
|
||||
fields: fields.slice(0, 20)
|
||||
title: 'Stat',
|
||||
desc: '`' + p + '`',
|
||||
fields: fields.slice(0, 12)
|
||||
})
|
||||
)
|
||||
} catch (err) {
|
||||
@@ -1571,7 +1738,7 @@ async function discordCmdHandleFs(ctx, sub, rawPath, nlines) {
|
||||
}
|
||||
const text = await discordCmdReadText(ctx, p)
|
||||
if (!text) {
|
||||
return discordResult(discordEmbed({ title: p, desc: '(empty or unreadable)' }))
|
||||
return discordResult(discordEmbed({ title: 'File', desc: '`' + p + '` is empty or unreadable.' }))
|
||||
}
|
||||
const writable = Boolean(discordCmdPathWriteOk(ctx, p))
|
||||
const editRow = writable
|
||||
@@ -1583,63 +1750,148 @@ async function discordCmdHandleFs(ctx, sub, rawPath, nlines) {
|
||||
}
|
||||
const asJson = discordTryJson(text)
|
||||
if (asJson && typeof asJson === 'object' && !Array.isArray(asJson)) {
|
||||
return discordPrettySnapshot((sub === 'head' ? 'head ' : '') + p, asJson, extra)
|
||||
return discordPrettySnapshot(p, asJson, extra)
|
||||
}
|
||||
if (sub === 'head') {
|
||||
const n = Math.max(1, Math.min(40, Number(nlines) || 12))
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'head ' + p,
|
||||
desc: discordCmdFence(text.split(/\r?\n/).slice(0, n).join('\n'))
|
||||
title: 'Head',
|
||||
desc: '`' + p + '` · first ' + n + ' lines\n' + discordCmdFence(text.split(/\r?\n/).slice(0, n).join('\n'))
|
||||
}),
|
||||
extra
|
||||
)
|
||||
}
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: p,
|
||||
desc: discordCmdFence(text.slice(0, BARE_OS_DISCORD_FS_MAX)),
|
||||
title: 'File',
|
||||
desc: '`' + p + '`\n' + discordCmdFence(text.slice(0, BARE_OS_DISCORD_FS_MAX)),
|
||||
footer: text.length + ' bytes · ' + discordSessionUser(ctx)
|
||||
}),
|
||||
extra
|
||||
)
|
||||
}
|
||||
|
||||
function discordFormatNetSummary(obj) {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return { fields: [], desc: '', color: BARE_OS_DISCORD_COLOR }
|
||||
}
|
||||
const rq =
|
||||
obj.replicationQueue && typeof obj.replicationQueue === 'object' ? obj.replicationQueue : {}
|
||||
const st = obj.stagingSlot && typeof obj.stagingSlot === 'object' ? obj.stagingSlot : {}
|
||||
const fields = []
|
||||
const peers = obj.peerCount
|
||||
const role = obj.seedRole || rq.role || st.role
|
||||
const proto = rq.protocol || st.protocol || obj.protocol
|
||||
if (peers != null) fields.push(discordField('Peers', String(peers), true))
|
||||
if (role) fields.push(discordField('Role', String(role), true))
|
||||
if (proto) fields.push(discordField('Protocol', '`' + String(proto) + '`', true))
|
||||
if (obj.topicHex) {
|
||||
const hex = String(obj.topicHex)
|
||||
fields.push(
|
||||
discordField('Topic', '`' + hex.slice(0, 16) + (hex.length > 16 ? '…' : '') + '`', true)
|
||||
)
|
||||
}
|
||||
if (obj.replicationQueueDepth != null) {
|
||||
fields.push(discordField('Queue depth', String(obj.replicationQueueDepth), true))
|
||||
}
|
||||
if (rq.queueDepthEstimate != null) {
|
||||
fields.push(discordField('Queue estimate', String(rq.queueDepthEstimate), true))
|
||||
}
|
||||
if (rq.hypercoreLengthHint != null) {
|
||||
fields.push(discordField('Core length', String(rq.hypercoreLengthHint), true))
|
||||
}
|
||||
if (rq.manifestPathCount != null) {
|
||||
fields.push(discordField('Manifests', String(rq.manifestPathCount), true))
|
||||
}
|
||||
if (rq.localRamBlockCount != null) {
|
||||
fields.push(discordField('RAM blocks', String(rq.localRamBlockCount), true))
|
||||
}
|
||||
if (st.activeSlot) fields.push(discordField('Active slot', String(st.activeSlot), true))
|
||||
const fwA = obj.peerFirewallAcceptedTotal
|
||||
const fwR = obj.peerFirewallRejectedTotal
|
||||
const fwI = obj.peerFirewallInboundTotal
|
||||
const fwO = obj.peerFirewallOutboundTotal
|
||||
if (fwA != null || fwR != null || fwI != null || fwO != null) {
|
||||
fields.push(
|
||||
discordField(
|
||||
'Firewall',
|
||||
'accept ' +
|
||||
(fwA == null ? '0' : fwA) +
|
||||
' · reject ' +
|
||||
(fwR == null ? '0' : fwR) +
|
||||
'\nin ' +
|
||||
(fwI == null ? '0' : fwI) +
|
||||
' · out ' +
|
||||
(fwO == null ? '0' : fwO),
|
||||
true
|
||||
)
|
||||
)
|
||||
}
|
||||
if (obj.seedHandshakeError) {
|
||||
fields.push(discordField('Handshake', String(obj.seedHandshakeError), false))
|
||||
}
|
||||
const peerN = Number(peers)
|
||||
const head = []
|
||||
if (Number.isFinite(peerN)) head.push('**' + peerN + '** peer' + (peerN === 1 ? '' : 's'))
|
||||
if (role) head.push(String(role))
|
||||
if (proto) head.push('`' + proto + '`')
|
||||
let desc = head.join(' · ')
|
||||
const note = rq.snapshotWorkflowNote || obj.note
|
||||
if (note) desc += (desc ? '\n' : '') + '*' + String(note).slice(0, 220) + '*'
|
||||
const at = rq.atMs || obj.atMs
|
||||
return {
|
||||
fields: fields,
|
||||
desc: desc,
|
||||
color: Number.isFinite(peerN) && peerN > 0 ? BARE_OS_DISCORD_COLOR_OK : BARE_OS_DISCORD_COLOR_WARN,
|
||||
footer: at ? 'Updated ' + new Date(Number(at)).toISOString() + ' · expires after 2m idle' : ''
|
||||
}
|
||||
}
|
||||
|
||||
function discordFormatSwarm(obj) {
|
||||
if (!obj || typeof obj !== 'object') return []
|
||||
const fields = []
|
||||
if (obj.peerCount != null) fields.push(discordField('Peers', String(obj.peerCount), true))
|
||||
if (obj.protocol) fields.push(discordField('Protocol', String(obj.protocol), true))
|
||||
if (obj.protocol) fields.push(discordField('Protocol', '`' + String(obj.protocol) + '`', true))
|
||||
if (obj.topicCount != null) fields.push(discordField('Topics', String(obj.topicCount), true))
|
||||
if (obj.topicHex) {
|
||||
fields.push(discordField('Topic', String(obj.topicHex).slice(0, 16) + '…', true))
|
||||
const hex = String(obj.topicHex)
|
||||
fields.push(
|
||||
discordField('Topic', '`' + hex.slice(0, 16) + (hex.length > 16 ? '…' : '') + '`', true)
|
||||
)
|
||||
}
|
||||
const lc = obj.lifecycle
|
||||
if (lc && typeof lc === 'object') {
|
||||
const bits = []
|
||||
const lk = Object.keys(lc)
|
||||
for (let i = 0; i < lk.length && bits.length < 6; i++) {
|
||||
if (typeof lc[lk[i]] !== 'object') bits.push(lk[i] + ': ' + discordPrettyValue(lk[i], lc[lk[i]]))
|
||||
if (typeof lc[lk[i]] === 'object' || discordValueEmpty(lc[lk[i]])) continue
|
||||
bits.push('**' + discordHumanLabel(lk[i]) + '** ' + discordPrettyValue(lk[i], lc[lk[i]]))
|
||||
}
|
||||
if (bits.length) fields.push(discordField('Lifecycle', bits.join('\n'), true))
|
||||
if (bits.length) fields.push(discordField('Lifecycle', bits.join('\n'), false))
|
||||
}
|
||||
const sc = obj.peerScoringAggregates
|
||||
if (sc && typeof sc === 'object') {
|
||||
fields.push(
|
||||
discordField(
|
||||
'Scoring',
|
||||
'banned ' +
|
||||
String(sc.bannedActiveCount || 0) +
|
||||
' · high-lat ' +
|
||||
String(sc.highLatencyEwmaCount || 0) +
|
||||
' · low-ok ' +
|
||||
String(sc.lowSuccessRateBucketCount || 0),
|
||||
true
|
||||
const banned = sc.bannedActiveCount
|
||||
const hi = sc.highLatencyEwmaCount
|
||||
const low = sc.lowSuccessRateBucketCount
|
||||
if (banned != null || hi != null || low != null) {
|
||||
fields.push(
|
||||
discordField(
|
||||
'Scoring',
|
||||
'banned ' +
|
||||
String(banned || 0) +
|
||||
' · high-lat ' +
|
||||
String(hi || 0) +
|
||||
' · low-ok ' +
|
||||
String(low || 0),
|
||||
true
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (Array.isArray(obj.peers) && obj.peers.length) {
|
||||
fields.push(discordField('Peer rows', String(obj.peers.length), true))
|
||||
fields.push(discordField('Peer list', String(obj.peers.length), true))
|
||||
}
|
||||
return fields
|
||||
}
|
||||
@@ -1653,9 +1905,14 @@ async function discordCmdHandleNet(ctx, sub) {
|
||||
const obj = discordTryJson(t)
|
||||
const fields = obj ? discordFormatSwarm(obj) : []
|
||||
if (fields.length) {
|
||||
const n = obj && obj.peerCount
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'Swarm',
|
||||
desc:
|
||||
n != null
|
||||
? '**' + n + '** peer' + (Number(n) === 1 ? '' : 's') + (obj.protocol ? ' · `' + obj.protocol + '`' : '')
|
||||
: '',
|
||||
fields: fields,
|
||||
color: obj && obj.peerCount > 0 ? BARE_OS_DISCORD_COLOR_OK : BARE_OS_DISCORD_COLOR_WARN
|
||||
})
|
||||
@@ -1667,7 +1924,21 @@ async function discordCmdHandleNet(ctx, sub) {
|
||||
(await discordCmdReadText(ctx, '/proc/bare_os/net_summary.json')) ||
|
||||
(await discordCmdReadText(ctx, '/proc/net/dev'))
|
||||
const obj = discordTryJson(t)
|
||||
if (obj) return discordPrettySnapshot('Network', obj)
|
||||
if (obj) {
|
||||
const fmt = discordFormatNetSummary(obj)
|
||||
if (fmt.fields.length) {
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'Network',
|
||||
desc: fmt.desc,
|
||||
fields: fmt.fields,
|
||||
color: fmt.color,
|
||||
footer: fmt.footer || undefined
|
||||
})
|
||||
)
|
||||
}
|
||||
return discordPrettySnapshot('Network', obj)
|
||||
}
|
||||
if (t && t.indexOf('Inter-|') >= 0) {
|
||||
const lines = t.split(/\r?\n/).filter(Boolean)
|
||||
return discordResult(
|
||||
@@ -1770,7 +2041,7 @@ async function discordCmdHandleRun(ctx, raw) {
|
||||
title: '$ ' + key,
|
||||
footer: 'ran as ' + discordSessionUser(ctx),
|
||||
desc: out
|
||||
? discordCmdClip(discordCmdRedact(out), 1500)
|
||||
? discordCmdFence(discordCmdClip(discordCmdRedact(out), 1400))
|
||||
: '(no output, exit ' + String(ctx.exitCode || 0) + ')'
|
||||
})
|
||||
)
|
||||
@@ -1793,14 +2064,16 @@ async function discordCmdHandleJournal(ctx, unit) {
|
||||
.slice(-20)
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'journal · ' + unit,
|
||||
title: 'Journal · ' + unit,
|
||||
desc: lines.length
|
||||
? lines
|
||||
.map(function (ln) {
|
||||
return discordCmdRedact(ln).slice(0, 180)
|
||||
})
|
||||
.join('\n')
|
||||
: '(empty journal)'
|
||||
? discordCmdFence(
|
||||
lines
|
||||
.map(function (ln) {
|
||||
return discordCmdRedact(ln).slice(0, 180)
|
||||
})
|
||||
.join('\n')
|
||||
)
|
||||
: 'Empty journal for `' + unit + '`.'
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -1814,14 +2087,16 @@ async function discordCmdHandleJournal(ctx, unit) {
|
||||
.slice(-18)
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'journal',
|
||||
title: 'Journal',
|
||||
desc: lines.length
|
||||
? lines
|
||||
.map(function (ln) {
|
||||
return discordCmdRedact(ln).slice(0, 180)
|
||||
})
|
||||
.join('\n')
|
||||
: '(no journal)'
|
||||
? discordCmdFence(
|
||||
lines
|
||||
.map(function (ln) {
|
||||
return discordCmdRedact(ln).slice(0, 180)
|
||||
})
|
||||
.join('\n')
|
||||
)
|
||||
: 'No journal in `/var/log/bare-os`.'
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -1836,11 +2111,12 @@ function discordPanel(ctx) {
|
||||
desc:
|
||||
'Signed in as **' +
|
||||
user +
|
||||
'** · home `' +
|
||||
'** · `' +
|
||||
(e.HOME || '/home/' + user) +
|
||||
'`\nUse the buttons, or slash commands with autocomplete.',
|
||||
'`\nPick a button, or use a slash command.',
|
||||
fields: [
|
||||
discordField('Identity', e.BARE_OS_IDENTITY || (ctx.identity && ctx.identity.state) || '—', true),
|
||||
discordField('User', user, true),
|
||||
discordField('Identity', e.BARE_OS_IDENTITY || (ctx.identity && ctx.identity.state) || '', true),
|
||||
discordField('Host', e.HOSTNAME || 'bare-os', true)
|
||||
]
|
||||
})
|
||||
@@ -3081,10 +3357,10 @@ async function discordSettingsView(ctx, interaction) {
|
||||
rec.page = pg.page
|
||||
pages = pg.pages
|
||||
page = pg.page
|
||||
if (!entries.length) fields.push(discordField('aliases', '(none)'))
|
||||
if (!entries.length) fields.push(discordField('Aliases', 'None defined. Add one from the menu.'))
|
||||
for (let i = 0; i < pg.slice.length; i++) {
|
||||
const e = pg.slice[i]
|
||||
fields.push(discordField((rec.sel === e.name ? '▸ ' : '') + e.name, e.value || '(empty)'))
|
||||
fields.push(discordField((rec.sel === e.name ? '▸ ' : '') + e.name, '`' + (e.value || '') + '`'))
|
||||
}
|
||||
itemOpts = [{ label: 'Add alias', value: 'alias_add', description: 'name=command' }].concat(
|
||||
pg.slice.map(function (e) {
|
||||
@@ -3103,7 +3379,7 @@ async function discordSettingsView(ctx, interaction) {
|
||||
fields.push(
|
||||
discordField(
|
||||
(rec.sel === spec.id ? '▸ ' : '') + spec.label,
|
||||
discordSettingsDisplay(spec, cur) + ' · ' + spec.live
|
||||
'**' + discordSettingsDisplay(spec, cur) + '**\n' + spec.live
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -3135,12 +3411,9 @@ async function discordSettingsView(ctx, interaction) {
|
||||
return discordResult(
|
||||
discordEmbed({
|
||||
title: 'Settings · ' + meta.label + (pages > 1 ? ' · ' + (page + 1) + '/' + pages : ''),
|
||||
desc:
|
||||
'Live-safe session knobs only. Tokens, API keys, and passwords are never shown or written.\n' +
|
||||
meta.description +
|
||||
' — persist to `~/.barerc`, `~/.discord/.env` (non-secret keys), or app JSON.',
|
||||
desc: meta.description + '. Secrets are never shown. Pick a row, then **Edit** or **Toggle**.',
|
||||
fields: fields.slice(0, 20),
|
||||
footer: discordSessionUser(ctx) + ' · pick a setting, then Edit / Toggle'
|
||||
footer: discordSessionUser(ctx) + ' · expires after 2m idle'
|
||||
}),
|
||||
{ components: [gsel, isel, acts, pageRow].filter(Boolean), nav: false }
|
||||
)
|
||||
@@ -4306,7 +4579,9 @@ var bareOsDiscordCommands = {
|
||||
parseSystemctlList: discordParseSystemctlList,
|
||||
formatRlimits: discordFormatRlimits,
|
||||
formatFeatures: discordFormatFeatures,
|
||||
formatDoctor: discordFormatDoctor,
|
||||
formatSwarm: discordFormatSwarm,
|
||||
formatNetSummary: discordFormatNetSummary,
|
||||
formatHostDf: discordFormatHostDf,
|
||||
pathWriteOk: discordCmdPathWriteOk,
|
||||
editChunks: discordEditChunks,
|
||||
|
||||
Reference in New Issue
Block a user