Add a no-SSH install path: a self-hosted Homebrew Channel repository

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>
This commit is contained in:
Rene Kievits
2026-08-26 12:14:31 +02:00
co-authored by Claude Opus 5
parent 3e4d0e17bc
commit 4f008c558b
3 changed files with 88 additions and 30 deletions
+24 -15
View File
@@ -51,27 +51,36 @@ See [docs/hyperhdr.md](docs/hyperhdr.md) for both, step by step.
## Installing ## Installing
**From the Homebrew Channel.** Open the Homebrew Channel on the TV, find This app is not in the official Homebrew Channel repository (that needs a
*Audio Cap*, install, launch. review submission — see [docs/development.md](docs/development.md)). Two ways
to get it onto the TV instead, neither needing SSH or Developer Mode:
**From an ipk.** Copy the ipk to the TV and install it with the Homebrew **Add it as a custom repository (recommended).** The Homebrew Channel can
Channel's *Install from file*, or from a workstation: browse-and-install from any repository URL you give it, not just the official
one. Build the ipk, publish it plus a generated repo index, then add that URL
on the TV:
```sh
npm install # ares-cli, only needed for step 2
./tools/docker-build.sh # cross-compile; Linux: ./tools/build.sh native
./tools/build.sh package # -> out/*.ipk
python3 tools/make-manifest.py --base-url <where you'll host the release assets>
# -> out/repo.json, out/manifest.json
```
Attach the ipk, `frontend/assets/icon.png` and `out/repo.json` to a release at
that URL, then on the TV: **Homebrew Channel → ⚙ → Add repository** → paste the
`repo.json` URL → back to Browse → *Audio Cap* → Install. Full walkthrough,
including why the URLs have to match exactly, in
[docs/development.md](docs/development.md#publishing-to-the-homebrew-channel).
**Install the ipk directly**, if you do have ares-cli talking to the TV (e.g.
LG Developer Mode's ssh on port 9922, or a root SSH server you've enabled):
```sh ```sh
ares-install --device tv out/org.webosbrew.audiocap_1.0.0_all.ipk ares-install --device tv out/org.webosbrew.audiocap_1.0.0_all.ipk
``` ```
**From source.** The NDK is Linux-only, so on macOS or Windows the compile goes
through Docker:
```sh
npm install # ares-cli
./tools/docker-build.sh # or ./tools/build.sh native on Linux
./tools/build.sh package # -> out/*.ipk
```
See [docs/development.md](docs/development.md) for the rest.
## First run ## First run
1. Launch **Audio Cap** on the TV. 1. Launch **Audio Cap** on the TV.
+41 -7
View File
@@ -107,22 +107,56 @@ npm run serve # http://localhost:8000
## Publishing to the Homebrew Channel ## Publishing to the Homebrew Channel
Two ways to get an ipk into the Browse tab. Neither one touches `ares-install`
or ssh — the app installs itself once the TV can reach a URL.
### Your own repository (no review, no waiting)
The Homebrew Channel's *Settings → Repositories → Add repository* accepts any
URL that returns `{"packages": [...]}`, where each entry is the same manifest
[`make-manifest.py`](../tools/make-manifest.py) already writes. Point one at
your own git host's release assets and the app shows up in Browse with no
submission process at all — this is what `--repo-out` (on by default) is for.
1. Bump `version` in `frontend/appinfo.json`, `servicefiles/package.json` and 1. Bump `version` in `frontend/appinfo.json`, `servicefiles/package.json` and
`package.json`. `package.json`.
2. `./tools/build.sh` and test the ipk on a real set. 2. Build the ipk: `./tools/docker-build.sh && ./tools/build.sh package` (or
3. Attach the ipk and `frontend/assets/icon.png` to a release. `./tools/build.sh` on Linux with the NDK installed).
4. Generate the manifest and attach that too: 3. Create a release tagged e.g. `v1.0.0` and attach three files to it: the
ipk, `frontend/assets/icon.png`, and a repo index generated with
`--base-url` set to that release's asset URL:
```sh ```sh
python3 tools/make-manifest.py \ python3 tools/make-manifest.py \
--base-url https://git.crylia.de/Crylia/lgtv_audio_cap/releases/download/v1.0.0 --base-url https://git.crylia.de/Crylia/lgtv_audio_cap/releases/download/v1.0.0
# -> out/manifest.json (one app entry)
# -> out/repo.json (that entry wrapped as {"packages": [...]})
``` ```
5. Submit the manifest URL to [webosbrew/repo](https://github.com/webosbrew/repo). Attach `out/repo.json` itself too — its own download URL is what you paste
into the TV, and it must match `--base-url` exactly or the ipk/icon links
inside it point at the wrong place.
4. On the TV: Homebrew Channel → gear icon → *Add repository* → paste the
`repo.json` release URL → back out to Browse → find *Audio Cap* → Install.
The manifest sets `"rootRequired": true`, which tells the Homebrew Channel the Shipping an update later is the same four steps with a bumped version and a
service needs elevating. The app can also do it on demand — *System → Grant root new release tag; the Channel diffs against the installed version and offers
access* runs the Channel's `elevate-service`. an update once the repo URL is already added.
### The official repo (public listing, reviewed)
Submit once to [webosbrew/apps-repo](https://github.com/webosbrew/apps-repo)
and anyone with a stock Homebrew Channel install can find it without adding a
custom repository. That project wants a `.yml` descriptor pointing at your own
hosted `manifest.json` (the un-wrapped file from step 3 above) — see its README
for the submission format.
---
`rootRequired: true` in the manifest tells the Homebrew Channel the service
needs elevating, from either path. The app can also do it on demand —
*System → Grant root access* runs the Channel's `elevate-service` itself, over
the Luna bus, so it works whether SSH is enabled on the TV or not.
## How the service is put together ## How the service is put together
+23 -8
View File
@@ -1,15 +1,21 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Writes the Homebrew Channel manifest for a built ipk. """Writes the Homebrew Channel manifest for a built ipk, and a custom-repo
index that wraps it.
The Homebrew Channel installs apps from a manifest that points at the ipk and The manifest (out/manifest.json) is one app entry: id, ipkUrl, ipkHash, and so
carries its hash. Publishing means putting this file somewhere stable (a GitHub on. It is what you submit to webosbrew/apps-repo to get into the official
release asset, or the repository itself) and submitting the URL to store.
webosbrew/repo.
The repo index (out/repo.json) is that same entry wrapped as
`{"packages": [...]}`, which is the format the Homebrew Channel's own
"Add repository" dialog expects (Settings -> Repositories -> Add repository).
Host it anywhere static, paste its URL in, and the app shows up in Browse —
no submission, no review, no shell access to the TV at all.
python3 tools/make-manifest.py \\ python3 tools/make-manifest.py \\
--base-url https://github.com/you/lgtv-audio-cap/releases/download/v1.0.0 --base-url https://git.example/you/lgtv-audio-cap/releases/download/v1.0.0
Defaults to the newest ipk in out/ and writes out/manifest.json. Defaults to the newest ipk in out/ and writes out/manifest.json + out/repo.json.
""" """
import argparse import argparse
@@ -47,6 +53,8 @@ def main():
parser.add_argument("--source-url", default=SOURCE_URL, parser.add_argument("--source-url", default=SOURCE_URL,
help="project page shown in the Homebrew Channel") help="project page shown in the Homebrew Channel")
parser.add_argument("--out", default=os.path.join(ROOT, "out", "manifest.json")) parser.add_argument("--out", default=os.path.join(ROOT, "out", "manifest.json"))
parser.add_argument("--repo-out", default=os.path.join(ROOT, "out", "repo.json"),
help="path for the custom-repo index (set to '' to skip)")
args = parser.parse_args() args = parser.parse_args()
appinfo = json.load(open(os.path.join(ROOT, "frontend", "appinfo.json"))) appinfo = json.load(open(os.path.join(ROOT, "frontend", "appinfo.json")))
@@ -79,9 +87,16 @@ def main():
with open(args.out, "w") as fh: with open(args.out, "w") as fh:
json.dump(manifest, fh, indent=2) json.dump(manifest, fh, indent=2)
fh.write("\n") fh.write("\n")
print(json.dumps(manifest, indent=2)) print(json.dumps(manifest, indent=2))
print("\nwrote %s" % os.path.relpath(args.out, ROOT), file=sys.stderr) print("\nwrote %s" % os.path.relpath(args.out, ROOT), file=sys.stderr)
if args.repo_out:
os.makedirs(os.path.dirname(args.repo_out) or ".", exist_ok=True)
with open(args.repo_out, "w") as fh:
json.dump({"packages": [manifest]}, fh, indent=2)
fh.write("\n")
print("wrote %s" % os.path.relpath(args.repo_out, ROOT), file=sys.stderr)
if not base: if not base:
print("note: no --base-url given, so ipkUrl and iconUri are relative", print("note: no --base-url given, so ipkUrl and iconUri are relative",
file=sys.stderr) file=sys.stderr)