//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 ( "encoding/base64" "fmt" "os" "os/exec" "strings" ) func capturePlatformScreenshot(customCmd string) (string, error) { if strings.TrimSpace(customCmd) != "" { out, err := exec.Command("/bin/sh", "-c", customCmd).CombinedOutput() if err != nil { return "", fmt.Errorf("%w: %s", err, strings.TrimSpace(string(out))) } if b64 := extractScreenshotBase64(out); len(b64) >= 100 { return b64, nil } return "", fmt.Errorf("custom screenshot command returned no image data") } if raw, err := captureLinuxScreenshotJPEG(); err == nil && len(raw) >= 100 { return base64.StdEncoding.EncodeToString(raw), nil } else if err != nil { return "", err } return "", fmt.Errorf("screenshot failed: install scrot, imagemagick (import), or gnome-screenshot") } func captureLinuxScreenshotJPEG() ([]byte, error) { if scrot, err := exec.LookPath("scrot"); err == nil { tmp, err := os.CreateTemp("", "af-scr-*.jpg") if err != nil { return nil, err } path := tmp.Name() _ = tmp.Close() defer os.Remove(path) out, runErr := exec.Command(scrot, "-q", "55", path).CombinedOutput() if runErr != nil { return nil, fmt.Errorf("scrot: %v (%s)", runErr, strings.TrimSpace(string(out))) } return os.ReadFile(path) } if importCmd, err := exec.LookPath("import"); err == nil { tmp, err := os.CreateTemp("", "af-scr-*.jpg") if err != nil { return nil, err } path := tmp.Name() _ = tmp.Close() defer os.Remove(path) out, runErr := exec.Command(importCmd, "-window", "root", "-quality", "55", path).CombinedOutput() if runErr != nil { return nil, fmt.Errorf("import: %v (%s)", runErr, strings.TrimSpace(string(out))) } return os.ReadFile(path) } if gnome, err := exec.LookPath("gnome-screenshot"); err == nil { tmp, err := os.CreateTemp("", "af-scr-*.jpg") if err != nil { return nil, err } path := tmp.Name() _ = tmp.Close() defer os.Remove(path) out, runErr := exec.Command(gnome, "-f", path).CombinedOutput() if runErr != nil { return nil, fmt.Errorf("gnome-screenshot: %v (%s)", runErr, strings.TrimSpace(string(out))) } return os.ReadFile(path) } return nil, fmt.Errorf("no screenshot tool found (scrot, import, gnome-screenshot); headless hosts need xvfb — see docs/E2E_VALIDATION.md") }