pakstr
v0.27.0
Published
CLI for packaging Nostr web apps into Android APKs
Readme
Pakstr
Pakstr turns your web app into a signed, installable Android APK — and publishes it to Zap Store — in one CI step, with one secret.
Pakstr takes a folder of built web assets (a dist/ containing an index.html) and produces a signed Android APK. You bring one secret — a Nostr nsec — and run one command: pakstr run. No Android SDK, no Gradle, no Java, and no keystores to manage locally.
The same nsec serves two core purposes:
- Deterministically derives the APK signing key – the same
nsec+ app ID always produces the same signature, allowing seamless application updates across releases. - Authenticates the Zap Store publish – handles the authorization needed to publish your app to the store.
Status
Pakstr is currently in early development (0.x). The CLI API and configuration format may change before 1.0.0.
Requirements
- Node.js 22+
- Docker Desktop
Note: No Android SDK, Gradle, Java, or Android keystore setup is required locally.
Quick Start
Run the following commands from your web app project root:
1. Initialize the project
npx pakstr initThis creates pakstr.yaml, zapstore.yaml, and a .env file (automatically added to .gitignore) containing your PAKSTR_NSEC.
2. Configure your web assets
Adjust the paths inside the generated pakstr.yaml file:
build:
web: ./dist
out: ./build/app.apk
builder: docker
# runtime:
# apiBase: https://api.example.com # Optional packaged /api/* proxy target.runtime.apiBase must be an absolute HTTPS URL. It is the immutable packaged/default proxy target and public APK metadata, not a secret. The effective target precedence is persistent runtime override, then packaged default, then disabled. A web app may call window.PakstrBridge.setApiBaseUrl(url) to validate and synchronously persist an HTTPS override, and window.PakstrBridge.getApiBaseUrl() to read the effective URL. Developer Tools uses the same setting. Reset removes the override and restores the packaged default; without a packaged default it disables /api/*. Changes affect the next proxy request without an app restart, LocalServer restart, or WebView reload.
3. Build, sign, and publish
npx pakstr runCommands
npx pakstr init— Initialize a Pakstr project.npx pakstr sign— Sign the APK using the key derived from yournsec.npx pakstr publish— Publish the signed APK to Zap Store.npx pakstr delete --app <appId>— Inventory app-scoped Zapstore deletion; add--executeto submit NIP-09 requests, and--delete-blobsto include eligible Blossom release blobs.npx pakstr delete --publisher— Inventory every event authored by the configured publish nsec; add--executefor the publisher-wide NIP-09 deletion request.npx pakstr run— Execute the full pipeline at once (build + sign + publish).
Delete commands are non-destructive by default. --app and --publisher are mutually exclusive, and Blossom HTTP deletion requires --app --execute --delete-blobs. NIP-09 is best-effort: relay acceptance does not prove physical erasure, and discovery is limited to events retained and returned by the configured relay.
CI/CD
Set PAKSTR_NSEC as a CI secret in your repository provider (e.g., GitHub Actions) and run:
npm run build
npx pakstr runIf your CI runs the job inside a sandbox with its own kernel - a Kata microVM, or any
runner where the job container is given no host Docker socket - the job has to start its
own Docker daemon, and that daemon's default bridge cannot be brought up inside the
sandbox. Set PAKSTR_DOCKER_NETWORK=host so every container pakstr creates joins the
sandbox's own network namespace instead:
PAKSTR_DOCKER_NETWORK=host npx pakstr runIt applies to the build container, the signing container and the short-lived copy containers alike, because all of them are created the same way. Leave it unset on a workstation, where Docker's default network is what you want - host networking is not available on Docker Desktop for macOS.
Documentation & License
- See
docs/SPEC.mdfor the full configuration and signing specification. - License: MIT
