From 92e868890eca3baa28de72b5db93ade503e3b0c3 Mon Sep 17 00:00:00 2001 From: Erwan Leboucher Date: Tue, 4 Aug 2026 10:19:43 +0200 Subject: [PATCH] feat(android): publish an Obtainium app config with each release --- .changeset/add-obtainium-app-config.md | 5 ++ .github/workflows/tauri-build.yml | 27 +++++++++++ README.md | 26 +++++++++++ scripts/obtainium-generate.js | 63 ++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 .changeset/add-obtainium-app-config.md create mode 100644 scripts/obtainium-generate.js diff --git a/.changeset/add-obtainium-app-config.md b/.changeset/add-obtainium-app-config.md new file mode 100644 index 0000000000..40ad03bcf6 --- /dev/null +++ b/.changeset/add-obtainium-app-config.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Publish an `obtainium.json` app config with every release so Android builds can be installed and auto-updated through Obtainium. diff --git a/.github/workflows/tauri-build.yml b/.github/workflows/tauri-build.yml index 3d976d4623..6d24d08200 100644 --- a/.github/workflows/tauri-build.yml +++ b/.github/workflows/tauri-build.yml @@ -408,6 +408,33 @@ jobs: gh release upload "$TAG" "$f" --clobber done + obtainium: + name: Publish Obtainium config + needs: [setup-release, android] + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + env: + TAG: ${{ needs.setup-release.outputs.tag }} + VERSION: ${{ needs.setup-release.outputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ needs.setup-release.outputs.ref }} + persist-credentials: false + + - name: Generate obtainium.json + shell: bash + run: node scripts/obtainium-generate.js "$VERSION" "$TAG" obtainium.json + + - name: Attach Obtainium config to release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "$TAG" obtainium.json --clobber + ios: name: Build iOS (AltStore/SideStore) needs: setup-release diff --git a/README.md b/README.md index 787f27bee6..a839b7e572 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,32 @@ The stable web app is available at [app.sable.moe](https://app.sable.moe/) and t You can also download our desktop app for Windows and Linux from [releases](https://github.com/SableClient/Sable/releases/latest). Release artifacts include build attestations, and desktop installations update automatically. +## Android (Obtainium) + +Android APKs are published to every release, and [Obtainium](https://obtainium.imranr.dev) keeps them updated straight from GitHub. Each release also ships an `obtainium.json` app config. Use it for the nightly channel, where prereleases and date-based version tracking have to be enabled to follow the rolling `nightly` tag. + +### Stable + +Add to Obtainium +  +App config +  +Download APK + +### Nightly + +App config +  +Download APK + +### Setup & install + +1. Install [Obtainium](https://github.com/ImranR98/Obtainium/releases/latest). +2. For stable, tap the button above, or add `https://github.com/SableClient/Sable` by hand under **Add App**. +3. For nightly, download the `obtainium.json` above and import it with **Import/Export → Import from file**. + +Android builds are produced by the `android` job in [`tauri-build.yml`](.github/workflows/tauri-build.yml), and the config by the `obtainium` job in the same workflow. + ## iOS (AltStore / SideStore) Sable iOS builds are distributed as unsigned IPAs through [AltStore](https://altstore.io) and [SideStore](https://sidestore.io). Each release publishes both the IPA and an `altstore-source.json` manifest — stable builds to the [latest GitHub release](https://github.com/SableClient/Sable/releases/latest), nightly builds to the [`nightly` GitHub release](https://github.com/SableClient/Sable/releases/tag/nightly). diff --git a/scripts/obtainium-generate.js b/scripts/obtainium-generate.js new file mode 100644 index 0000000000..bd7ec703cf --- /dev/null +++ b/scripts/obtainium-generate.js @@ -0,0 +1,63 @@ +#!/usr/bin/env node +//MISE hide=true +//MISE description="Generate obtainium.json for one-click app import" +/* oxlint-disable no-console */ + +import { writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import process from 'node:process'; + +const version = process.argv[2]; +const tag = process.argv[3] || `v${version}`; +const outputPath = process.argv[4] || 'obtainium.json'; + +if (!version) { + console.error('Usage: node scripts/obtainium-generate.js [tag] [output-path]'); + process.exit(1); +} + +const GITHUB_REPO = 'SableClient/Sable'; +const APK_NAME = `Sable-${version}-android-universal.apk`; +const APK_URL = `https://github.com/${GITHUB_REPO}/releases/download/${tag}/${APK_NAME}`; +const isNightly = tag === 'nightly'; + +// Obtainium fills in the rest from its own defaults on import. +const additionalSettings = { + about: 'An almost stable Matrix client', + ...(isNightly && { + includePrereleases: true, + // The nightly tag name never changes, so the re-uploaded APK's date is the + // only usable version, which in turn needs pseudo-versioning. + useLatestAssetDateAsReleaseDate: true, + releaseDateAsVersion: true, + versionDetection: false, + }), +}; + +const config = { + apps: [ + { + id: 'moe.sable.client', + url: `https://github.com/${GITHUB_REPO}`, + author: 'SableClient', + name: 'Sable', + installedVersion: null, + latestVersion: version, + apkUrls: JSON.stringify([[APK_NAME, APK_URL]]), + otherAssetUrls: '[]', + preferredApkIndex: 0, + additionalSettings: JSON.stringify(additionalSettings), + lastUpdateCheck: null, + pinned: false, + categories: ['Communication'], + releaseDate: null, + changeLog: null, + overrideSource: 'GitHub', + allowIdChange: false, + pendingRepoRenameUrl: null, + }, + ], +}; + +writeFileSync(resolve(outputPath), JSON.stringify(config, null, 2) + '\n'); +console.log(`Generated ${outputPath}`);