# NEMESIS Cloud Store API What NEMESIS Manager 1.16 expects from a store server, and what the public API on xwtk.Portal serves. The API is read-only and public, so anyone can host a compatible store of their own: fork NEMESIS, serve the routes below, and point `Portal.BASE_URL` in `manager/src/com/nemesis/manager/Portal.java` at your server. The NEMESIS web page uses the same routes. How the packages get onto the server (accounts, review, uploads) is up to the host and is not part of this API. ``` BASE_URL = https://portal.xwtk.cloud/nemesis/v1 ``` ## Hosting your own store 1. Serve the routes below over HTTPS with a certificate the head unit can validate (see TLS). 2. Change `BASE_URL` in `Portal.java` and rebuild the Manager (`make manager`). 3. If your certificate chain is not covered by the unit's system store, add the root to the bundle with `tools/make_cacerts.py` before building. 4. The server can be static files. xwtk.Portal publishes the catalog and one descriptor per package as prebuilt JSON, and only the download routes run code. 5. Compute `sha256` and `size` from the exact zip you serve. Manager deletes any download that does not match. ## Common rules - HTTPS only. The client refuses any `http://` URL, including download URLs in replies. - All requests are `GET` with no cookies, API key or account data. The `User-Agent` is `NEMESIS/`. - JSON replies are UTF-8 and at most 2 MiB. - Any status other than 200 is treated as a failure. Redirects between HTTPS hosts are followed by the platform client; a redirect to a different scheme is not. - Client timeouts: 15 s to connect, 30 s per read. ## TLS - The client offers TLS 1.2 only, because KitKat ships it disabled and the client turns it on explicitly. TLS 1.3-only servers will not work. - Server chains must validate against the unit's system store or the roots bundled in the APK (`manager/res/raw/nemesis_roots.pem`, generated by `tools/make_cacerts.py`). - Certificate validation needs a correct clock. The unit reads 2015 until it has real time, and validation fails until then, which looks the same as "unreachable". The background check then fails silently and tries again on the monitor's next hourly tick. ## Update check ``` GET {BASE_URL}/update/latest?current=1.16&os=&model= ``` Up to date: ```json { "update": false } ``` Update available: ```json { "update": true, "version": "1.17", "url": "https://host/path/nemesis-update-1.17.zip", "sha256": "<64 hex characters>" } ``` The client ignores a reply whose `version` is not newer than `current`, using a dotted numeric compare. ## Update download ``` GET ``` Any HTTPS URL works. xwtk.Portal gives `{BASE_URL}/update/download/` as the `url`. The body is a NEMESIS update zip, in the same format as a USB `type=update` package. The client hashes it while downloading and deletes it on a `sha256` mismatch. `nemesis update` then checks it again as a package before installing it. ## Store catalog ``` GET {BASE_URL}/store/catalog ``` ```json { "packages": [ { "id": "vendor.tool", "name": "Tool", "short-desc": "One line for the list", "published": "2026-10-31 14:05:09", "package-version": "1.2", "os": "any", "models": "any", "type": "tool", "descriptor": "1.1", "community": true } ] } ``` - `id` is required. `name` falls back to `id`. `short-desc` and `published` may be empty. - `community` is a boolean. The web store shows a Community badge when it is `true`. xwtk.Portal sets it for packages from other publishers, and the owner of the store chooses it for their own. Missing means `false`. - `descriptor` is the package's `NEMESIS.data` `version`. The web store and Manager show anything below `1.1` in yellow with a warning that it may not be compatible. Missing means no warning. - Use `YYYY-MM-DD HH:MM:SS` in UTC for `published`, because sorting by date compares the text. A plain `YYYY-MM-DD` still works, and sorts before any time on that day. - A reply without a `packages` array counts as unreachable. - Clients ignore keys they do not use. The Manager reads `package-version`, `os`, `models` and `type` from the descriptor, and the web page reads them from here to avoid one request per row. ## Descriptor by id Requested once per catalog row. It carries what the Store needs for compatibility colours and the install. ``` GET {BASE_URL}/store/packages/ ``` The `id` is URL-encoded. ```json { "package-version": "1.2", "os": "any", "models": "any", "depends": "", "url": "https://host/path/vendor.tool-1.2.zip", "sha256": "<64 hex characters>" } ``` The server also sends the fields below, which the web page shows under Details. Manager reads only `descriptor` and `detailed-desc` from them. | Field | Type | Meaning | |---|---|---| | `name` | string | Display title | | `short-desc` | string | One line, at most 64 characters | | `detailed-desc` | string | Longer text, may be empty | | `type` | string | `tool`, `app`, and so on, copied from the package | | `published` | string | `YYYY-MM-DD HH:MM:SS`, UTC | | `size` | number | Zip size in bytes | | `repository` | string | Source URL from the package, may be empty | | `apps`, `services` | array of strings | What the package installs | | `descriptor` | string | As in the catalog | | `community` | boolean | As in the catalog | All of the other fields are strings, and `package-version`, `os`, `models`, `depends`, `url` and `sha256` are the ones Manager acts on. `os`, `models` and `depends` use the same syntax as the keys in `NEMESIS.data`, and are passed to `nemesis compat-check`. Missing `os` or `models` means `any`. A row whose descriptor request fails keeps its plain colour, and its Install and Uninstall buttons stay disabled. ## Package download ``` GET ``` Any HTTPS URL works. xwtk.Portal gives `{BASE_URL}/store/download//`, serves only the current version of a listed package, and names the file `-.zip`. The body is a normal NEMESIS package zip. The client verifies `sha256` (case-insensitive hex), then runs `nemesis install`, then deletes the file. On a mismatch it deletes the file and installs nothing. The zip's own `NEMESIS.data` is what the install trusts. The descriptor only decides what the Store shows. A package the zip itself marks incompatible still stops the install, with exit code 20, until the user accepts the risk. ## Errors and offline behaviour | Situation | Manager does | |---|---| | Catalog unreachable, bad status, or bad JSON | Online tab grayed out, one popup on entering the Store. Falls to the USB tab if a `type=list` stick is mounted. | | Descriptor unreachable for one row | That row stays plain and cannot be installed from. Other rows are unaffected. | | Manual update check, no stick and portal unreachable | Popup: "No local NEMESIS update package was found, and there was a problem connecting to xwtk.Portal." | | Background update check unreachable | Silent, and retried by the next tick. Only a successful check starts the 24 h wait. | | Hash mismatch | Popup saying the download was deleted. Nothing is installed. | The server should return 200 with `{"update": false}` when there is no update, not 404, because any non-200 reply is reported as a connection problem.