Files

65 lines
3.5 KiB
Markdown
Raw Permalink Normal View History

# mobitool.wasm — Kindle→EPUB converter module
`mobitool.wasm` is [libmobi](https://github.com/bfabiszewski/libmobi)'s `mobitool`
compiled to `wasm32-wasi`. The server runs it **in-process** via
[`wazero`](https://github.com/tetratelabs/wazero) (pure Go) to convert
MOBI/AZW/AZW3 ebooks to EPUB on demand. See the design doc:
`docs/superpowers/specs/2026-06-17-kindle-epub-conversion-design.md`.
## Why this shape
- **No cgo, no external binary.** The `.wasm` is `go:embed`-ed; the Go build stays
`CGO_ENABLED=0` and cross-compiles trivially.
- **Architecture-independent.** wasm32 bytecode runs identically on amd64/arm64
servers through wazero. Build once (CI, amd64), run anywhere.
- **Sandboxed.** Untrusted ebook bytes are parsed inside the WASM sandbox with
only a per-conversion scratch dir mounted — a libmobi memory bug on a malicious
file cannot reach the host.
## Pinned versions
| Component | Version / commit |
|-------------------|--------------------------------------------|
| wasi-sdk | 25.0 |
| libmobi | `906274205c11944b628da1c553b255acb1af7c55` |
| zlib | 1.3.1 (compiled to wasm) |
| wasmtime (smoke) | 27.0.0 (build-time smoke test only) |
License: libmobi is **LGPL-3.0-or-later**; shipping the unmodified compiled
artifact + this build recipe satisfies the relink obligation. zlib is zlib-license.
## Build config notes (validated by spike, 2026-06-17 on linux/amd64)
- `--with-libxml2=no` → libmobi's **internal xmlwriter** provides OPF/EPUB output,
so the `-e` (create EPUB) path works with no libxml2 dependency.
- libmobi is built **against a wasm-compiled zlib** (not `--with-zlib=no`). Forcing
`--with-zlib=no` makes libmobi vendor its own miniz, which then collides with
`mobitool`'s separate miniz (used for EPUB zip creation) — `wasm-ld` rejects the
duplicate symbols. Real zlib for the library + the tool's miniz for zip = no clash.
## Rebuild + install the artifact
```sh
docker build --platform linux/amd64 -t mobitool-wasm tools/mobitool-wasm
id=$(docker create mobitool-wasm)
docker cp "$id:/mobitool.wasm" internal/ebookconvert/mobitool.wasm
docker cp "$id:/mobitool.wasm.sha256" internal/ebookconvert/mobitool.wasm.sha256
docker rm "$id"
```
The build runs a **smoke conversion** of a bundled libmobi sample and fails if no
EPUB is produced. `mobitool -v` embeds `__DATE__`/`__TIME__`, so rebuilds are not
byte-for-byte reproducible — rely on the smoke test + recorded sha256, not a
bit-identical rebuild check.
## Runtime behavior characterized by the spike
- Converts MOBI6, KF8/AZW3 hybrids, HUFF/CDIC-compressed, and unicode samples to
well-formed EPUB (`mimetype` first = `application/epub+zip`, `META-INF/container.xml`,
`OEBPS/*`).
- **DRM:** `mobitool` prints `Document is encrypted` to **stdout** but still **exits 0**
and writes an EPUB (garbage content when it lacks the key). So the converter must
capture stdout and validate the output is a usable EPUB — exit code alone is not a
reliable DRM/failure signal.
- ⚠️ **Brittleness note:** DRM/print-replica detection matches the **English
stdout strings** mobitool emits (`drmMarkers` / `printReplicaMarker` in
`internal/ebookconvert/converter.go`). When bumping the pinned libmobi commit,
re-check those messages still match — a wording change silently downgrades a
DRM book to a generic "no EPUB produced" failure (it still falls back to the raw
original safely, just without the explicit DRM signal).