From b24a5bb612767b759445b587a839d24e3e844f56 Mon Sep 17 00:00:00 2001 From: Aleks Rutins Date: Mon, 9 Jun 2025 13:32:53 -0400 Subject: [PATCH] Add bulk wipe --- gum.js | 21 ++++++++++++++++ ipadtool.sh | 5 ++-- mosyle.js | 25 ++++++++++++++++++ wipe.sh => wipe | 0 wipebulk | 3 +++ wipebulk.js | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 gum.js create mode 100644 mosyle.js rename wipe.sh => wipe (100%) create mode 100644 wipebulk create mode 100644 wipebulk.js diff --git a/gum.js b/gum.js new file mode 100644 index 0000000..6ced40c --- /dev/null +++ b/gum.js @@ -0,0 +1,21 @@ +import { spawn } from "child_process"; + +export function gum(...args) { + return new Promise((res, rej) => { + const gum = spawn("gum", args, { + env: { ...process.env, TERM: "xterm-256color" }, + }); + + gum.stderr.pipe(process.stderr); + + gum.stdout.on("data", (data) => { + res(data.toString()); + }); + + gum.on("exit", res); + }); +} + +export function info(msg) { + return gum("log", "-l", "info", msg); +} diff --git a/ipadtool.sh b/ipadtool.sh index 287e89d..686a241 100755 --- a/ipadtool.sh +++ b/ipadtool.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash while true; do - cmd=$(gum choose wipe exit) + cmd=$(gum choose wipe wipebulk exit) case $cmd in exit) exit;; - *) source ./$cmd.sh;; + "") exit;; + *) source ./$cmd;; esac done diff --git a/mosyle.js b/mosyle.js new file mode 100644 index 0000000..0c39e6f --- /dev/null +++ b/mosyle.js @@ -0,0 +1,25 @@ +export function call(endpoint, data, auth) { + const headers = { + "Content-Type": "application/json", + }; + if (auth) headers.Authorization = `Bearer ${auth}`; + return fetch("https://managerapi.mosyle.com/v2" + endpoint, { + method: "POST", + headers, + body: JSON.stringify(data), + }); +} + +export async function authenticate(email, password, token) { + const response = await call("/login", { + email, + password, + accessToken: token, + }); + if (!response.ok) throw new Error("Authentication failed"); + return response.headers.get("Authorization").replace(/^Bearer\s/, ""); +} + +export function bulk(data, auth) { + return call("/bulkops", data, auth); +} diff --git a/wipe.sh b/wipe similarity index 100% rename from wipe.sh rename to wipe diff --git a/wipebulk b/wipebulk new file mode 100644 index 0000000..9a83d98 --- /dev/null +++ b/wipebulk @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +gum log "Enter your serial numbers below, one per line." +gum write --placeholder "FXN234342G" | node wipebulk.js diff --git a/wipebulk.js b/wipebulk.js new file mode 100644 index 0000000..31dd954 --- /dev/null +++ b/wipebulk.js @@ -0,0 +1,67 @@ +#!/usr/bin/env node +import fs from "fs"; +import { info } from "./gum.js"; +import { authenticate, bulk, call } from "./mosyle.js"; + +let serialsTxt = ""; +for await (const chunk of process.stdin) { + serialsTxt += chunk; +} + +const serials = serialsTxt.split("\n").filter(Boolean); + +const accessToken = process.env.MOSYLE_TOKEN; + +info("Authenticating..."); +const token = await authenticate( + process.env.MOSYLE_USER, + process.env.MOSYLE_PASSWORD, + accessToken, +); + +info("Getting devices..."); +const devices = await call( + "/listdevices", + { accessToken, serial_number: serials, options: { os: "ios" } }, + token, +) + .then((res) => res.json()) + .then((j) => + j.status == "OK" + ? j.response.devices.map((d) => d.deviceudid) + : console.error(j), + ); + +info("Changing to limbo and wiping..."); +console.log( + await ( + await bulk( + { + accessToken, + elements: [ + { + operation: "change_to_limbo", + devices, + }, + { + operation: "wipe_devices", + devices, + options: { + PreserveDataPlan: "true", + DisallowProximitySetup: "true", + RevokeVPPLicenses: "true", + EnableReturnToService: "true", + }, + }, + ], + }, + token, + ) + ).text(), +); + +await info( + "To do more, paste this comma-separated list of serial numbers into the Mosyle search box:", +); + +console.log(serials.join(","));