diff --git a/MILESTONES.md b/MILESTONES.md
index 342ffe1..b914c26 100644
--- a/MILESTONES.md
+++ b/MILESTONES.md
@@ -48,11 +48,19 @@
- [ ] toast on skip + skip-counter in settings
- [ ] category toggles in `settings.xml`
-## M6 — install + cross-compile
+## M6 — install + cross-compile [PARTIAL]
-- [ ] crafting-table builds `torttube-sidecar.aarch64` + `.armv7`
-- [ ] `addon.zip` ships with platform detect via `xbmc.getCondVisibility('system.platform.linux.raspberrypi')`
-- [ ] one-shot install path documented for LibreELEC `/storage/.kodi/`
+- [x] cross-compile sidecar for aarch64-musl static via throwaway
+ `messense/rust-musl-cross:aarch64-musl` container. 6.2MB stripped
+ static binary. Builds clean from `scripts/build-addon-zip.sh`.
+- [x] bundle yt-dlp's `yt-dlp_linux_aarch64` release binary for Tier 2/3
+- [x] zip layout matches Kodi "install from zip" expectations
+- [x] addon.zip dropped at `smb://lucy/downloads/torttube/` for Pi-side install
+- [x] install + smoke recipe documented at `docs/install.md`
+- [ ] **install on the actual Pi** + verify the JSON-RPC `Player.Open`
+ smoke against `192.168.0.158` — needs Cobb to either install via
+ Kodi UI or grant SSH to drop the zip directly
+- [ ] armv7 build for older Pis (deferred — Cobb's TVs are all aarch64-capable)
## Upstream PR work (parallel lane — every bug evaluated for "fix it upstream?")
diff --git a/addon/plugin.video.torttube/addon.xml b/addon/plugin.video.torttube/addon.xml
index 0866235..d1b51ab 100644
--- a/addon/plugin.video.torttube/addon.xml
+++ b/addon/plugin.video.torttube/addon.xml
@@ -5,7 +5,6 @@
provider-name="Sulkta-Coop">
-
diff --git a/docs/install.md b/docs/install.md
new file mode 100644
index 0000000..0ff36a1
--- /dev/null
+++ b/docs/install.md
@@ -0,0 +1,65 @@
+# Installing torttube on the LibreELEC RPi
+
+The addon ships as a single `plugin.video.torttube-.zip` that
+contains the Python addon, a static aarch64 sidecar binary, and yt-dlp's
+aarch64 release binary (for Tier 2/3 fallback). Nothing else needs to be
+installed on the Pi.
+
+## Build the zip (Sulkta-internal)
+
+```bash
+bash scripts/build-addon-zip.sh
+# → /mnt/user/downloads/torttube/plugin.video.torttube-0.0.1.zip on Lucy
+```
+
+The script cross-compiles the sidecar in a throwaway
+`messense/rust-musl-cross:aarch64-musl` container, fetches yt-dlp's
+official `yt-dlp_linux_aarch64` release binary, packages everything,
+drops the result at `/mnt/user/downloads/torttube/` (Lucy SMB).
+
+## Install on the Pi (Kodi UI flow)
+
+1. On the Pi: Settings → File manager → Add source → enter
+ `smb://lucy/downloads/` → name it `lucy-downloads`.
+2. Settings → Add-ons → Install from zip file → `lucy-downloads` →
+ `torttube/plugin.video.torttube-.zip`.
+3. Kodi installs, the addon appears under Video add-ons.
+
+Unsigned addons need `Settings → System → Add-ons → Unknown sources` ON.
+
+## Verify
+
+After install, fire the smoke from any LAN client:
+
+```bash
+curl -u kodi:pineapple -H "Content-Type: application/json" \
+ -X POST http://192.168.0.158:8080/jsonrpc -d '{
+ "jsonrpc": "2.0", "id": 1, "method": "Player.Open",
+ "params": {"item": {"file":
+ "plugin://plugin.video.torttube/?action=play&id=dQw4w9WgXcQ"}}}'
+```
+
+The TV should switch to playback of "Never Gonna Give You Up" within a
+few seconds (rustypipe resolve takes ~1s, then Kodi starts the stream).
+
+## Troubleshooting
+
+- **Black screen + "no stream URL"** notification: rustypipe returned
+ separate audio + video streams (DASH-style), and we're picking the
+ video stream which may be video-only. This is the M3 known gap —
+ needs the audio+video merge work in M3+. Workaround: try a video that
+ yt-dlp's tier-2 path can resolve to a combined format; the sidecar
+ falls back automatically.
+- **Sidecar crashes**: `cat /storage/.kodi/temp/kodi.log | grep torttube`
+ on the Pi. Sidecar logs to stderr; Kodi captures those.
+- **yt-dlp permission denied**: addon's bin/ dir needs +x. The zip
+ preserves perms but if you copied files manually, `chmod +x bin/*`.
+- **HTTPS errors from sidecar**: it bundles rustls + webpki roots, so
+ no system CA store needed. If you see "invalid certificate", your Pi
+ clock is wrong (LibreELEC's NTP failed).
+
+## Updating
+
+Just rebuild + bump the version in `addon/plugin.video.torttube/addon.xml`,
+re-run `build-addon-zip.sh`, install the new zip from the same SMB
+location. Kodi will treat it as an upgrade if the version is higher.
diff --git a/scripts/build-addon-zip.sh b/scripts/build-addon-zip.sh
new file mode 100755
index 0000000..3b4ef60
--- /dev/null
+++ b/scripts/build-addon-zip.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+# Build addon.zip for torttube — runs from a host that can ssh lucy.
+#
+# Cross-compiles the sidecar via messense/rust-musl-cross:aarch64-musl
+# (one-shot container, no crafting-table mutation), bundles yt-dlp's
+# aarch64 release binary, packages with the Kodi addon dir layout, and
+# drops the zip at /mnt/user/downloads/torttube/ on Lucy.
+#
+# Usage: bash scripts/build-addon-zip.sh
+#
+# Expected: ssh alias "lucy" works (per Sulkta SSH config), and the
+# torttube source is at /mnt/cache/appdata/crafting-table/workspace/torttube/
+# on Lucy (rsync first if you're iterating locally).
+set -euo pipefail
+
+VERSION="${VERSION:-0.0.1}"
+SRC=/mnt/cache/appdata/crafting-table/workspace/torttube
+CARGO_HOME_CACHE=/mnt/cache/appdata/crafting-table/workspace/.cargo-aarch64
+TARGET_DIR=/mnt/cache/appdata/crafting-table/workspace/.aarch64-target
+STAGE=/tmp/torttube-stage
+DEST_DIR=/mnt/user/downloads/torttube
+
+echo ">>> Cross-compile sidecar for aarch64-musl"
+ssh lucy "docker run --rm \
+ -v $SRC/sidecar:/src \
+ -v $CARGO_HOME_CACHE:/cargo-home \
+ -v $TARGET_DIR:/target \
+ -e PATH=/root/.cargo/bin:/usr/local/musl/bin:/usr/local/bin:/usr/bin:/bin \
+ -e CARGO_HOME=/cargo-home \
+ -e CARGO_TARGET_DIR=/target \
+ -e CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-unknown-linux-musl-gcc \
+ -e CC_aarch64_unknown_linux_musl=aarch64-unknown-linux-musl-gcc \
+ -w /src \
+ messense/rust-musl-cross:aarch64-musl \
+ cargo build --release"
+
+echo ">>> Stage addon tree"
+ssh lucy "rm -rf $STAGE && mkdir -p $STAGE/plugin.video.torttube/bin"
+ssh lucy "rsync -a $SRC/addon/plugin.video.torttube/ $STAGE/plugin.video.torttube/ --exclude bin"
+ssh lucy "cp $TARGET_DIR/aarch64-unknown-linux-musl/release/torttube-sidecar $STAGE/plugin.video.torttube/bin/"
+
+echo ">>> Fetch yt-dlp aarch64 release binary"
+ssh lucy "curl -sSL -o $STAGE/plugin.video.torttube/bin/yt-dlp \
+ https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux_aarch64"
+ssh lucy "chmod +x $STAGE/plugin.video.torttube/bin/*"
+
+echo ">>> Build addon.zip"
+ssh lucy "cd $STAGE && rm -f plugin.video.torttube.zip && \
+ zip -r plugin.video.torttube.zip plugin.video.torttube/ -x '*.DS_Store' > /dev/null"
+
+echo ">>> Drop at $DEST_DIR/plugin.video.torttube-$VERSION.zip"
+ssh lucy "mkdir -p $DEST_DIR && cp $STAGE/plugin.video.torttube.zip \
+ $DEST_DIR/plugin.video.torttube-$VERSION.zip"
+
+ssh lucy "ls -la $DEST_DIR/"
+echo ">>> done — install via Kodi: Settings > Add-ons > Install from zip"
+echo " SMB path: smb://lucy/downloads/torttube/plugin.video.torttube-$VERSION.zip"
diff --git a/sidecar/crates/torttube-sidecar/Cargo.toml b/sidecar/crates/torttube-sidecar/Cargo.toml
index 2e67476..017df7d 100644
--- a/sidecar/crates/torttube-sidecar/Cargo.toml
+++ b/sidecar/crates/torttube-sidecar/Cargo.toml
@@ -11,8 +11,11 @@ name = "torttube-sidecar"
path = "src/main.rs"
[dependencies]
-# Tier 1 — native Rust Innertube
-rustypipe = "0.11"
+# Tier 1 — native Rust Innertube.
+# default-features=false skips rustypipe's default-tls which pulls in
+# native-tls / openssl — we use rustls so cross-compile to aarch64-musl
+# stays openssl-free.
+rustypipe = { version = "0.11", default-features = false, features = ["rustls-tls-webpki-roots"] }
# Tier 2 + 3 — yt-dlp subprocess shell-out (no library, just std::process)