// Package preflight prüft vor dem Supervise-Flow: // - iPhone via USB erreichbar (über device.Connect) // - ProductType iPhone/iPad (kein Mac, kein Apple-TV) // - iOS-Version >= 16 // - ActivationState=Activated // - Find-My-iPhone aus (via NonVolatileRAM-Parse) // - IsSupervised-Status anzeigen (kein Hard-Fail bei true — caller entscheidet) package preflight import ( "fmt" "strconv" "strings" "github.com/raynis/rebreak-supervise-magic/internal/device" ) type Result struct { OK bool Reasons []string Device DeviceInfo } type DeviceInfo struct { UDID string DeviceName string ProductType string ProductVersion string ActivationState string FindMyEnabled bool IsSupervised bool } func Run(conn *device.Conn) (*Result, error) { res := &Result{OK: true} info, err := conn.Info() if err != nil { return nil, fmt.Errorf("preflight: device info: %w", err) } res.Device.UDID = conn.UDID() res.Device.DeviceName = asString(info["DeviceName"]) res.Device.ProductType = asString(info["ProductType"]) res.Device.ProductVersion = asString(info["ProductVersion"]) res.Device.ActivationState = asString(info["ActivationState"]) if supervised, err := conn.IsSupervised(); err == nil { res.Device.IsSupervised = supervised } if fmi, err := conn.FindMyEnabled(); err == nil { res.Device.FindMyEnabled = fmi } if res.Device.FindMyEnabled { res.OK = false res.Reasons = append(res.Reasons, "Find My iPhone is ON — disable in Settings → [Name] → Wo ist? → Mein iPhone suchen → AUS") } if !strings.HasPrefix(res.Device.ProductType, "iPhone") && !strings.HasPrefix(res.Device.ProductType, "iPad") { res.OK = false res.Reasons = append(res.Reasons, fmt.Sprintf("ProductType '%s' not supported — only iPhone/iPad. Mac uses different stack (NanoMDM enrollment).", res.Device.ProductType)) } if !checkIOSVersionAtLeast(res.Device.ProductVersion, 16) { res.OK = false res.Reasons = append(res.Reasons, fmt.Sprintf("OS-Version '%s' too low. Need iOS 16+ for Cloud-Config-Plist-Path.", res.Device.ProductVersion)) } if res.Device.ActivationState != "" && res.Device.ActivationState != "Activated" { res.OK = false res.Reasons = append(res.Reasons, fmt.Sprintf("ActivationState '%s' — device must be activated.", res.Device.ActivationState)) } if len(res.Reasons) > 0 { res.OK = false } return res, nil } func asString(v any) string { if s, ok := v.(string); ok { return s } return "" } func checkIOSVersionAtLeast(version string, minMajor int) bool { if version == "" { return true } parts := strings.SplitN(version, ".", 2) if len(parts) == 0 { return true } major, err := strconv.Atoi(parts[0]) if err != nil { return true } return major >= minMajor }