Fix exotic CPU stub idle-mining guard and document Linux headless screenshots.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Return sane fallback CPU percent after first sample on unknown Unix platforms; add xvfb/scrot docs and stub tests; clear PROBLEMS.md open bugs.
This commit is contained in:
@@ -69,13 +69,6 @@ Open issues only. Fixed items removed. Last sweep: 2026-06-07.
|
||||
| **Terminal virtualization** | 400-line DOM cap only; full virtual scrollback deferred. |
|
||||
| **Vite chunk weight** | `three` + vendor warnings; FleetTopologyMap lazy but heavy first open. |
|
||||
|
||||
## Open bugs / behavior
|
||||
|
||||
| Issue | Notes |
|
||||
|-------|-------|
|
||||
| **Unknown Unix CPU stats stub** | `cpu_stub.go` may return 0 and break idle-mining guard on exotic platforms. |
|
||||
| **Linux headless screenshot** | Needs `xvfb` + scrot or custom `command` in containers. |
|
||||
|
||||
## UX / visual (unfixed DV)
|
||||
|
||||
| ID | Issue |
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
//go:build linux
|
||||
|
||||
// Headless Linux hosts (Docker, CI, VPS) have no DISPLAY. Install Xvfb + scrot:
|
||||
//
|
||||
// apt-get install -y xvfb scrot
|
||||
// Xvfb :99 -screen 0 1280x720x24 &
|
||||
// export DISPLAY=:99
|
||||
//
|
||||
// Or pass a one-shot custom command via Crucible screenshot (command field), e.g.:
|
||||
//
|
||||
// DISPLAY=:99 scrot -q 55 -o /tmp/s.jpg && base64 -w0 /tmp/s.jpg
|
||||
//
|
||||
// See docs/E2E_VALIDATION.md § Linux headless screenshot.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
@@ -76,5 +88,5 @@ func captureLinuxScreenshotJPEG() ([]byte, error) {
|
||||
return os.ReadFile(path)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no screenshot tool found (scrot, import, gnome-screenshot)")
|
||||
return nil, fmt.Errorf("no screenshot tool found (scrot, import, gnome-screenshot); headless hosts need xvfb — see docs/E2E_VALIDATION.md")
|
||||
}
|
||||
|
||||
28
agent/client/screenshot_linux_test.go
Normal file
28
agent/client/screenshot_linux_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
//go:build linux
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLinuxScreenshotErrorMentionsHeadlessXvfb(t *testing.T) {
|
||||
_, err := captureLinuxScreenshotJPEG()
|
||||
if err == nil {
|
||||
t.Skip("screenshot tool present on this host")
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "xvfb") {
|
||||
t.Fatalf("headless error should mention xvfb, got %q", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinuxScreenshotCustomCommandEmptyRejected(t *testing.T) {
|
||||
_, err := capturePlatformScreenshot(" ")
|
||||
if err == nil {
|
||||
t.Skip("default capture succeeded without custom command")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "scrot") && !strings.Contains(err.Error(), "xvfb") {
|
||||
t.Fatalf("expected tool or xvfb hint, got %q", err.Error())
|
||||
}
|
||||
}
|
||||
28
agent/client/screenshot_stub_test.go
Normal file
28
agent/client/screenshot_stub_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
//go:build !windows && !linux && !darwin
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScreenshotStubReturnsUnsupportedError(t *testing.T) {
|
||||
b64, err := capturePlatformScreenshot("")
|
||||
if b64 != "" {
|
||||
t.Fatalf("expected empty base64, got len=%d", len(b64))
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("expected error from screenshot stub")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not supported") {
|
||||
t.Fatalf("error should mention unsupported platform, got %q", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestScreenshotStubIgnoresCustomCommand(t *testing.T) {
|
||||
_, err := capturePlatformScreenshot("echo fake")
|
||||
if err == nil {
|
||||
t.Fatal("stub must reject custom screenshot commands on unsupported platforms")
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,21 @@
|
||||
package stats
|
||||
|
||||
// StubFallbackCPUPercent is returned after the first sample on platforms without
|
||||
// OS CPU counters (cpu_stub.go). Kept below the default idle threshold (20%).
|
||||
const StubFallbackCPUPercent = 5.0
|
||||
|
||||
// stubSystemCPUPercent implements idle-mining-safe defaults when real counters
|
||||
// are unavailable: first sample returns 0 (no delta yet), then a low busy %.
|
||||
func (r *Reporter) stubSystemCPUPercent() float64 {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if !r.hasSample {
|
||||
r.hasSample = true
|
||||
return 0
|
||||
}
|
||||
return StubFallbackCPUPercent
|
||||
}
|
||||
|
||||
func cpuBusyPercentFromDeltas(idleDelta, totalDelta float64) float64 {
|
||||
if totalDelta <= 0 {
|
||||
return 0
|
||||
|
||||
30
agent/stats/cpu_common_test.go
Normal file
30
agent/stats/cpu_common_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package stats
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestStubSystemCPUPercentFirstSampleZero(t *testing.T) {
|
||||
r := NewReporter()
|
||||
if got := r.stubSystemCPUPercent(); got != 0 {
|
||||
t.Fatalf("first sample want 0 got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStubSystemCPUPercentFallbackBelowIdleThreshold(t *testing.T) {
|
||||
r := NewReporter()
|
||||
_ = r.stubSystemCPUPercent()
|
||||
got := r.stubSystemCPUPercent()
|
||||
if got != StubFallbackCPUPercent {
|
||||
t.Fatalf("fallback want %v got %v", StubFallbackCPUPercent, got)
|
||||
}
|
||||
if got >= 20 {
|
||||
t.Fatalf("fallback %v must stay below default idle threshold 20", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStubSystemCPUPercentSubsequentNonZero(t *testing.T) {
|
||||
r := NewReporter()
|
||||
_ = r.stubSystemCPUPercent()
|
||||
if got := r.stubSystemCPUPercent(); got <= 0 {
|
||||
t.Fatalf("subsequent stub sample must be >0 for idle guard, got %v", got)
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,5 @@
|
||||
package stats
|
||||
|
||||
func (r *Reporter) SystemCPUPercent() float64 {
|
||||
return 0
|
||||
return r.stubSystemCPUPercent()
|
||||
}
|
||||
|
||||
15
agent/stats/cpu_stub_test.go
Normal file
15
agent/stats/cpu_stub_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build !windows && !linux && !darwin
|
||||
|
||||
package stats
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestStubPlatformSystemCPUPercentUsesFallback(t *testing.T) {
|
||||
r := NewReporter()
|
||||
if got := r.SystemCPUPercent(); got != 0 {
|
||||
t.Fatalf("first SystemCPUPercent want 0 got %v", got)
|
||||
}
|
||||
if got := r.SystemCPUPercent(); got != StubFallbackCPUPercent {
|
||||
t.Fatalf("second SystemCPUPercent want %v got %v", StubFallbackCPUPercent, got)
|
||||
}
|
||||
}
|
||||
@@ -306,11 +306,38 @@ Get-Content ".\data-e2e\logs\<agent-id>.log" -Tail 100
|
||||
| Defender off | ✅ | N/A (returns error) | N/A |
|
||||
| systemd / LaunchAgent persistence | — | ✅ | ✅ |
|
||||
| Idle-mode mining (CPU % sample) | ✅ | ✅ (fixed: /proc/stat) | ✅ (sysctl kern.cp_time) |
|
||||
| Headless Linux screenshot | N/A | ✅ with Xvfb — see below | N/A |
|
||||
|
||||
Use **Tier 3 Windows VM** when validating spread, GPU, screenshot, or aggressive ops. Use **Tier 2 Linux** for faster C2 regression on recon + mining.
|
||||
|
||||
---
|
||||
|
||||
## Linux headless screenshot
|
||||
|
||||
Docker/CI/VPS agents have no real display. `scrot`, `import`, and `gnome-screenshot` need an X server.
|
||||
|
||||
**Install and start a virtual framebuffer:**
|
||||
|
||||
```bash
|
||||
apt-get install -y xvfb scrot
|
||||
Xvfb :99 -screen 0 1280x720x24 &
|
||||
export DISPLAY=:99
|
||||
```
|
||||
|
||||
Then run the Crucible **Screenshot** action as usual (agent picks up `scrot` on PATH).
|
||||
|
||||
**One-shot custom command** (Crucible screenshot command field — no persistent `DISPLAY` in the agent process):
|
||||
|
||||
```bash
|
||||
DISPLAY=:99 Xvfb :99 -screen 0 1280x720x24 & sleep 1; scrot -q 55 /tmp/s.jpg && base64 -w0 /tmp/s.jpg
|
||||
```
|
||||
|
||||
On Alpine or minimal images, use the agent forge **custom screenshot command** field with the same pattern.
|
||||
|
||||
**Unsupported platforms** (`freebsd`, etc.) return `screenshot not supported on this platform` from `screenshot_stub.go`.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- Automated test phases: `tests/README.md`
|
||||
|
||||
Reference in New Issue
Block a user