Found the real cause of "-5: ipk verify failed": ares-package's own
packaging code (ar-async/fstream/tar, last touched 2017-2019) silently
zeroes every file's mtime in the ipk when run under a sufficiently new
Node (reproduced on v25.8.1; a Node-18 container was unaffected).
Confirmed by unpacking data.tar.gz from our built ipk (every entry
1970-01-01) versus webosbrew/hyperhdr-webos-loader's real published
ipk, structurally identical to ours (web app + native service, same
control file, same ares-cli) but with genuine October 2025 timestamps.
The archive still parses fine everywhere generic tooling looks —
Python's tarfile, our own ar/tar inspection — so nothing here ever
errors. The TV's own installer is what eventually rejects it, and it
gives no hint why.
This took three rounds of elimination to isolate: root elevation
wasn't it (Homebrew Channel's own root status was "ok"), and
system-wide native-code verification wasn't it either (a real
native-service app installed fine on the same TV). Comparing our ipk
against that known-good one byte-for-byte was what surfaced the
timestamp anomaly, and rebuilding under Node 18 reproduced correct
timestamps immediately.
build.sh now runs ares-package inside a pinned node:18 container by
default, falling back to the host's Node with a warning if Docker
isn't available. Rebuilt the actual release ipk this way and
regenerated manifest.json/repo.json against its corrected hash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Read the Homebrew Channel's own DetailsPanel.js: when an app is opened
from Browse, refresh() only ever takes entry.manifest directly, or
fetches entry.manifestUrl if that's absent — never the entry's own
top-level fields. Our repo.json had neither, so
resolveURL(undefined, repositoryURL) threw before the fetch could even
start, and the details screen spun on "Loading" forever with no visible
error. This is what happened when it was tried against a real repo.
Fixed by nesting the full manifest under a "manifest" key per package
entry, alongside the id/title/iconUri the Browse grid reads directly.
Verified structurally (grid fields present, manifest embedded and
non-empty, ipkUrl absolute) since there's no local Homebrew Channel
build to run this against.
Also caught in the same investigation: a stale --base-url in the docs
used the tag v1.0.0 while the actual release was tagged 1.0.0, which
404s the icon and ipk silently — same symptom, different cause. Noted
in both the tool's docstring and the docs that a tag mismatch looks
identical to the manifest bug from the client's side.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ares-install needs a working ssh into the TV, and this TV only has root
access (no Developer Mode, no ssh currently reachable). The Homebrew
Channel's own "Add repository" dialog accepts any URL that returns
{"packages": [...]}, which is the same schema its own gen-manifest.js
uses per app — confirmed by reading the app's source directly rather
than guessing at the format.
make-manifest.py now also writes out/repo.json, the existing manifest
wrapped in that shape, so publishing is: build the ipk, run the script
with --base-url pointing at wherever the release assets will live,
attach ipk + icon + repo.json there, then paste the repo.json URL into
the TV once. No ares, no ssh, no review queue.
Root elevation afterwards is unaffected either way: "Grant root access"
in the app calls elevate-service over the Luna bus from inside the
running app, which was already ssh-independent.
Corrected two inaccuracies in the process: webosbrew/repo doesn't exist
(the real project is webosbrew/apps-repo), and the Homebrew Channel has
no "install from file" UI — Browse-and-install or the /install Luna
service are the only ways in, both of which need a URL, not a local
path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The openlgtv NDK is a Linux toolchain with no macOS or Windows build, so
tools/build.sh could not produce a binary anywhere else. docker-build.sh
bakes the SDK into an image and compiles there; packaging and deploy stay
on the host, where the TV is reachable. The SDK ships aarch64 as well as
x86_64, so the image picks the one matching the daemon and Apple Silicon
builds natively rather than under emulation.
Cross-compiling for real turned up three things the host compiler did
not:
sink_hyperhdr_viz.c read p->width and p->height to format the error
message after free(p)
sink_hyperhdr.c an SDP connection line of 128 bytes cannot hold
"IN IP4 " plus a 127-byte host plus "/255", so a
long hostname would silently lose its TTL suffix
common/log.c the log body was sized to the whole ring line,
leaving nothing for the prefix; budget for it so
the bound is provable rather than left to
snprintf
A clean cross-compile is now warning-free, and readelf confirms the
design rule holds: luna-service2, glib, PmLogLib and libc, with no
libpulse or libasound.
Also: @webosose/ares-cli was pinned to ^3.0.0, which does not exist
(latest is 2.4.0), so npm install failed outright. build.sh now puts
node_modules/.bin on PATH so a local install is enough.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Captures the TV's audio and sends it out over several transports. The
primary one is HyperHDR: RTP/L16 to a host-side loopback device, since
HyperHDR has no network audio input of its own. A second route renders
the spectrum on the TV and sends FlatBuffers images to port 19400
instead, for setups where touching the host's sound config is not an
option.
native/ the service: capture backends (PulseAudio, ALSA, exec,
test tone, all dlopen-based), DSP, and one file per sink
frontend/ D-pad driven UI at a fixed 1920x1080
servicefiles/ native service manifest plus the boot script
host/ RTP receiver and the loopback installer for the HyperHDR
machine
tools/ build/package, asset generation, Homebrew Channel
manifest, on-TV probe
test/ host-side suites: FlatBuffers and RTP verified against
real decoders, the engine end to end, the page in jsdom
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>