Watch
1
0
Fork
You've already forked RedFlag
0

fix: grant CAP_SYS_PTRACE on agent binary for display/process discovery

The agent runs as redflag-agent user and cannot read /proc/[pid]/environ
from the logged-in user's session processes (owner-only permissions).
Without this, discoverSessionDisplay() finds nothing — screenshot fails
on Wayland because XDG_RUNTIME_DIR/WAYLAND_DISPLAY are never discovered.

- Installer: setcap cap_sys_ptrace=eip after placing the binary
- Helper (agent self-update): restore setcap after atomic replace,
  since rename() creates a new inode and strips file capabilities

Non-fatal if setcap is unavailable — agent degrades gracefully,
just loses screenshot and per-process telemetry.
This commit is contained in:
Fimeg 2026-06-10 12:27:14 -04:00
commit 2084019be7
3 changed files with 159 additions and 131 deletions

View file

@ -821,18 +821,12 @@ const Agents: React.FC = () => {
)}
</div>
{/* System info — CSS Grid auto-placement, screenshot anchors top-left */}
{/* System info — screenshot left, stats right, top processes below */}
<div className="card">
<h2 className="text-lg font-medium text-gray-900 mb-4">System Information</h2>
<div
className="grid gap-4"
style={{
gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
gridAutoRows: 'min-content',
}}
>
{/* Screenshot — spans 2 rows, anchors top-left */}
<div className="flex gap-6">
{/* Screenshot — left, fixed width */}
{(() => {
const integrations = readIntegrations(selectedAgent.metadata);
const sunshine = integrations.sunshine;
@ -845,7 +839,7 @@ const Agents: React.FC = () => {
const canClick = !isCapturing && !isPolling;
return (
<div style={{ gridRow: 'span 2', gridColumn: '1' }}>
<div className="shrink-0 w-[260px]">
<div
className={cn(
'relative aspect-video w-full overflow-hidden rounded border',
@ -903,139 +897,140 @@ const Agents: React.FC = () => {
? <span className="text-red-500">Failed</span>
: null}
</div>
{/* Platform info — under screenshot */}
{(() => {
const osInfo = parseOSInfo(selectedAgent);
return (
<div className="mt-2 space-y-1">
<div>
<p className="text-xs text-gray-500">Platform</p>
<p className="text-sm font-medium text-gray-900">{osInfo.platform}</p>
</div>
<div>
<p className="text-xs text-gray-500">Distribution</p>
<p className="text-sm font-medium text-gray-900">
{osInfo.distribution}
{osInfo.version && <span className="text-xs text-gray-500 ml-1">({osInfo.version})</span>}
</p>
</div>
<div>
<p className="text-xs text-gray-500">Architecture</p>
<p className="text-sm font-medium text-gray-900">
{selectedAgent.os_architecture || selectedAgent.architecture}
</p>
</div>
</div>
);
})()}
</div>
);
})()}
{/* Platform metadata */}
{(() => {
const osInfo = parseOSInfo(selectedAgent);
return (
<>
<div>
<p className="text-xs text-gray-500">Platform</p>
<p className="text-sm font-medium text-gray-900">{osInfo.platform}</p>
</div>
<div>
<p className="text-xs text-gray-500">Distribution</p>
<p className="text-sm font-medium text-gray-900">
{osInfo.distribution}
{osInfo.version && <span className="text-xs text-gray-500 ml-1">({osInfo.version})</span>}
</p>
</div>
<div>
<p className="text-xs text-gray-500">Architecture</p>
<p className="text-sm font-medium text-gray-900">
{selectedAgent.os_architecture || selectedAgent.architecture}
</p>
</div>
</>
);
})()}
{/* Hardware metrics */}
{(() => {
const meta = getSystemMetadata(selectedAgent);
return (
<>
<div>
<p className="text-xs text-gray-500 flex items-center gap-1">
<Cpu className="h-3 w-3" /> CPU
</p>
<p className="text-sm font-medium text-gray-900">{meta.cpuModel}</p>
<p className="text-xs text-gray-500">{meta.cpuCores} cores</p>
</div>
{meta.memoryTotal > 0 && (
<div>
<p className="text-xs text-gray-500 flex items-center gap-1">
<MemoryStick className="h-3 w-3" /> Memory
</p>
<p className="text-sm font-medium text-gray-900">{formatBytes(meta.memoryTotal)}</p>
</div>
)}
{meta.diskTotal > 0 && (
<div>
<p className="text-xs text-gray-500 flex items-center gap-1">
<HardDrive className="h-3 w-3" /> Disk ({meta.diskMount})
</p>
<p className="text-sm font-medium text-gray-900">
{formatBytes(meta.diskUsed)} / {formatBytes(meta.diskTotal)}
</p>
<div className="w-full bg-gray-200 rounded-full h-1.5 mt-1">
<div
className="bg-blue-600 h-1.5 rounded-full"
style={{ width: `${Math.round((meta.diskUsed / meta.diskTotal) * 100)}%` }}
/>
</div>
<p className="text-xs text-gray-500">{Math.round((meta.diskUsed / meta.diskTotal) * 100)}% used</p>
</div>
)}
{meta.processes !== 'Unknown' && (
<div>
<p className="text-xs text-gray-500 flex items-center gap-1">
<GitBranch className="h-3 w-3" /> Running Processes
</p>
<p className="text-sm font-medium text-gray-900">{meta.processes}</p>
</div>
)}
{meta.uptime !== 'Unknown' && (
<div>
<p className="text-xs text-gray-500 flex items-center gap-1">
<Clock className="h-3 w-3" /> Uptime
</p>
<p className="text-sm font-medium text-gray-900">{meta.uptime}</p>
</div>
)}
</>
);
})()}
{/* Top Processes — grid item, auto-placed */}
<div>
<div className="flex items-center justify-between mb-2">
<p className="text-xs font-medium text-gray-900 flex items-center gap-1">
<Activity className="h-3 w-3" /> Top Processes
</p>
<button
onClick={() => navigate(`/updates?agent=${selectedAgent.id}`)}
className="text-[10px] text-blue-600 hover:text-blue-800"
>
See More
</button>
</div>
{/* All stats — right column, stacked flush top */}
<div className="flex-1 space-y-3 min-w-0">
{(() => {
const meta = getSystemMetadata(selectedAgent);
const topProcesses = selectedAgent.metadata?.top_processes;
if (topProcesses && Array.isArray(topProcesses) && topProcesses.length > 0) {
return (
<table className="w-full text-xs">
<thead>
<tr className="text-gray-500 border-b border-gray-100">
<th className="text-left py-1 font-medium">Name</th>
<th className="text-right py-1 font-medium">PID</th>
<th className="text-right py-1 font-medium">CPU%</th>
<th className="text-right py-1 font-medium">Mem%</th>
</tr>
</thead>
<tbody>
{topProcesses.slice(0, 5).map((proc: any, i: number) => (
<tr key={proc.pid || i} className="border-b border-gray-50 last:border-0">
<td className="py-1 text-gray-900 font-medium truncate max-w-[160px]">{proc.name}</td>
<td className="py-1 text-right text-gray-600">{proc.pid}</td>
<td className="py-1 text-right text-gray-600">{proc.cpu != null ? `${proc.cpu.toFixed(1)}%` : '—'}</td>
<td className="py-1 text-right text-gray-600">{proc.mem != null ? `${proc.mem.toFixed(1)}%` : '—'}</td>
</tr>
))}
</tbody>
</table>
);
}
return (
<p className="text-[10px] text-gray-400 italic">
Process details not reported. Count: {meta.processes}
</p>
<>
<div>
<p className="text-xs text-gray-500 flex items-center gap-1">
<Cpu className="h-3 w-3" /> CPU
</p>
<p className="text-sm font-medium text-gray-900">{meta.cpuModel}</p>
<p className="text-xs text-gray-500">{meta.cpuCores} cores</p>
</div>
{meta.memoryTotal > 0 && (
<div>
<p className="text-xs text-gray-500 flex items-center gap-1">
<MemoryStick className="h-3 w-3" /> Memory
</p>
<p className="text-sm font-medium text-gray-900">{formatBytes(meta.memoryTotal)}</p>
</div>
)}
{meta.diskTotal > 0 && (
<div>
<p className="text-xs text-gray-500 flex items-center gap-1">
<HardDrive className="h-3 w-3" /> Disk ({meta.diskMount})
</p>
<p className="text-sm font-medium text-gray-900">
{formatBytes(meta.diskUsed)} / {formatBytes(meta.diskTotal)}
</p>
<div className="w-full bg-gray-200 rounded-full h-1.5 mt-1">
<div
className="bg-blue-600 h-1.5 rounded-full"
style={{ width: `${Math.round((meta.diskUsed / meta.diskTotal) * 100)}%` }}
/>
</div>
<p className="text-xs text-gray-500">{Math.round((meta.diskUsed / meta.diskTotal) * 100)}% used</p>
</div>
)}
{meta.processes !== 'Unknown' && (
<div>
<p className="text-xs text-gray-500 flex items-center gap-1">
<GitBranch className="h-3 w-3" /> Running Processes
</p>
<p className="text-sm font-medium text-gray-900">{meta.processes}</p>
</div>
)}
{meta.uptime !== 'Unknown' && (
<div>
<p className="text-xs text-gray-500 flex items-center gap-1">
<Clock className="h-3 w-3" /> Uptime
</p>
<p className="text-sm font-medium text-gray-900">{meta.uptime}</p>
</div>
)}
</>
);
})()}
{/* Top Processes */}
<div className="pt-3 border-t border-gray-200">
<div className="flex items-center justify-between mb-2">
<p className="text-xs font-medium text-gray-900 flex items-center gap-1">
<Activity className="h-3 w-3" /> Top Processes
</p>
<button
onClick={() => navigate(`/updates?agent=${selectedAgent.id}`)}
className="text-[10px] text-blue-600 hover:text-blue-800"
>
See More
</button>
</div>
{(() => {
const meta = getSystemMetadata(selectedAgent);
const topProcesses = selectedAgent.metadata?.top_processes;
if (topProcesses && Array.isArray(topProcesses) && topProcesses.length > 0) {
return (
<table className="w-full text-xs">
<thead>
<tr className="text-gray-500 border-b border-gray-100">
<th className="text-left py-1 font-medium">Name</th>
<th className="text-right py-1 font-medium">PID</th>
<th className="text-right py-1 font-medium">CPU%</th>
<th className="text-right py-1 font-medium">Mem%</th>
</tr>
</thead>
<tbody>
{topProcesses.slice(0, 5).map((proc: any, i: number) => (
<tr key={proc.pid || i} className="border-b border-gray-50 last:border-0">
<td className="py-1 text-gray-900 font-medium truncate max-w-[160px]">{proc.name}</td>
<td className="py-1 text-right text-gray-600">{proc.pid}</td>
<td className="py-1 text-right text-gray-600">{proc.cpu != null ? `${proc.cpu.toFixed(1)}%` : '—'}</td>
<td className="py-1 text-right text-gray-600">{proc.mem != null ? `${proc.mem.toFixed(1)}%` : '—'}</td>
</tr>
))}
</tbody>
</table>
);
}
return (
<p className="text-[10px] text-gray-400 italic">
Process details not reported. Count: {meta.processes}
</p>
);
})()}
</div>
</div>
</div>
</div>