Skip to content

Releases ​

ngit release adds GitHub Releases-like functionality to a Nostr repository: versioned release notes with downloadable files, published from your machine or from CI. It works for any software, and Android APKs published this way appear in Zapstore.

Files are uploaded to multiple Blossom servers, simple HTTP file hosts for Nostr. Each file is addressed by its hash rather than by where it lives, so any server holding a copy can serve it, anyone can verify the download, and no single host can take the release down. The release metadata is signed with your key and published as Nostr events using the NIP-82 software publishing protocol, so it does not depend on any one forge either.

Publish a one-off release ​

Name the version, the file, and the notes:

bash
ngit release publish 1.0.0 \
  --file dist/example-app-1.0.0.apk \
  --notes-file release-notes.md \
  --zapstore-relay

The release records the commit you are on. Add --tag v1.0.0 to record the commit that tag points to instead, or --commit for any other commit.

ngit uploads the file to your Blossom servers and publishes the release as signed Nostr events. For an APK, ngit reads the package details, SDK levels, signing certificate, and supported ABIs from the file itself. --zapstore-relay also sends the release to the Zapstore catalog; see Zapstore compatibility.

This form is useful for an experiment. Once the process repeats, the committed manifest described next is clearer.

Keep one publishing identity

Unlike the repository, an application has one owner. Every release for it must be signed by the same npub as the first. Pick a long-lived signer before you publish, and give release automation access to it.

Create .ngit/release.yaml ​

For repeatable releases, commit a manifest at .ngit/release.yaml. It lists the files to publish and holds the settings that stay the same from release to release, so the command itself needs nothing but a version.

This example publishes a command-line tool for two platforms and a checksum file:

yaml
schema: 1
application: my-tool
name: My Tool
summary: A short description
repository: nostr://npub1.../my-tool
release_notes: CHANGELOG.md
publication:
  blossom_servers:
    - https://blossom.example.com
    - https://mirror.example.com
assets:
  - file: dist/my-tool-{version}-linux-x86_64
    platforms: [linux-x86_64]
  - file: dist/my-tool-{version}-macos-aarch64
    platforms: [macos-aarch64]
  - file: dist/checksums.txt
    platform_agnostic: true

Replace the repository URL and Blossom servers with the ones you use, and list more than one server so the files stay available if one goes away. {version} expands to the release version, so version 1.0.0 reads dist/my-tool-1.0.0-linux-x86_64. release_notes: CHANGELOG.md takes the section for that version from a Keep a Changelog file; use notes for literal text instead.

For an Android app, release_source is the Zapstore-compatible shorthand for one local APK. ngit reads the package details, platforms, and signing certificate from the file itself, so this is a complete manifest:

yaml
schema: 1
identifier: com.example.myapp
name: Example App
summary: A short description for app listings
repository: nostr://npub1.../example-app
release_notes: CHANGELOG.md
release_source: dist/example-app-{version}.apk
publication:
  blossom_servers:
    - https://blossom.example.com
    - https://mirror.example.com
  zapstore_relay: true

Every field, including application metadata, URL-backed assets, and Android options, is documented on The release manifest.

Publish from the manifest ​

Build the files at the paths the manifest declares, then publish:

bash
ngit release publish

With no version argument, ngit reads the version from the Git tag on the current commit, so tag v1.0.0 gives version 1.0.0. The command finds .ngit/release.yaml, uploads the files it lists, and publishes the linked application, asset, and release records. There is no hidden source for the binaries.

If the commit has no tag, pass the version explicitly or tag the commit first. If it has more than one tag, select one with --tag. An explicitly supplied version is not normalised: 1.0.0 and v1.0.0 identify different releases. Only a version derived from a tag strips one leading lowercase v.

Use --manifest <PATH> for a manifest outside the default location. Automatic discovery applies when creating a release without direct asset flags; pass --manifest explicitly when combining manifest and CLI assets or when editing.

Several applications in one repository ​

A repository can publish more than one application, which suits a monorepo with, say, a command-line tool and an Android app. Give each its own manifest with application set, and choose the manifest when publishing:

bash
ngit release publish 1.2.0 --manifest .ngit/release-cli.yaml --tag cli-v1.2.0

Pass the version explicitly here, since a prefixed tag like cli-v1.2.0 is not reduced to 1.2.0 automatically. --tag still pins the commit and fills {tag} in the manifest. Each application has its own owner, so the publishing identity rule applies per application.

Zapstore compatibility ​

Zapstore reads the same NIP-82 records ngit publishes, so there is no separate format to produce. The manifest reuses Zapstore-compatible names where their meanings agree, including identifier, application metadata, and release_source.

Set publication.zapstore_relay: true, as in the manifest above, or pass --zapstore-relay to publish the complete release batch to the Zapstore catalog relay. This is additive: it keeps the repository and application relays, and it does not silently change the Blossom servers.

After Zapstore's one-time publisher acceptance and APK certificate-linking steps are complete, APK releases published this way become available in Zapstore automatically. You do not need a second zsp publish for each version. New publishers should complete Zapstore's publisher setup; ngit does not perform those onboarding steps.

Application identity ​

ngit represents each publication as three related records:

  • an application identifies the software and its publication authority;
  • a release identifies an exact version, source commit, channel, and notes;
  • an asset describes a downloadable file referenced by a release.

The first manifest-backed publication can create the linked application from repository, APK, and manifest metadata. Use the explicit application flow only when you want to define or inspect that identity before publishing:

bash
ngit release app init
ngit release app view <APP>

An application can also be linked to an existing repository.

Add downloadable assets ​

Attach local files while publishing with repeatable --file options:

bash
ngit release publish 1.0.0 \
  --file linux-x86_64=dist/my-tool \
  --file macos-aarch64=dist/my-tool-macos

The PLATFORM=PATH prefix associates each file with a target platform. The command also supports URL-backed assets and existing asset events; see the generated ngit release publish reference.

ngit attempts every requested Blossom server. Publication can continue once each blob has at least one exact confirmed copy, with incomplete replication reported as a warning. Automation that requires every requested placement must inspect the JSON warnings and placement results rather than relying on a zero exit status alone.

To attach one asset after publication, use the explicit ngit release asset add workflow. It requires --edit because it replaces the release record.

Inspect and verify ​

Populate the cache from relays, then inspect a release and its assets:

bash
ngit release list
ngit release view 1.0.0 --verify

--verify downloads referenced assets and verifies their hashes and sizes. After the online read, --offline can skip another relay fetch:

bash
ngit release view 1.0.0 --verify --offline

--verify still downloads the release assets. Here, offline describes event retrieval, not asset verification.

For automation, request one JSON document:

bash
ngit release view 1.0.0 --verify --json

If a bare version is ambiguous, provide --app <APP>.

Editing is explicit ​

Publishing an existing version requires --edit; it does not silently replace a release. Review the existing record first:

bash
ngit release view 1.0.0
ngit release publish 1.0.0 --edit --notes-file corrected-notes.md

Containers ​

Use ngit container publish when the result should be a pullable OCI image. It uploads the OCI content graph to Blossom and publishes a signed, mutable tag map through Nostr. This is separate from attaching a container archive to a release: a release asset is a downloadable file, not a pullable container repository.

Container tag maps follow the same single publishing identity rule as releases.

See the generated ngit container publish reference for layouts, .ngit/containers.yaml, Blossom placement, tag updates, and JSON output.

Nsites ​

Static websites are not software release records, but publishing them often lives beside release automation. The nsite guide explains when to use the dedicated nsyte CLI, when the focused ngit nsite publish command is a better fit, and how to turn pull-request builds into disposable GitWorkshop previews.

Exact reference ​