Lua · The Store

One JSON file is the whole catalogue.

The device's Store app fetches snailos.org/apps/index.json and installs any listed app onto the card. The index is an array of flat scalars, read by the same extractor a card app reads its own data with.

Two screens: the list, and the app's page§

Every app sits in one scrolling column under a plain heading per category. There was a storefront here for a while — a 2×2 featured grid, a top chart, an ALL APPS door and one door per category. That is the right shape for several hundred entries. This catalogue holds 3, and every door cost a 511 ms refresh to walk through and another to walk back, to reach a list the reader could already have been reading.

OK opens the app's own page: the full description, who made it, its version against what the card holds, its size, and the one or two actions that make sense for its state. Install, update and uninstall live there, so the list stays a place to read. RIGHT refreshes, because everywhere else in the OS OK is the refresh key and here it opens something.

Array order is the ranking§

{ "apps": [
    { "name": "Billy's Blackjack", "file": "billy.lua", "also": "billy.art",
      "category": "Games", "author": "Snail", "version": "1.0", "size": "15216",
      "desc": "One deck and a dealer who talks. Double down on the first two cards." },
    { "name": "Stocks", "file": "stocks.lua", "also": "",
      "category": "Connected", "author": "Snail", "version": "1.7", "size": "24423",
      "desc": "A watchlist you edit on the device, then one symbol a screen." }
] }
Every value is a string, including size — the extractor captures wire text, so a JSON type here would be a type nothing reads.

Categories appear in the order they first occur and the entries inside each keep the order the file gives them. There is no rank field and there should not be — a number beside an ordering is two orderings that can disagree. Reordering the file reorders the store.

There is no featured field any more. It flagged the storefront grid, and a flat list cannot promote an entry without moving it, which is what array order is for. Eight fields is the whole of what the device's extractor captures, so a ninth would have to displace one.

version is an opaque string of up to 11 characters, compared only for inequality: the device records what it installed and shows UPDATE whenever this file says something different. Any changed string in any format publishes an update. Never re-upload a changed file under an unchanged version, because the devices holding that version will not be offered it.

The apps page here is built from the same file at the same path, so the site and the device cannot drift.

An app is not always one file§

also names one more file, downloaded from the same directory and landed beside file. It is usually an .art sprite pack. It is a single name rather than a list because an array inside an element comes back empty from the extractor.

The install is atomic across the set. Every file is staged to name.part and nothing is renamed until all of them have arrived with the byte count the server declared; the companion is renamed first and the .lua file last, so the launcher cannot see an app before its art is already beside it. A failed download leaves the card exactly as it was.

There is no kind field. The extension says what the file is. When the launcher has no slot left for another card app the Store says so and refuses before raising the radio, because a file the launcher will not list is not worth the download.

What installing means§

A .lua file is source, and installing one from the internet is downloading text that a compiled interpreter will later read. It never runs on the CPU: the card is not in the address space. Reading the source, which every listing here shows whole, is the entire audit.

The interpreter opens four standard libraries — base, table, string and math — and removes dofile, loadfile and collectgarbage. A card app cannot open a file, write to the card, or load a C module. It can fetch, which is the one capability worth reading a source for: check the URLs before you install something. An .art pack is data, and every directory entry is validated against the file size before a byte is allocated.

The transport is pinned, not merely encrypted: the Store verifies snailos.org's certificate chain against a compiled-in root. A network that can answer for this domain would otherwise choose the device's app catalogue — a supply chain, and it would be an unauthenticated one. When the pinned root eventually expires, the Store fails closed: cached rows and card sideloading keep working, and installs over Wi-Fi stop until a new firmware release carries the next root.

Publishing your app§

Send the file — reply to your receipt, or use the address on the main page. Run it through lua-lint first (the agent page says where it lives). A published app is listed with its source readable in full.

Firmware is a cable§

A new Snail OS build is installed the way the first one was: ./install.sh over USB-C. Every published image is signed offline, and the verify page carries the public key, the SHA-256 of each image, and the two commands that check a file before it goes near the device.

Next: Compiled apps