// Command rebreak-supervise-magic ist die CLI für unsupervised→supervised // Migration ohne Datenverlust via Apple MCInstall-Service. package main import ( "bufio" "flag" "fmt" "os" "sort" "strings" "time" ios "github.com/danielpaulus/go-ios/ios" "github.com/raynis/rebreak-supervise-magic/internal/cert" "github.com/raynis/rebreak-supervise-magic/internal/device" "github.com/raynis/rebreak-supervise-magic/internal/dlmessage" "github.com/raynis/rebreak-supervise-magic/internal/mcinstall" "github.com/raynis/rebreak-supervise-magic/internal/preflight" "github.com/raynis/rebreak-supervise-magic/internal/supervise" ) func init() { // Wire authoritative IsSupervised-check via MCInstall.GetCloudConfiguration. device.CheckSupervisedFunc = func(dev ios.DeviceEntry) (bool, error) { mc, err := mcinstall.Open(dev) if err != nil { return false, nil // can't check → assume unsupervised } defer mc.Close() _ = mc.HelloHostIdentifier() cfg, err := mc.GetCloudConfiguration() if err != nil || cfg == nil { return false, nil } v, _ := cfg["IsSupervised"].(bool) return v, nil } } func dlmsgDebugSet() { dlmessage.DebugMode = true } const usage = `rebreak-supervise-magic — iPhone/iPad-Supervision ohne Datenverlust Usage: rebreak-supervise-magic [flags] Commands: check Print device info + supervision status. No writes. cert-info Print supervision-cert path + status. No writes. cloud-config Read current Cloud-Configuration via MCInstall. No writes. supervise SetCloudConfiguration(IsSupervised=true) + reboot. unsupervise Reverse-flow — IsSupervised=false + reboot. Global flags: -v Verbose / debug output. -udid Target specific device (default: first detected). -org OrganizationName shown in Settings (default: "ReBreak"). -dry-run Run all checks + plan but skip writes/reboot. -yes Skip confirmation prompt (use in scripts). -force Allow supervise on already-supervised devices (re-supervise / change orgname). Examples: rebreak-supervise-magic check rebreak-supervise-magic cloud-config rebreak-supervise-magic supervise -org "ReBreak" -yes rebreak-supervise-magic -v -dry-run supervise ` type cliOpts struct { verbose bool udid string orgName string dryRun bool yes bool force bool } func main() { if len(os.Args) < 2 { fmt.Fprint(os.Stderr, usage) os.Exit(2) } fs := flag.NewFlagSet("rebreak-supervise-magic", flag.ExitOnError) opts := &cliOpts{} fs.BoolVar(&opts.verbose, "v", false, "verbose output") fs.StringVar(&opts.udid, "udid", "", "target UDID (default: first device)") fs.StringVar(&opts.orgName, "org", "ReBreak", "OrganizationName") fs.BoolVar(&opts.dryRun, "dry-run", false, "skip writes/reboot") fs.BoolVar(&opts.yes, "yes", false, "skip confirm prompt") fs.BoolVar(&opts.force, "force", false, "allow supervise on already-supervised devices") // Optional DLMessage debug-mode via env if os.Getenv("REBREAK_DLMSG_DEBUG") == "1" { dlmsgDebugSet() } if err := fs.Parse(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, "flag parse error:", err) os.Exit(2) } positional := fs.Args() if len(positional) == 0 { fmt.Fprint(os.Stderr, "missing command\n\n") fmt.Fprint(os.Stderr, usage) os.Exit(2) } cmd := positional[0] switch cmd { case "check": exitOnErr(runCheck(opts)) case "cert-info": exitOnErr(runCertInfo(opts)) case "cloud-config": exitOnErr(runCloudConfig(opts)) case "supervise": exitOnErr(runSupervise(opts)) case "unsupervise": exitOnErr(runUnsupervise(opts)) case "-h", "--help", "help": fmt.Print(usage) default: fmt.Fprintf(os.Stderr, "unknown command: %s\n\n", cmd) fmt.Fprint(os.Stderr, usage) os.Exit(2) } } func runCheck(opts *cliOpts) error { conn, err := device.Connect(opts.udid) if err != nil { return err } defer conn.Close() res, err := preflight.Run(conn) if err != nil { return err } fmt.Println("Device:") fmt.Printf(" UDID: %s\n", res.Device.UDID) fmt.Printf(" Name: %s\n", res.Device.DeviceName) fmt.Printf(" Type: %s\n", res.Device.ProductType) fmt.Printf(" iOS: %s\n", res.Device.ProductVersion) fmt.Printf(" ActivationState: %s\n", res.Device.ActivationState) fmt.Printf(" FindMyEnabled: %v\n", res.Device.FindMyEnabled) fmt.Printf(" IsSupervised: %v\n", res.Device.IsSupervised) fmt.Println() if res.OK { fmt.Println("Pre-Flight: ✓ ready for supervise") } else { fmt.Println("Pre-Flight: ✗ not ready:") for _, r := range res.Reasons { fmt.Printf(" - %s\n", r) } return fmt.Errorf("pre-flight failed") } return nil } func runCertInfo(opts *cliOpts) error { dir := cert.DefaultDir() fmt.Printf("Supervision identity directory: %s\n", dir) id, err := cert.LoadOrCreate() if err != nil { return err } fmt.Printf(" Cert (DER): %d bytes\n", len(id.CertDER)) fmt.Printf(" Key (DER): %d bytes\n", len(id.PrivateKeyDER)) fmt.Println() fmt.Println("Identity ready — usable for supervise command.") return nil } func runCloudConfig(opts *cliOpts) error { conn, err := device.Connect(opts.udid) if err != nil { return err } defer conn.Close() mc, err := mcinstall.Open(conn.Device()) if err != nil { return err } defer mc.Close() if err := mc.HelloHostIdentifier(); err != nil { return err } cfg, err := mc.GetCloudConfiguration() if err != nil { return err } if cfg == nil { fmt.Println("(no cloud configuration set on device)") return nil } keys := make([]string, 0, len(cfg)) for k := range cfg { keys = append(keys, k) } sort.Strings(keys) fmt.Println("Cloud Configuration (live from device MCInstall):") for _, k := range keys { val := cfg[k] switch v := val.(type) { case []byte: fmt.Printf(" %-40s = [%d bytes binary]\n", k, len(v)) case [][]byte: fmt.Printf(" %-40s = [%d certs]\n", k, len(v)) default: fmt.Printf(" %-40s = %v\n", k, v) } } return nil } func runSupervise(opts *cliOpts) error { conn, err := device.Connect(opts.udid) if err != nil { return err } res, err := preflight.Run(conn) conn.Close() if err != nil { return err } if !res.OK { fmt.Println("Pre-Flight failed:") for _, r := range res.Reasons { fmt.Printf(" - %s\n", r) } return fmt.Errorf("pre-flight blocking supervise") } if res.Device.IsSupervised && !opts.force { fmt.Printf("Device already supervised (UDID=%s). Use --force to re-supervise (overwrites existing supervisor cert).\n", res.Device.UDID) return nil } fmt.Printf("About to supervise device:\n %s (%s, iOS %s)\n", res.Device.DeviceName, res.Device.ProductType, res.Device.ProductVersion) if res.Device.IsSupervised { fmt.Println("⚠ Device IS already supervised — will OVERWRITE existing supervisor cert with our own.") fmt.Println(" Existing cert cannot be restored without external tool (TechLockdown / Apple Configurator).") } fmt.Printf("OrganizationName: %s\n", opts.orgName) fmt.Println("Device will reboot. No data loss expected.") if !opts.yes && !opts.dryRun { if !confirm("Continue? [y/N]: ") { return fmt.Errorf("aborted by user") } } return supervise.Supervise(res.Device.UDID, supervise.Options{ OrgName: opts.orgName, DryRun: opts.dryRun, Verbose: opts.verbose, BackupPath: defaultBackupPath(res.Device.UDID), }) } func defaultBackupPath(udid string) string { short := udid if len(short) > 12 { short = short[:12] } ts := time.Now().UTC().Format("20060102-150405") return fmt.Sprintf("/tmp/rebreak-supervise-backup-%s-%s.json", short, ts) } func runUnsupervise(opts *cliOpts) error { conn, err := device.Connect(opts.udid) if err != nil { return err } res, err := preflight.Run(conn) conn.Close() if err != nil { return err } if !res.Device.IsSupervised { fmt.Printf("Device already un-supervised (UDID=%s). Nothing to do.\n", res.Device.UDID) return nil } fmt.Printf("About to UN-supervise device:\n %s (%s, iOS %s)\n", res.Device.DeviceName, res.Device.ProductType, res.Device.ProductVersion) if !opts.yes && !opts.dryRun { if !confirm("Continue? [y/N]: ") { return fmt.Errorf("aborted by user") } } return supervise.Unsupervise(res.Device.UDID, supervise.Options{ OrgName: opts.orgName, DryRun: opts.dryRun, Verbose: opts.verbose, }) } func confirm(prompt string) bool { fmt.Print(prompt) reader := bufio.NewReader(os.Stdin) line, _ := reader.ReadString('\n') line = strings.TrimSpace(strings.ToLower(line)) return line == "y" || line == "yes" } func exitOnErr(err error) { if err == nil { return } if !strings.HasPrefix(err.Error(), "aborted") { fmt.Fprintln(os.Stderr, "ERROR:", err) } os.Exit(1) }