Implement web install using fastboot.js
This implements the WebUSB-based web installer using fastboot.js to act as a fastboot client. A bare minimum UI with a plain-text status/progress caption for each step is included, as well as a plain button to trigger it and basic error handling. WebADB has been removed now that we are only using fastboot.js. Initial features: - Unlocking and locking the bootloader - Downloading the latest GrapheneOS release available for the device - Flashing the factory images zip - Reusing USB connections
This commit is contained in:
@@ -1,77 +1,102 @@
|
||||
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT
|
||||
|
||||
async function unlockBootloader() {
|
||||
const webusb = await Adb.open("WebUSB");
|
||||
import { FastbootDevice } from "./fastboot/fastboot.js";
|
||||
import * as Factory from "./fastboot/factory.js";
|
||||
|
||||
if (!webusb.isFastboot()) {
|
||||
console.log("error: not in fastboot mode");
|
||||
const RELEASES_URL = "https://releases.grapheneos.org";
|
||||
|
||||
let device = new FastbootDevice();
|
||||
let lastReleaseZip = null;
|
||||
|
||||
async function ensureConnected(setProgress) {
|
||||
console.log(device.device);
|
||||
if (!device.isConnected) {
|
||||
setProgress("Connecting to device...");
|
||||
await device.connect();
|
||||
}
|
||||
|
||||
console.log("connecting with fastboot");
|
||||
|
||||
const fastboot = await webusb.connectFastboot();
|
||||
await fastboot.send("flashing unlock");
|
||||
await fastboot.receive();
|
||||
}
|
||||
|
||||
async function downloadRelease() {
|
||||
const webusb = await Adb.open("WebUSB");
|
||||
async function unlockBootloader(setProgress) {
|
||||
await ensureConnected(setProgress);
|
||||
|
||||
if (!webusb.isFastboot()) {
|
||||
console.log("error: not in fastboot mode");
|
||||
}
|
||||
|
||||
console.log("connecting with fastboot");
|
||||
|
||||
const fastboot = await webusb.connectFastboot();
|
||||
await fastboot.send("getvar:product");
|
||||
const response = await fastboot.receive();
|
||||
if (fastboot.get_cmd(response) == "FAIL") {
|
||||
throw new Error("getvar product failed");
|
||||
}
|
||||
const decoder = new TextDecoder();
|
||||
const product = decoder.decode(fastboot.get_payload(response));
|
||||
|
||||
const baseUrl = "https://releases.grapheneos.org/";
|
||||
const metadata = await (await fetch(baseUrl + product + "-stable")).text();
|
||||
const buildNumber = metadata.split(" ")[0];
|
||||
const downloadUrl = baseUrl + product + "-factory-" + buildNumber + ".zip";
|
||||
console.log(downloadUrl);
|
||||
|
||||
// Download factory images
|
||||
//
|
||||
// Need to do this in a way that works well with huge files, particularly since the zip needs
|
||||
// to be extracted. Could potentially split it up on the server if this works out badly.
|
||||
setProgress("Unlocking bootloader...");
|
||||
await device.runCommand("flashing unlock");
|
||||
return "Bootloader unlocked.";
|
||||
}
|
||||
|
||||
async function lockBootloader() {
|
||||
const webusb = await Adb.open("WebUSB");
|
||||
async function downloadRelease(setProgress) {
|
||||
await ensureConnected(setProgress);
|
||||
|
||||
if (!webusb.isFastboot()) {
|
||||
console.log("error: not in fastboot mode");
|
||||
}
|
||||
setProgress("Getting device model...");
|
||||
let product = await device.getVariable("product");
|
||||
setProgress("Finding latest release...");
|
||||
let metadataResp = await fetch(`${RELEASES_URL}/${product}-stable`);
|
||||
let metadata = await metadataResp.text();
|
||||
let releaseId = metadata.split(" ")[0];
|
||||
|
||||
console.log("connecting with fastboot");
|
||||
|
||||
const fastboot = await webusb.connectFastboot();
|
||||
await fastboot.send("flashing lock");
|
||||
await fastboot.receive();
|
||||
// Download and cache the zip as a blob
|
||||
setProgress(`Downloading ${releaseId} release for ${product}...`);
|
||||
lastReleaseZip = `${product}-factory-${releaseId}.zip`;
|
||||
await Factory.downloadZip(`${RELEASES_URL}/${lastReleaseZip}`);
|
||||
return `Downloaded ${releaseId} release for ${product}.`;
|
||||
}
|
||||
|
||||
async function flashRelease(setProgress) {
|
||||
await ensureConnected(setProgress);
|
||||
|
||||
setProgress("Flashing release...");
|
||||
await Factory.flashZip(device, lastReleaseZip, (action, partition) => {
|
||||
let userPartition = partition == "avb_custom_key" ? "verified boot key" : partition;
|
||||
if (action == "unpack") {
|
||||
setProgress(`Unpacking image: ${userPartition}`);
|
||||
} else {
|
||||
setProgress(`Flashing image: ${userPartition}`);
|
||||
}
|
||||
});
|
||||
return `Flashed ${lastReleaseZip} to device.`;
|
||||
}
|
||||
|
||||
async function lockBootloader(setProgress) {
|
||||
await ensureConnected(setProgress);
|
||||
|
||||
setProgress("Locking bootloader...");
|
||||
await device.runCommand("flashing lock");
|
||||
return "Bootloader locked.";
|
||||
}
|
||||
|
||||
function addButtonHook(id, callback) {
|
||||
let statusField = document.getElementById(`${id}-status`);
|
||||
let statusCallback = (status) => {
|
||||
statusField.textContent = status;
|
||||
statusField.className = "";
|
||||
};
|
||||
|
||||
let button = document.getElementById(`${id}-button`);
|
||||
button.disabled = false;
|
||||
button.onclick = async () => {
|
||||
try {
|
||||
let finalStatus = await callback(statusCallback);
|
||||
statusCallback(finalStatus);
|
||||
} catch (error) {
|
||||
statusCallback(`Error: ${error.message}`);
|
||||
statusField.className = "error-text";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// zip.js is loaded separately.
|
||||
// eslint-disable-next-line no-undef
|
||||
zip.configure({
|
||||
workerScriptsPath: "/js/fastboot/libs/",
|
||||
});
|
||||
|
||||
if ("usb" in navigator) {
|
||||
console.log("WebUSB available");
|
||||
|
||||
const unlockBootloaderButton = document.getElementById("unlock-bootloader");
|
||||
unlockBootloaderButton.disabled = false;
|
||||
unlockBootloaderButton.onclick = unlockBootloader;
|
||||
|
||||
const downloadReleaseButton = document.getElementById("download-release");
|
||||
downloadReleaseButton.disabled = false;
|
||||
downloadReleaseButton.onclick = downloadRelease;
|
||||
|
||||
const lockBootloaderButton = document.getElementById("lock-bootloader");
|
||||
lockBootloaderButton.disabled = false;
|
||||
lockBootloaderButton.onclick = lockBootloader;
|
||||
addButtonHook("unlock-bootloader", unlockBootloader);
|
||||
addButtonHook("download-release", downloadRelease);
|
||||
addButtonHook("flash-release", flashRelease);
|
||||
addButtonHook("lock-bootloader", lockBootloader);
|
||||
} else {
|
||||
console.log("WebUSB unavailable");
|
||||
}
|
||||
|
||||
1055
static/js/webadb.js
1055
static/js/webadb.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user