Built-in App Store
The App Store itself is a built-in Rust app (tk_appstore) built on thistle-tk. It shows ratings, download counts, categories, and changelogs. Component-level driver detection ensures only compatible entries are offered for your hardware.

Catalog format

A catalog is a single JSON file. The top-level object contains metadata and an array of entries:

{
  "catalog_version": 2,
  "name": "ThistleOS Official",
  "url": "https://wan0net.github.io/thistle-apps/catalog.json",
  "updated": "2026-03-22",
  "entries": [
    {
      "id":               "com.example.chat",
      "name":             "LoRa Chat",
      "version":          "1.2.0",
      "type":             "app",
      "arch":             ["xtensa", "riscv"],
      "url":              "https://example.com/apps/chat-1.2.0.app.elf",
      "sha256":           "a3f1b29c...",
      "sig_url":          "https://example.com/apps/chat-1.2.0.app.elf.sig",
      "size_bytes":       48320,
      "permissions":      ["radio", "ipc"],
      "description":      "Send and receive LoRa messages with nearby devices.",
      "author":           "Example Dev",
      "min_os":           "0.2.0",
      "category":         "communication",
      "rating":           4.7,
      "download_count":   1204,
      "changelog":        "v1.2.0: Group chat support, RSSI display",
      "compatible_boards": ["tdeck_pro", "tdeck", "t3s3"],
      "detection": {
        "requires_i2c": [],
        "requires_hal": ["radio"]
      }
    }
  ]
}

Entry field reference

FieldTypeRequiredDescription
idstringyesReverse-DNS identifier. Must be globally unique.
namestringyesHuman-readable display name.
versionstringyesSemantic version (MAJOR.MINOR.PATCH).
typestringyes"app", "driver", or "firmware".
urlstringyesHTTPS URL to the binary.
sha256stringyesHex SHA-256 of the binary at url.
sig_urlstringnoURL to the .sig file (Ed25519 signature).
size_bytesnumbernoDownload size hint for progress display.
permissionsarraynoPermission strings the app declares it needs.
descriptionstringnoShort description shown in the store UI.
authorstringnoAuthor name or organization.
min_osstringnoMinimum ThistleOS version required.
archarraynoTarget architectures: "xtensa", "riscv". Omit = all.
categorystringnoApp category for store browsing (e.g. "communication").
ratingnumbernoAverage star rating (0.0–5.0).
download_countnumbernoTotal install count shown in the store UI.
changelogstringnoShort description of changes in this version.
compatible_boardsarraynoBoard IDs this entry is compatible with. Used by Recovery for driver matching.
detectionobjectnoComponent-level hardware detection hints. requires_i2c: I2C addresses; requires_hal: HAL interface names.

Entry types

app — A .app.elf file. Downloaded to /sdcard/apps/. The kernel's ELF loader runs it. Must implement thistle_app_t.

driver — A .drv.elf file. Downloaded to /sdcard/drivers/. Loaded on next boot, before app loading. Must export driver_init().

firmware — A full .bin firmware image. Downloaded to /sdcard/update/thistle_os.bin. The OTA subsystem picks it up on next boot and flashes it to ota_1. This is how ThistleOS updates itself over the air.

Hosting your own catalog

A catalog is just a static JSON file served over HTTPS. GitHub Pages is the easiest option:

  1. Create a GitHub repo (e.g., myname/thistle-apps)
  2. Enable GitHub Pages from the docs/ directory or root
  3. Add your catalog.json and binary files to the repo
  4. Your catalog URL is https://myname.github.io/thistle-apps/catalog.json

The device will periodically fetch this URL (configurable in /sdcard/config/system.json) and check for newer versions of installed entries.

Tip
Name your binaries with versions in the filename (e.g., chat-1.2.0.app.elf) so that multiple versions can coexist in the same directory.

Catalog caching

The device caches the last-fetched catalog on the SD card at /sdcard/data/catalog_cache.json. This allows the app store UI to show available apps even when WiFi is not connected. The cache is invalidated when the device successfully fetches a newer version (determined by the updated field).

Download and verification flow

User selects entry in App Store UI │ ▼ [1] HTTP GET binary URL │ ▼ [2] Compute SHA-256 of downloaded bytes ├─ SHA-256 mismatch? ─► abort, show error, delete partial file │ ▼ [3] sig_url present? ├─ NO ─► install with zero permissions (unsigned) │ ▼ [4] HTTP GET sig_url │ ▼ [5] Verify signature against developer public key ├─ Signature invalid? ─► abort, delete binary, show error │ ▼ [6] Grant declared permissions to the app │ ▼ [7] Copy binary to /sdcard/apps/ (or /drivers/ or /update/) │ ▼ [8] Notify user — app appears in launcher on next launch

Publishing apps: step by step

  1. Build your .app.elf — see Building Apps
  2. Compute the SHA-256: sha256sum my_app.app.elf
  3. Sign the binary: thistle-sign --key dev_key.pem my_app.app.elf — produces my_app.app.elf.sig
  4. Upload both files to your HTTPS host (GitHub Pages, S3, Cloudflare Pages, etc.)
  5. Add an entry to your catalog.json with the correct URL and SHA-256
  6. Commit and push — devices will find the new entry on their next catalog refresh

Firmware updates via the app store

Firmware updates work exactly like app downloads, but with "type": "firmware" and a .bin extension. The update flow:

  1. User opens App Store → sees "ThistleOS vX.Y.Z available"
  2. User taps "Update" — firmware binary downloaded and verified
  3. Binary written to /sdcard/update/thistle_os.bin
  4. Device reboots — Recovery OS checks SD card, finds the binary, flashes to ota_1
  5. Device boots into new firmware — ota_mark_valid() called to confirm success

If the firmware entry's sig_url is present and the signature does not verify, the download is rejected before the file is written to the SD card.

Firmware signing is mandatory
Unsigned firmware entries are rejected outright — the device will not install them regardless of the user's choice. Always sign firmware updates with your development key.

Adding the official catalog

The official ThistleOS catalog is pre-configured in /sdcard/config/system.json:

{
  "app_store": {
    "catalogs": [
      "https://wan0net.github.io/thistle-apps/catalog.json"
    ],
    "refresh_interval_hours": 12
  }
}

To add a third-party catalog, append its URL to the catalogs array. The device merges entries from all catalogs. If two catalogs provide the same id, the entry with the higher version wins.