1
0

Add fourth install step

This commit is contained in:
2026-03-16 14:21:21 +01:00
parent 8dde08b494
commit 3b5faf9696
7 changed files with 123 additions and 27 deletions

View File

@@ -5,8 +5,6 @@
A web application that provides a GUI for applying custom [kobopatch](https://github.com/pgaskin/kobopatch) patches to Kobo e-readers. It uses the File System Access API (Chromium) to interface with connected Kobo devices, or falls back to manual model/firmware selection on other browsers. A web application that provides a GUI for applying custom [kobopatch](https://github.com/pgaskin/kobopatch) patches to Kobo e-readers. It uses the File System Access API (Chromium) to interface with connected Kobo devices, or falls back to manual model/firmware selection on other browsers.
![A screenshot of the prototype of the web UI.](./screenshot.png)
The app makes it easy to configure which patches to apply, downloads the correct firmware from Kobo's servers, runs the patcher (compiled to WebAssembly), and places the resulting `KoboRoot.tgz` on the device. The user then reboots to apply. The app makes it easy to configure which patches to apply, downloads the correct firmware from Kobo's servers, runs the patcher (compiled to WebAssembly), and places the resulting `KoboRoot.tgz` on the device. The user then reboots to apply.
Fully client-side — no backend needed, can be hosted as a static site. Patches are community-contributed via [MobileRead](https://www.mobileread.com/) and need to be manually updated when new Kobo firmware versions come out. Fully client-side — no backend needed, can be hosted as a static site. Patches are community-contributed via [MobileRead](https://www.mobileread.com/) and need to be manually updated when new Kobo firmware versions come out.

View File

@@ -23,8 +23,10 @@ echo "Copying artifacts to $PUBLIC_DIR..."
cp kobopatch.wasm "$PUBLIC_DIR/kobopatch.wasm" cp kobopatch.wasm "$PUBLIC_DIR/kobopatch.wasm"
cp wasm_exec.js "$PUBLIC_DIR/wasm_exec.js" cp wasm_exec.js "$PUBLIC_DIR/wasm_exec.js"
# Update the cache-busting timestamp in the worker # Update cache-busting timestamps
sed -i "s|kobopatch\.wasm?ts=[0-9]*|kobopatch.wasm?ts=$TS|g" "$PUBLIC_DIR/patch-worker.js" sed -i "s|kobopatch\.wasm?ts=[0-9]*|kobopatch.wasm?ts=$TS|g" "$PUBLIC_DIR/patch-worker.js"
sed -i "s|\.js?ts=[0-9]*|.js?ts=$TS|g" "$PUBLIC_DIR/index.html"
sed -i "s|\.css?ts=[0-9]*|.css?ts=$TS|g" "$PUBLIC_DIR/index.html"
echo "Build timestamp: $TS" echo "Build timestamp: $TS"
echo "Done." echo "Done."

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -41,7 +41,8 @@
const deviceStatus = document.getElementById('device-status'); const deviceStatus = document.getElementById('device-status');
const patchContainer = document.getElementById('patch-container'); const patchContainer = document.getElementById('patch-container');
const buildStatus = document.getElementById('build-status'); const buildStatus = document.getElementById('build-status');
const writeSuccess = document.getElementById('write-success'); const writeInstructions = document.getElementById('write-instructions');
const downloadInstructions = document.getElementById('download-instructions');
const firmwareVersionLabel = document.getElementById('firmware-version-label'); const firmwareVersionLabel = document.getElementById('firmware-version-label');
const firmwareDeviceLabel = document.getElementById('firmware-device-label'); const firmwareDeviceLabel = document.getElementById('firmware-device-label');
const patchCountHint = document.getElementById('patch-count-hint'); const patchCountHint = document.getElementById('patch-count-hint');
@@ -306,7 +307,6 @@
} }
btnBuild.addEventListener('click', async () => { btnBuild.addEventListener('click', async () => {
hideNav();
showStep(stepBuilding); showStep(stepBuilding);
buildLog.textContent = ''; buildLog.textContent = '';
buildProgress.textContent = 'Starting...'; buildProgress.textContent = 'Starting...';
@@ -337,32 +337,52 @@
buildStatus.textContent = buildStatus.textContent =
'Patching complete. KoboRoot.tgz is ' + 'Patching complete. KoboRoot.tgz is ' +
(resultTgz.length / 1024).toFixed(0) + ' KB.'; (resultTgz.length / 1024).toFixed(0) + ' KB.';
writeSuccess.hidden = true;
const doneLog = document.getElementById('done-log'); const doneLog = document.getElementById('done-log');
doneLog.textContent = buildLog.textContent; doneLog.textContent = buildLog.textContent;
doneLog.scrollTop = doneLog.scrollHeight;
// Reset install step state.
btnWrite.hidden = manualMode; btnWrite.hidden = manualMode;
hideNav(); btnWrite.disabled = false;
btnWrite.className = 'primary';
btnWrite.textContent = 'Write to Kobo';
btnDownload.disabled = false;
writeInstructions.hidden = true;
downloadInstructions.hidden = true;
setNavStep(4);
showStep(stepDone); showStep(stepDone);
// Scroll log to bottom after the step becomes visible.
requestAnimationFrame(() => {
doneLog.scrollTop = doneLog.scrollHeight;
});
} catch (err) { } catch (err) {
showError('Build failed: ' + err.message, buildLog.textContent); showError('Build failed: ' + err.message, buildLog.textContent);
} }
}); });
// --- Done step --- // --- Install step ---
btnWrite.addEventListener('click', async () => { btnWrite.addEventListener('click', async () => {
if (!resultTgz || !device.directoryHandle) return; if (!resultTgz || !device.directoryHandle) return;
btnWrite.disabled = true;
btnWrite.textContent = 'Writing...';
downloadInstructions.hidden = true;
try { try {
const koboDir = await device.directoryHandle.getDirectoryHandle('.kobo'); const koboDir = await device.directoryHandle.getDirectoryHandle('.kobo');
const fileHandle = await koboDir.getFileHandle('KoboRoot.tgz', { create: true }); const fileHandle = await koboDir.getFileHandle('KoboRoot.tgz', { create: true });
const writable = await fileHandle.createWritable(); const writable = await fileHandle.createWritable();
await writable.write(resultTgz); await writable.write(resultTgz);
await writable.close(); await writable.close();
writeSuccess.hidden = false;
btnWrite.textContent = 'Written';
btnWrite.className = 'btn-success';
writeInstructions.hidden = false;
} catch (err) { } catch (err) {
btnWrite.disabled = false;
btnWrite.textContent = 'Write to Kobo';
showError('Failed to write KoboRoot.tgz: ' + err.message); showError('Failed to write KoboRoot.tgz: ' + err.message);
} }
}); });
@@ -376,6 +396,9 @@
a.download = 'KoboRoot.tgz'; a.download = 'KoboRoot.tgz';
a.click(); a.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
writeInstructions.hidden = true;
downloadInstructions.hidden = false;
}); });
// --- Error / Retry --- // --- Error / Retry ---
@@ -398,7 +421,6 @@
manualMode = false; manualMode = false;
selectedPrefix = null; selectedPrefix = null;
patchesLoaded = false; patchesLoaded = false;
btnWrite.hidden = false;
btnDeviceNext.hidden = false; btnDeviceNext.hidden = false;
if (hasFileSystemAccess) { if (hasFileSystemAccess) {

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KoboPatch Web UI</title> <title>KoboPatch Web UI</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css?ts=1773666969">
<script src="https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js"></script>
</head> </head>
<body> <body>
@@ -20,11 +20,17 @@
<li data-step="1" class="active">Device</li> <li data-step="1" class="active">Device</li>
<li data-step="2">Patches</li> <li data-step="2">Patches</li>
<li data-step="3">Build</li> <li data-step="3">Build</li>
<li data-step="4">Install</li>
</ol> </ol>
</nav> </nav>
<!-- Step 1a: Connect device (automatic, Chromium only) --> <!-- Step 1a: Connect device (automatic, Chromium only) -->
<section id="step-connect" class="step" hidden> <section id="step-connect" class="step" hidden>
<div class="warning">
Patching modifies system files on your Kobo. If something goes wrong,
you may need to <a href="https://help.kobo.com/hc/en-us/articles/360017605314-Manual-reset-your-Kobo-Clara-HD-Kobo-Nia-Kobo-Elipsa-Kobo-Clara-2E-Kobo-Elipsa-2E" target="_blank">manually reset your device</a>.
Proceed with care.
</div>
<p> <p>
Connect your Kobo e-reader via USB. It should appear as a removable drive. Connect your Kobo e-reader via USB. It should appear as a removable drive.
Then click the button below and select the root of the Kobo drive. Then click the button below and select the root of the Kobo drive.
@@ -38,6 +44,11 @@
<!-- Step 1b (manual): Select device model + firmware --> <!-- Step 1b (manual): Select device model + firmware -->
<section id="step-manual" class="step" hidden> <section id="step-manual" class="step" hidden>
<div class="warning">
Patching modifies system files on your Kobo. If something goes wrong,
you may need to <a href="https://help.kobo.com/hc/en-us/articles/360017605314-Manual-reset-your-Kobo-Clara-HD-Kobo-Nia-Kobo-Elipsa-Kobo-Clara-2E-Kobo-Elipsa-2E" target="_blank">manually reset your device</a>.
Proceed with care.
</div>
<p id="manual-chrome-hint" class="info-banner" hidden> <p id="manual-chrome-hint" class="info-banner" hidden>
Tip: if you use Chrome or Edge, this tool can auto-detect your device Tip: if you use Chrome or Edge, this tool can auto-detect your device
and write patched files directly to it. Even easier, but sadly requires a Chromium based browser. and write patched files directly to it. Even easier, but sadly requires a Chromium based browser.
@@ -122,22 +133,31 @@
<pre id="build-log" class="build-log"></pre> <pre id="build-log" class="build-log"></pre>
</section> </section>
<!-- Step 5: Done --> <!-- Step 5: Install -->
<section id="step-done" class="step" hidden> <section id="step-done" class="step" hidden>
<div id="build-status" class="info-banner"></div> <div id="build-status" class="info-banner"></div>
<details class="log-details">
<summary>Build log</summary>
<pre id="done-log" class="build-log done-log"></pre> <pre id="done-log" class="build-log done-log"></pre>
</details>
<div id="build-actions"> <div id="build-actions">
<button id="btn-write" class="primary">Write to Kobo</button> <button id="btn-write" class="primary">Write to Kobo</button>
<button id="btn-download" class="secondary">Download KoboRoot.tgz</button> <button id="btn-download" class="secondary">Download KoboRoot.tgz</button>
</div> </div>
<p class="fallback-hint" id="download-hint"> <div id="write-instructions" class="install-instructions" hidden>
Place the downloaded <strong>KoboRoot.tgz</strong> in the <strong>.kobo</strong> directory <p class="hint">
on your Kobo's USB drive, then safely eject and reboot the device.
</p>
<p class="hint" hidden id="write-success">
KoboRoot.tgz has been written to your Kobo. KoboRoot.tgz has been written to your Kobo.
Safely eject the device and reboot it to apply the patches. <strong>Safely eject</strong> the device before unplugging the USB cable — it will reboot and apply the patches automatically.
</p> </p>
</div>
<div id="download-instructions" class="install-instructions" hidden>
<ol class="install-steps">
<li>Connect your Kobo via USB so it appears as a removable drive.</li>
<li>Copy <strong>KoboRoot.tgz</strong> into the <strong>.kobo</strong> folder on the Kobo drive.</li>
<li><strong>Safely eject</strong> the Kobo — do not just unplug the cable, as this can corrupt data.</li>
<li>The device will reboot and apply the patches automatically.</li>
</ol>
</div>
</section> </section>
<!-- Error state --> <!-- Error state -->
@@ -150,9 +170,9 @@
</main> </main>
<!-- wasm_exec.js loaded by patch-worker.js inside the Web Worker --> <!-- wasm_exec.js loaded by patch-worker.js inside the Web Worker -->
<script src="kobo-device.js"></script> <script src="kobo-device.js?ts=1773666969"></script>
<script src="kobopatch.js"></script> <script src="kobopatch.js?ts=1773666969"></script>
<script src="patch-ui.js"></script> <script src="patch-ui.js?ts=1773666969"></script>
<script src="app.js"></script> <script src="app.js?ts=1773666969"></script>
</body> </body>
</html> </html>

View File

@@ -10,7 +10,7 @@ async function loadWasm() {
const go = new Go(); const go = new Go();
const result = await WebAssembly.instantiateStreaming( const result = await WebAssembly.instantiateStreaming(
fetch('kobopatch.wasm?ts=1773611308'), fetch('kobopatch.wasm?ts=1773666969'),
go.importObject go.importObject
); );
go.run(result.instance); go.run(result.instance);

View File

@@ -213,6 +213,19 @@ button:disabled {
box-shadow: none; box-shadow: none;
} }
button.btn-success {
background: var(--success-text);
border-color: var(--success-text);
color: #fff;
opacity: 1;
cursor: default;
}
button.btn-success:hover {
background: var(--success-text);
border-color: var(--success-text);
}
/* Cards */ /* Cards */
.info-card { .info-card {
background: var(--card-bg); background: var(--card-bg);
@@ -468,6 +481,23 @@ input[type="file"] {
line-height: 1.5; line-height: 1.5;
} }
/* Collapsible log on install step */
.log-details {
margin-top: 0.5rem;
margin-bottom: 0.25rem;
}
.log-details summary {
font-size: 0.83rem;
color: var(--text-secondary);
cursor: pointer;
padding: 0.25rem 0;
}
.log-details summary:hover {
color: var(--text);
}
/* Done screen log is shorter */ /* Done screen log is shorter */
.done-log { .done-log {
height: calc(7 * 1.5em + 1.5rem); height: calc(7 * 1.5em + 1.5rem);
@@ -511,6 +541,30 @@ input[type="file"] {
color: var(--text-secondary); color: var(--text-secondary);
} }
/* Install instructions */
.install-instructions {
margin-top: 1rem;
}
.install-instructions .warning {
margin-bottom: 0.75rem;
}
.install-instructions .hint {
margin-top: 0;
}
.install-steps {
margin: 0.5rem 0 0 1.25rem;
font-size: 0.88rem;
color: var(--text-secondary);
line-height: 1.7;
}
.install-steps li {
padding: 0.1rem 0;
}
.info-banner { .info-banner {
background: var(--primary-light); background: var(--primary-light);
border: 1px solid #bfdbfe; border: 1px solid #bfdbfe;