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:
parent
bd2626b41e
commit
713b4ef564
@ -150,6 +150,11 @@ footer a, footer a:visited {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
/* Baseline Material error color */
|
||||
color: #b00020;
|
||||
}
|
||||
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
|
@ -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
@ -26,8 +26,12 @@
|
||||
<link rel="stylesheet" href="/grapheneos.css?29"/>
|
||||
<link rel="manifest" href="/manifest.webmanifest"/>
|
||||
<link rel="license" href="/LICENSE.txt"/>
|
||||
<script defer="defer" src="/js/webadb.js?0"></script>
|
||||
<script defer="defer" src="/js/web-install.js?1"></script>
|
||||
<script defer="defer" src="/js/fastboot/libs/zip.min.js?0"></script>
|
||||
<script type="module" src="/js/fastboot/common.js?0"></script>
|
||||
<script type="module" src="/js/fastboot/factory.js?0"></script>
|
||||
<script type="module" src="/js/fastboot/sparse.js?0"></script>
|
||||
<script type="module" src="/js/fastboot/fastboot.js?0"></script>
|
||||
<script type="module" src="/js/web-install.js?2"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@ -129,11 +133,13 @@
|
||||
|
||||
<p>Unlock the bootloader to allow flashing the OS and firmware:</p>
|
||||
|
||||
<button id="unlock-bootloader" disabled="disabled">Unlock bootloader</button>
|
||||
<button id="unlock-bootloader-button" disabled="disabled">Unlock bootloader</button>
|
||||
|
||||
<p>The command needs to be confirmed on the device and will wipe all data. Use one
|
||||
of the volume keys to switch the selection to accepting it and the power button to
|
||||
confirm.</p>
|
||||
|
||||
<p><strong id="unlock-bootloader-status"></strong></p>
|
||||
</section>
|
||||
|
||||
<section id="obtaining-factory-images">
|
||||
@ -144,9 +150,9 @@
|
||||
|
||||
<p>Press the button below to start the download:</p>
|
||||
|
||||
<button id="download-release" disabled="disabled">Download release</button>
|
||||
<button id="download-release-button" disabled="disabled">Download release</button>
|
||||
|
||||
<p><strong>Download not implemented yet.</strong></p>
|
||||
<p><strong id="download-release-status"></strong></p>
|
||||
</section>
|
||||
|
||||
<section id="flashing-factory-images">
|
||||
@ -155,14 +161,13 @@
|
||||
<p>The initial install will be performed by flashing the factory images. This will
|
||||
replace the existing OS installation and wipe all the existing data.</p>
|
||||
|
||||
<p><strong>Needs to be implemented based on extracting and flashing images from
|
||||
the downloaded zip (or a split approach to downloading files).</strong></p>
|
||||
|
||||
<button id="flash-release" disabled="disabled">Flash release</button>
|
||||
<button id="flash-release-button" disabled="disabled">Flash release</button>
|
||||
|
||||
<p>Wait for the flashing process to complete and proceed to
|
||||
<a href="#locking-the-bootloader">locking the bootloader</a> before using the
|
||||
device as locking wipes the data again.</p>
|
||||
|
||||
<p><strong id="flash-release-status"></strong></p>
|
||||
</section>
|
||||
|
||||
<section id="locking-the-bootloader">
|
||||
@ -177,11 +182,13 @@
|
||||
|
||||
<p>In the bootloader interface, set it to locked:</p>
|
||||
|
||||
<button id="lock-bootloader" disabled="disabled">Lock bootloader</button>
|
||||
<button id="lock-bootloader-button" disabled="disabled">Lock bootloader</button>
|
||||
|
||||
<p>The command needs to be confirmed on the device and will wipe all data. Use one
|
||||
of the volume buttons to switch the selection to accepting it and the power button
|
||||
to confirm.</p>
|
||||
|
||||
<p><strong id="lock-bootloader-status"></strong></p>
|
||||
</section>
|
||||
|
||||
<section id="post-installation">
|
||||
|
Loading…
x
Reference in New Issue
Block a user