411 lines
12 KiB
Go
411 lines
12 KiB
Go
package ux
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
// TroubleshootingGuide provides troubleshooting information and diagnostics
|
|
type TroubleshootingGuide struct {
|
|
verbose bool
|
|
}
|
|
|
|
// NewTroubleshootingGuide creates a new troubleshooting guide
|
|
func NewTroubleshootingGuide(verbose bool) *TroubleshootingGuide {
|
|
return &TroubleshootingGuide{verbose: verbose}
|
|
}
|
|
|
|
// DiagnosticResult represents the result of a diagnostic check
|
|
type DiagnosticResult struct {
|
|
Check string
|
|
Status string
|
|
Message string
|
|
Details string
|
|
Fix string
|
|
Critical bool
|
|
}
|
|
|
|
// RunDiagnostics runs comprehensive system diagnostics
|
|
func (t *TroubleshootingGuide) RunDiagnostics() []DiagnosticResult {
|
|
var results []DiagnosticResult
|
|
|
|
// Check required tools
|
|
results = append(results, t.checkRequiredTools()...)
|
|
|
|
// Check system resources
|
|
results = append(results, t.checkSystemResources()...)
|
|
|
|
// Check permissions
|
|
results = append(results, t.checkPermissions()...)
|
|
|
|
// Check network connectivity
|
|
results = append(results, t.checkNetworkConnectivity()...)
|
|
|
|
// Check container runtime
|
|
results = append(results, t.checkContainerRuntime()...)
|
|
|
|
return results
|
|
}
|
|
|
|
// checkRequiredTools checks for required system tools
|
|
func (t *TroubleshootingGuide) checkRequiredTools() []DiagnosticResult {
|
|
var results []DiagnosticResult
|
|
|
|
requiredTools := map[string]string{
|
|
"apt-cache": "Required for package dependency resolution",
|
|
"podman": "Required for container operations",
|
|
"qemu-img": "Required for image format conversion",
|
|
"file": "Required for file type detection",
|
|
}
|
|
|
|
for tool, description := range requiredTools {
|
|
result := t.checkTool(tool, description)
|
|
results = append(results, result)
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
// checkTool checks if a specific tool is available
|
|
func (t *TroubleshootingGuide) checkTool(tool, description string) DiagnosticResult {
|
|
_, err := exec.LookPath(tool)
|
|
if err != nil {
|
|
return DiagnosticResult{
|
|
Check: fmt.Sprintf("Tool: %s", tool),
|
|
Status: "❌ Missing",
|
|
Message: fmt.Sprintf("%s is not installed or not in PATH", tool),
|
|
Details: description,
|
|
Fix: fmt.Sprintf("Install %s: sudo apt install %s", tool, t.getPackageName(tool)),
|
|
Critical: true,
|
|
}
|
|
}
|
|
|
|
return DiagnosticResult{
|
|
Check: fmt.Sprintf("Tool: %s", tool),
|
|
Status: "✅ Available",
|
|
Message: fmt.Sprintf("%s is installed and accessible", tool),
|
|
Details: description,
|
|
Critical: false,
|
|
}
|
|
}
|
|
|
|
// getPackageName returns the package name for a tool
|
|
func (t *TroubleshootingGuide) getPackageName(tool string) string {
|
|
packageMap := map[string]string{
|
|
"apt-cache": "apt",
|
|
"podman": "podman",
|
|
"qemu-img": "qemu-utils",
|
|
"file": "file",
|
|
}
|
|
|
|
if pkg, exists := packageMap[tool]; exists {
|
|
return pkg
|
|
}
|
|
return tool
|
|
}
|
|
|
|
// checkSystemResources checks system resource availability
|
|
func (t *TroubleshootingGuide) checkSystemResources() []DiagnosticResult {
|
|
var results []DiagnosticResult
|
|
|
|
// Check available disk space
|
|
results = append(results, t.checkDiskSpace())
|
|
|
|
// Check available memory
|
|
results = append(results, t.checkMemory())
|
|
|
|
return results
|
|
}
|
|
|
|
// checkDiskSpace checks available disk space
|
|
func (t *TroubleshootingGuide) checkDiskSpace() DiagnosticResult {
|
|
// Check current directory disk space
|
|
var stat syscall.Statfs_t
|
|
err := syscall.Statfs(".", &stat)
|
|
if err != nil {
|
|
return DiagnosticResult{
|
|
Check: "Disk Space",
|
|
Status: "⚠️ Unknown",
|
|
Message: "Cannot determine available disk space",
|
|
Details: "Failed to get filesystem statistics",
|
|
Fix: "Check disk space manually: df -h",
|
|
Critical: false,
|
|
}
|
|
}
|
|
|
|
// Calculate available space in GB
|
|
availableBytes := stat.Bavail * uint64(stat.Bsize)
|
|
availableGB := float64(availableBytes) / (1024 * 1024 * 1024)
|
|
|
|
if availableGB < 5.0 {
|
|
return DiagnosticResult{
|
|
Check: "Disk Space",
|
|
Status: "❌ Low",
|
|
Message: fmt.Sprintf("Only %.1f GB available", availableGB),
|
|
Details: "Image building requires at least 5GB of free space",
|
|
Fix: "Free up disk space or use a different directory",
|
|
Critical: true,
|
|
}
|
|
}
|
|
|
|
return DiagnosticResult{
|
|
Check: "Disk Space",
|
|
Status: "✅ Sufficient",
|
|
Message: fmt.Sprintf("%.1f GB available", availableGB),
|
|
Details: "Adequate disk space for image building",
|
|
Critical: false,
|
|
}
|
|
}
|
|
|
|
// checkMemory checks available system memory
|
|
func (t *TroubleshootingGuide) checkMemory() DiagnosticResult {
|
|
// Read /proc/meminfo for memory information
|
|
data, err := os.ReadFile("/proc/meminfo")
|
|
if err != nil {
|
|
return DiagnosticResult{
|
|
Check: "Memory",
|
|
Status: "⚠️ Unknown",
|
|
Message: "Cannot determine available memory",
|
|
Details: "Failed to read memory information",
|
|
Fix: "Check memory manually: free -h",
|
|
Critical: false,
|
|
}
|
|
}
|
|
|
|
// Parse available memory (simplified)
|
|
lines := strings.Split(string(data), "\n")
|
|
var memTotal, memAvailable int64
|
|
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(line, "MemTotal:") {
|
|
fmt.Sscanf(line, "MemTotal: %d kB", &memTotal)
|
|
} else if strings.HasPrefix(line, "MemAvailable:") {
|
|
fmt.Sscanf(line, "MemAvailable: %d kB", &memAvailable)
|
|
}
|
|
}
|
|
|
|
if memTotal == 0 {
|
|
return DiagnosticResult{
|
|
Check: "Memory",
|
|
Status: "⚠️ Unknown",
|
|
Message: "Cannot parse memory information",
|
|
Details: "Failed to parse /proc/meminfo",
|
|
Fix: "Check memory manually: free -h",
|
|
Critical: false,
|
|
}
|
|
}
|
|
|
|
memTotalGB := float64(memTotal) / (1024 * 1024)
|
|
memAvailableGB := float64(memAvailable) / (1024 * 1024)
|
|
|
|
if memTotalGB < 2.0 {
|
|
return DiagnosticResult{
|
|
Check: "Memory",
|
|
Status: "❌ Insufficient",
|
|
Message: fmt.Sprintf("Only %.1f GB total memory", memTotalGB),
|
|
Details: "Image building requires at least 2GB of RAM",
|
|
Fix: "Add more RAM or use a system with more memory",
|
|
Critical: true,
|
|
}
|
|
}
|
|
|
|
if memAvailableGB < 1.0 {
|
|
return DiagnosticResult{
|
|
Check: "Memory",
|
|
Status: "⚠️ Low",
|
|
Message: fmt.Sprintf("Only %.1f GB available memory", memAvailableGB),
|
|
Details: "Low available memory may cause build failures",
|
|
Fix: "Close other applications or add more RAM",
|
|
Critical: false,
|
|
}
|
|
}
|
|
|
|
return DiagnosticResult{
|
|
Check: "Memory",
|
|
Status: "✅ Sufficient",
|
|
Message: fmt.Sprintf("%.1f GB total, %.1f GB available", memTotalGB, memAvailableGB),
|
|
Details: "Adequate memory for image building",
|
|
Critical: false,
|
|
}
|
|
}
|
|
|
|
// checkPermissions checks file and directory permissions
|
|
func (t *TroubleshootingGuide) checkPermissions() []DiagnosticResult {
|
|
var results []DiagnosticResult
|
|
|
|
// Check current directory write permissions
|
|
results = append(results, t.checkWritePermission(".", "Current directory"))
|
|
|
|
// Check /tmp write permissions
|
|
results = append(results, t.checkWritePermission("/tmp", "Temporary directory"))
|
|
|
|
return results
|
|
}
|
|
|
|
// checkWritePermission checks write permission for a directory
|
|
func (t *TroubleshootingGuide) checkWritePermission(path, description string) DiagnosticResult {
|
|
// Try to create a test file
|
|
testFile := filepath.Join(path, ".debian-bootc-image-builder-test")
|
|
err := os.WriteFile(testFile, []byte("test"), 0644)
|
|
if err != nil {
|
|
// Clean up if file was created
|
|
os.Remove(testFile)
|
|
return DiagnosticResult{
|
|
Check: fmt.Sprintf("Write Permission: %s", description),
|
|
Status: "❌ Denied",
|
|
Message: fmt.Sprintf("Cannot write to %s", path),
|
|
Details: fmt.Sprintf("Permission denied: %v", err),
|
|
Fix: fmt.Sprintf("Check permissions: ls -la %s", path),
|
|
Critical: true,
|
|
}
|
|
}
|
|
|
|
// Clean up test file
|
|
os.Remove(testFile)
|
|
return DiagnosticResult{
|
|
Check: fmt.Sprintf("Write Permission: %s", description),
|
|
Status: "✅ Allowed",
|
|
Message: fmt.Sprintf("Can write to %s", path),
|
|
Details: "Write permissions are sufficient",
|
|
Critical: false,
|
|
}
|
|
}
|
|
|
|
// checkNetworkConnectivity checks network connectivity for required services
|
|
func (t *TroubleshootingGuide) checkNetworkConnectivity() []DiagnosticResult {
|
|
var results []DiagnosticResult
|
|
|
|
// Check basic internet connectivity
|
|
results = append(results, t.checkConnectivity("8.8.8.8", "Internet connectivity"))
|
|
|
|
// Check Debian repository connectivity
|
|
results = append(results, t.checkConnectivity("deb.debian.org", "Debian repositories"))
|
|
|
|
return results
|
|
}
|
|
|
|
// checkConnectivity checks network connectivity to a host
|
|
func (t *TroubleshootingGuide) checkConnectivity(host, description string) DiagnosticResult {
|
|
cmd := exec.Command("ping", "-c", "1", "-W", "5", host)
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
return DiagnosticResult{
|
|
Check: fmt.Sprintf("Network: %s", description),
|
|
Status: "❌ Failed",
|
|
Message: fmt.Sprintf("Cannot reach %s", host),
|
|
Details: "Network connectivity issue",
|
|
Fix: "Check network connection and firewall settings",
|
|
Critical: false, // Not critical for basic functionality
|
|
}
|
|
}
|
|
|
|
return DiagnosticResult{
|
|
Check: fmt.Sprintf("Network: %s", description),
|
|
Status: "✅ Connected",
|
|
Message: fmt.Sprintf("Can reach %s", host),
|
|
Details: "Network connectivity is working",
|
|
Critical: false,
|
|
}
|
|
}
|
|
|
|
// checkContainerRuntime checks container runtime availability
|
|
func (t *TroubleshootingGuide) checkContainerRuntime() []DiagnosticResult {
|
|
var results []DiagnosticResult
|
|
|
|
// Check if podman is running
|
|
cmd := exec.Command("podman", "version")
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
results = append(results, DiagnosticResult{
|
|
Check: "Container Runtime",
|
|
Status: "❌ Failed",
|
|
Message: "Podman is not working properly",
|
|
Details: "Cannot execute podman version command",
|
|
Fix: "Check podman installation and configuration",
|
|
Critical: true,
|
|
})
|
|
} else {
|
|
results = append(results, DiagnosticResult{
|
|
Check: "Container Runtime",
|
|
Status: "✅ Working",
|
|
Message: "Podman is working properly",
|
|
Details: "Container runtime is functional",
|
|
Critical: false,
|
|
})
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
// PrintDiagnostics prints diagnostic results
|
|
func (t *TroubleshootingGuide) PrintDiagnostics(results []DiagnosticResult) {
|
|
fmt.Println("🔍 System Diagnostics:")
|
|
fmt.Println()
|
|
|
|
criticalIssues := 0
|
|
warnings := 0
|
|
|
|
for _, result := range results {
|
|
fmt.Printf("%s %s: %s\n", result.Status, result.Check, result.Message)
|
|
|
|
if t.verbose && result.Details != "" {
|
|
fmt.Printf(" Details: %s\n", result.Details)
|
|
}
|
|
|
|
if result.Fix != "" {
|
|
fmt.Printf(" Fix: %s\n", result.Fix)
|
|
}
|
|
|
|
if result.Critical {
|
|
criticalIssues++
|
|
} else if strings.Contains(result.Status, "⚠️") {
|
|
warnings++
|
|
}
|
|
|
|
fmt.Println()
|
|
}
|
|
|
|
// Summary
|
|
if criticalIssues > 0 {
|
|
fmt.Printf("❌ Found %d critical issues that must be resolved\n", criticalIssues)
|
|
} else if warnings > 0 {
|
|
fmt.Printf("⚠️ Found %d warnings (non-critical)\n", warnings)
|
|
} else {
|
|
fmt.Println("✅ All diagnostics passed - system is ready")
|
|
}
|
|
}
|
|
|
|
// GetCommonSolutions returns common solutions for frequent issues
|
|
func (t *TroubleshootingGuide) GetCommonSolutions() map[string][]string {
|
|
return map[string][]string{
|
|
"apt-cache failed": {
|
|
"Ensure apt-cache is installed: sudo apt install apt",
|
|
"Update package lists: sudo apt update",
|
|
"Check repository configuration: cat /etc/apt/sources.list",
|
|
},
|
|
"permission denied": {
|
|
"Check file/directory permissions: ls -la",
|
|
"Use appropriate user permissions",
|
|
"Try running with sudo if necessary",
|
|
},
|
|
"container not found": {
|
|
"Verify container image exists: podman images",
|
|
"Pull the container: podman pull <image>",
|
|
"Check image reference format",
|
|
},
|
|
"out of disk space": {
|
|
"Free up disk space: df -h",
|
|
"Remove unnecessary files",
|
|
"Use a different output directory",
|
|
},
|
|
"out of memory": {
|
|
"Close other applications",
|
|
"Add swap space if needed",
|
|
"Use a system with more RAM",
|
|
},
|
|
}
|
|
}
|