npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tauri-plugin-ota-self-update-api

v0.2.1

Published

JavaScript API for tauri-plugin-ota-self-update - self-hosted OTA web asset updates for Tauri v2 apps.

Readme

npm version Crates.io Documentation GitHub issues GitHub stars Donate

Tauri Plugin OTA Self Update

Self-hosted OTA updates for Tauri v2 web assets.
This project provides:

  • A Rust plugin for runtime update checks and apply flow.
  • A guest JS API package for frontend usage.
  • A universal GitHub Action (action.yml) for publishing OTA artifacts.

Table of contents

  1. Features
  2. Platform support
  3. Installation
  4. Usage
  5. Manifest contract
  6. Rust-side access
  7. Publishing
  8. GitHub Action
  9. Landing
  10. Permissions
  11. Security
  12. Development
  13. License

Features

  • Self-hosted OTA model (no vendor cloud lock-in).
  • Update channels (stable, beta, custom).
  • JS API: check(), checkWithMeta(), setChannel(), getCurrentVersion(), Update.apply().
  • Rust-side access via app.ota_self_update().
  • Single Rust runtime is used on all targets (no separate Kotlin/Swift OTA bridge required).
  • Multiple publish targets: GitHub Releases, Bitbucket Downloads, S3-compatible, custom HTTP server.
  • Marketplace-style reusable GitHub Action in repo root (action.yml).

Platform support

| Platform | Status | |----------|--------| | macOS | Supported | | Windows | Supported | | Linux | Supported | | Android | Supported | | iOS | Supported |

Update providers

| Provider | Status | Notes | |----------|--------|-------| | GitHub Releases | Supported | Runtime queries GitHub Releases API and selects a release that contains channel manifest asset (stable.json / beta.json). | | Bitbucket Downloads | Supported | Publisher uploads archive + channel manifest (stable.json/beta.json) to repo downloads; runtime resolves manifest from Bitbucket downloads URL. | | S3-compatible storage | Supported | Publisher uploads manifest/archive and maintains releases.json; runtime resolves latest released entry for channel/track, then falls back to manifest/<channel>.json. | | Custom HTTP server | Supported | Same resolution model as S3 (releases.json with status filtering, fallback to manifest/<channel>.json) plus token-protected upload/admin API. |


Installation

Automatic (recommended)

From your Tauri app root (where package.json and the tauri script live):

pnpm run tauri add ota-self-update

Rust (src-tauri/Cargo.toml)

[dependencies]
tauri-plugin-ota-self-update = "0.1"

JavaScript

pnpm add tauri-plugin-ota-self-update-api

Usage

Backend initialization

let context = tauri::generate_context!();
let (ota_plugin, context) = tauri_plugin_ota_self_update::init(context);

tauri::Builder::default()
  .plugin(ota_plugin)
  .run(context)
  .expect("error while running tauri application");

Plugin config (tauri.conf.json)

{
  "plugins": {
    "ota-self-update": {
      "baseUrl": "https://updates.example.com/ota",
      "pubkey": "",
      "channel": "stable",
      "timeoutSecs": 30,
      "activationPolicy": "nextLaunch",
      "requestHeaders": {
        "x-tenant": "acme"
      }
    }
  }
}

GitHub mode note:

  • Set baseUrl to https://github.com/<owner>/<repo>.
  • stable channel resolves the latest non-prerelease release asset stable.json.
  • beta channel resolves the latest prerelease asset beta.json.

activationPolicy values:

  • nextLaunch: apply assets and activate them on next app start.
  • softReload: apply assets and mark as active immediately for runtime reload flows.

Frontend flow

import { check, getCurrentVersion, setChannel } from "tauri-plugin-ota-self-update-api";

await setChannel("stable");
const update = await check();
if (update) {
  const applyResult = await update.apply();
  const version = await getCurrentVersion();
  console.log("Effective OTA version:", version.effectiveVersion, "source:", version.source);
  if (applyResult.status === "appliedNow") {
    location.reload();
  }
}

Periodic check example

import { check, getCurrentVersion, setChannel } from "tauri-plugin-ota-self-update-api";

const CHECK_INTERVAL_MS = 15 * 60 * 1000; // every 15 minutes

async function checkAndApplyUpdate() {
  try {
    await setChannel("stable");
    const update = await check();
    if (!update) return;

    const result = await update.apply();
    const version = await getCurrentVersion();
    console.log("OTA effective version:", version.effectiveVersion);
    // For softReload policy, refresh immediately to use new assets.
    if (result.status === "appliedNow") {
      location.reload();
    }
  } catch (error) {
    console.error("OTA periodic check failed:", error);
  }
}

// run once on app start
void checkAndApplyUpdate();
// run periodically
setInterval(() => {
  void checkAndApplyUpdate();
}, CHECK_INTERVAL_MS);

Auto-update on startup example (silent)

import { check, getCurrentVersion, setChannel } from "tauri-plugin-ota-self-update-api";

export async function runStartupOta() {
  // shows effective version from plugin (native or OTA)
  console.log(await getCurrentVersion());

  await setChannel("stable");
  const update = await check();
  if (!update) return;

  const result = await update.apply();
  if (result.status === "appliedNow") {
    // Soft reload policy: activate now.
    location.reload();
  }
  // Next launch policy: assets are already cached as active OTA payload and
  // will be used on next app start automatically.
}

Manifest contract

Example manifest/stable.json:

{
  "version": "1.2.3",
  "notes": "Bugfixes and UX improvements",
  "pubDate": "2026-05-28T10:00:00.000Z",
  "signature": "",
  "archiveSignature": "",
  "archiveUrl": "https://updates.example.com/ota/stable/ota-dist-1.2.3.tar.gz"
}

Rust-side access

You can call plugin logic from Rust commands/plugins through the extension trait:

use tauri::Manager;
use tauri_plugin_ota_self_update::OtaSelfUpdateExt;

#[tauri::command]
async fn switch_channel(app: tauri::AppHandle, channel: String) -> Result<(), String> {
  app.ota_self_update()
    .set_channel(Some(channel))
    .await
    .map_err(|e| e.to_string())
}

Publishing

Local publisher script:

OTA_PUBLISH_MODE=github \
OTA_CHANNEL=stable \
OTA_VERSION=1.2.3 \
OTA_BASE_URL=https://updates.example.com/ota \
OTA_TARGET_REPO=owner/repo \
pnpm run ota:publish

Modes:

  • github: uses GitHub REST API via native fetch.
  • bitbucket: uploads artifacts to Bitbucket Downloads.
  • s3: uses AWS SDK v3 (@aws-sdk/client-s3).
  • server: uses native fetch PUT for archive + manifest upload.

Cross-repo publish (private -> public) is supported in github mode:

  • Build in a private repository, publish OTA assets to another repository via OTA_TARGET_REPO=owner/public-repo.
  • Use OTA_GITHUB_TOKEN (or GITHUB_TOKEN/GH_TOKEN) with write access to the target repository releases.
  • In GitHub Actions, default GITHUB_TOKEN is often scoped to the current repo only; use a PAT/FGPAT secret for cross-repo publish.

Example (private CI -> public OTA repo):

OTA_PUBLISH_MODE=github \
OTA_CHANNEL=stable \
OTA_VERSION=1.2.3 \
OTA_TARGET_REPO=owner/public-repo \
OTA_GITHUB_TOKEN=ghp_xxx \
pnpm run ota:publish

For s3 and server modes, publisher also maintains releases.json index:

  • stable resolves latest non-prerelease entry.
  • beta resolves latest prerelease entry.
  • Client falls back to manifest/<channel>.json when releases.json is unavailable.

Bitbucket mode example:

OTA_PUBLISH_MODE=bitbucket \
OTA_CHANNEL=stable \
OTA_VERSION=1.2.3 \
OTA_BITBUCKET_REPO=workspace/repo \
OTA_BITBUCKET_USERNAME=your-user \
OTA_BITBUCKET_APP_PASSWORD=your-app-password \
pnpm run ota:publish

Alternative auth for Bitbucket mode:

  • OTA_BITBUCKET_TOKEN (Bearer token), or
  • OTA_BITBUCKET_USERNAME + OTA_BITBUCKET_APP_PASSWORD.

GitHub Action

This repository exposes a reusable action in action.yml.

- name: Publish OTA
  uses: s00d/tauri-plugin-ota-self-update@v1
  with:
    mode: github
    channel: stable
    version: 1.2.3
    dist_dir: dist
    base_url: https://updates.example.com/ota
    target_repo: owner/repo

Primary inputs:

  • mode: github | bitbucket | s3 | server (required)
  • version: OTA version (required)
  • channel, dist_dir, out_dir, base_url, notes
  • target_repo, release_tag, github_token (github mode)
  • bitbucket_repo, bitbucket_token, bitbucket_username, bitbucket_app_password (bitbucket mode)
  • s3_bucket (s3 mode)
  • server_token (server mode)
  • manifest_signature, archive_signature
  • dry_run (true|false)

Validation workflow example is provided at .github/workflows/example-build.yml.

Cross-repo action usage notes:

  • target_repo can point to a different repository than the workflow repository.
  • For cross-repo publish, pass a PAT/FGPAT via github_token that has release write permissions on target_repo.

Self-hosted OTA server (Docker)

This repository includes a ready-to-run OTA server in server/.

  • Server source: scripts-src/ota-server.ts
  • Dashboard UI source: scripts-src/server-ui/index.html, scripts-src/server-ui/dashboard.js
  • Built server runtime: server/ota-server.cjs
  • Built dashboard assets: server/dist/*
  • Docker image spec: server/Dockerfile
  • Docker Compose: server/docker-compose.yml
  • Upload auth: Authorization: Bearer <OTA_SERVER_TOKEN>

Quick start:

pnpm run build:scripts
docker build -t ota-self-update-server -f server/Dockerfile server
docker run --rm -p 8080:8080 \
  -e OTA_SERVER_TOKEN=super-secret \
  -v "$(pwd)/.ota-server-data:/data/ota" \
  ota-self-update-server

Local run without Docker:

pnpm run build:scripts
OTA_SERVER_TOKEN=super-secret \
PORT=8080 \
OTA_DATA_DIR=.ota-server-data \
pnpm run server:start

Generate OpenAPI file on startup:

OTA_SERVER_TOKEN=super-secret \
OTA_OPENAPI_OUTPUT=.ota-server-data/openapi.json \
pnpm run server:start

OpenAPI endpoint is always available at:

GET /openapi.json

Interactive online docs:

GET /docs

Then set plugin config:

{
  "plugins": {
    "ota-self-update": {
      "baseUrl": "https://your-server.example.com",
      "channel": "stable"
    }
  }
}

Use publisher action/script in mode=server with server_token equal to OTA_SERVER_TOKEN. Server dashboard:

  • GET / - web UI with token auth for release lifecycle operations.
  • GET /api/info - release counters and runtime info (requires token).
  • GET /api/releases - full release list (requires token).
  • POST /api/releases/:channel/:version/confirm - publish draft.
  • POST /api/releases/:channel/:version/revoke - revoke published version.
  • DELETE /api/releases/:channel/:version?purge=true - remove entry and OTA files.

Publish to this server (manual example):

OTA_PUBLISH_MODE=server \
OTA_BASE_URL=http://127.0.0.1:8080 \
OTA_SERVER_TOKEN=super-secret \
OTA_CHANNEL=stable \
OTA_VERSION=0.2.1 \
OTA_RELEASE_STATUS=released \
OTA_DIST_DIR=examples/tauri-app/dist \
pnpm run ota:publish

Beta/pre-release example:

OTA_PUBLISH_MODE=server \
OTA_BASE_URL=http://127.0.0.1:8080 \
OTA_SERVER_TOKEN=super-secret \
OTA_CHANNEL=beta \
OTA_VERSION=0.2.1-beta.1 \
OTA_RELEASE_STATUS=draft \
OTA_DIST_DIR=examples/tauri-app/dist \
pnpm run ota:publish

The server stores:

  • manifest/stable.json and manifest/beta.json
  • <channel>/ota-dist-<version>.tar.gz
  • releases.json index (used by client to resolve latest stable/beta)

Landing

A dedicated multi-page marketing + docs portal is included in landing/ with EN/RU translations.

  • Stack: Vue 3 + Vite + Tailwind CSS v4
  • i18n: content-driven (site.en.ts / site.ru.ts)
  • Routes:
    • Marketing home: / and /ru
    • Full docs portal: /docs and /ru/docs
    • Docs pages (examples): installation, quick-start, channels-lifecycle, publisher-modes, server-dashboard, troubleshooting
  • Local run:
    • pnpm --dir landing dev
  • Build:
    • pnpm --dir landing build
  • Deployment:
    • Separate workflow: .github/workflows/landing-pages.yml
    • Triggers: workflow_dispatch and version tags v*

Permissions

Default permission set: ota-self-update:default.
Granular permissions are generated under permissions/ for:

  • check_for_updates
  • apply_update
  • set_channel

Security

  • Always set pubkey and signatures in production.
  • Empty pubkey or empty signatures skip verification (development-only behavior).
  • Keep release keys in repository/org secrets and rotate periodically.

Development

cargo check
pnpm install
pnpm run build
cargo check --manifest-path examples/tauri-app/src-tauri/Cargo.toml

License

MIT OR Apache-2.0