M6 — cross-compile aarch64-musl + addon.zip + install docs

scripts/build-addon-zip.sh runs the whole pipeline from a host with ssh
lucy:
- one-shot messense/rust-musl-cross:aarch64-musl container builds the
  sidecar static (6.2MB stripped). Doesn't mutate crafting-table.
- fetches yt-dlp_linux_aarch64 from the upstream release page so Tier 2
  + Tier 3 work on the Pi (LibreELEC ships no Python YouTube tools)
- packages everything into plugin.video.torttube.zip with the Kodi
  install-from-zip layout
- drops the zip at /mnt/user/downloads/torttube/ on Lucy SMB

Cargo.toml swaps rustypipe to default-features=false +
rustls-tls-webpki-roots so the cross-compile is openssl-free.

addon.xml drops the unused script.module.requests requirement — main.py
only uses Python stdlib + Kodi's own modules.

docs/install.md walks the Kodi UI flow + a smoke curl that fires
Player.Open via JSON-RPC. Pi-side smoke is pending Cobb's install on
192.168.0.158.
This commit is contained in:
Kayos 2026-05-23 08:54:46 -07:00
parent 9b2a47c909
commit f4ceae3b70
5 changed files with 139 additions and 7 deletions

View file

@ -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?")

View file

@ -5,7 +5,6 @@
provider-name="Sulkta-Coop">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="script.module.requests" version="2.22.0"/>
<import addon="inputstream.adaptive" version="2.0.0" optional="true"/>
</requires>
<extension point="xbmc.python.pluginsource" library="main.py">

65
docs/install.md Normal file
View file

@ -0,0 +1,65 @@
# Installing torttube on the LibreELEC RPi
The addon ships as a single `plugin.video.torttube-<version>.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-<version>.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.

57
scripts/build-addon-zip.sh Executable file
View file

@ -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"

View file

@ -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)