First public release — git-nostr-sign v0.2.1
If you live in both worlds (Git + Nostr), you have probably wished your commit signatures were just another signed event — same keys, same mental model, no GPG keyserver theatre. This tool is that experiment shipped.
What it does
— Each commit (and tag) gets a signature Git understands: OpenPGP-shaped armor around a JSON payload that is, under the hood, a Nostr event (kind 1639). git verify-commit and git log --show-signature work the way your muscle memory expects.
— Verification is nostr-tools + the commit bytes — no keyring sync, no platform TOS as your trust root for the crypto.
How it ships (why Nostr people should care)
— There is no npm registry artifact to trust blindly. Releases are announced as kind 1063 “package” events, signed by the publisher key you already follow or verify out-of-band. The tarball bytes live on Blossom (and mirrors); the event carries the sha256 you check before install.
— Bootstrap is intentionally boring: curl a small install.sh from HTTPS (same domain as NIP-05 when you set it up that way), then the script pulls the latest matching release from relays. Upgrades are git-nostr-update — same pipeline, no git pull of our repo required for end users.
— Publisher npub for this line of releases: @git-nostr-sign
How you sign
— Local nsec on the machine, or Amber over NIP-46 (including Nostr Connect QR in the setup wizard) so the signing key never leaves the phone if you prefer that threat model.
This is our first broad invitation to try it on real repos and CI boxes. Expect rough edges; issues and patches welcome wherever you track the source.
Install (one line):
curl -fsSL https://nostr-svrn.codeberg.page/install.sh | sh -s -- --npub git-nostr-sign@nostr-svrn.codeberg.page
$ docker run -it alpine /bin/ash -c "apk update && apk add curl nodejs npm && curl -fsSL https://nostr-svrn.codeberg.page/install.sh | sed 's/set -e/set -ex/' | sh" v3.23.3-413-gfc223ba99f6 [https://dl-cdn.alpinelinux.org/alpine/v3.23/main] v3.23.3-414-gbbf576395af [https://dl-cdn.alpinelinux.org/alpine/v3.23/community] OK: 27583 distinct packages available ( 1/21) Installing brotli-libs (1.2.0-r0) ( 2/21) Installing c-ares (1.34.6-r0) ( 3/21) Installing libunistring (1.4.1-r0) ( 4/21) Installing libidn2 (2.3.8-r0) ( 5/21) Installing nghttp2-libs (1.68.0-r0) ( 6/21) Installing nghttp3 (1.13.1-r0) ( 7/21) Installing libpsl (0.21.5-r3) ( 8/21) Installing zstd-libs (1.5.7-r2) ( 9/21) Installing libcurl (8.17.0-r1) (10/21) Installing curl (8.17.0-r1) (11/21) Installing ca-certificates (20251003-r0) (12/21) Installing libgcc (15.2.0-r2) (13/21) Installing libstdc++ (15.2.0-r2) (14/21) Installing ada-libs (3.3.0-r0) (15/21) Installing icu-data-en (76.1-r1) Executing icu-data-en-76.1-r1.post-install * * If you need ICU with non-English locales and legacy charset support, install * package icu-data-full. * (16/21) Installing icu-libs (76.1-r1) (17/21) Installing simdjson (3.12.0-r0) (18/21) Installing simdutf (7.5.0-r1) (19/21) Installing sqlite-libs (3.51.2-r0) (20/21) Installing nodejs (24.14.1-r0) (21/21) Installing npm (11.11.0-r0) Executing busybox-1.37.0-r30.trigger Executing ca-certificates-20251003-r0.trigger OK: 86.0 MiB in 37 packages + PUBLISHER_NPUB=git-nostr-sign@nostr-svrn.codeberg.page + RELAY=wss://relay.damus.io + BOLD='\033[1m' + GREEN='\033[32m' + YELLOW='\033[33m' + RED='\033[31m' + RESET='\033[0m' + '[' 0 -gt 0 ] + printf '\n\033[1m╔══════════════════════════════════════════╗\033[0m\n' ╔══════════════════════════════════════════╗ + printf '\033[1m║ git-nostr-sign · installer ║\033[0m\n' ║ git-nostr-sign · installer ║ + printf '\033[1m╚══════════════════════════════════════════╝\033[0m\n\n' ╚══════════════════════════════════════════╝ + printf ' Publisher: %s\n' git-nostr-sign@nostr-svrn.codeberg.page Publisher: git-nostr-sign@nostr-svrn.codeberg.page + printf ' Relay: %s\n' wss://relay.damus.io Relay: wss://relay.damus.io + hdr 'Checking dependencies' + printf '\n\033[1m%s\033[0m\n' 'Checking dependencies' Checking dependencies + command -v node + command -v pnpm + command -v npm + PKG_MGR=npm + node -e 'process.stdout.write(process.versions.node.split('"'"'.'"'"')[0])' + NODE_MAJOR=24 + '[' 24 -lt 18 ] + node --version + ok 'Node.js v24.14.1' + printf ' \033[32m✓\033[0m %s\n' 'Node.js v24.14.1' ✓ Node.js v24.14.1 + ok 'Package manager: npm' + printf ' \033[32m✓\033[0m %s\n' 'Package manager: npm' ✓ Package manager: npm + command -v sha256sum + SHA_CMD=sha256sum + ok 'SHA-256 available' + printf ' \033[32m✓\033[0m %s\n' 'SHA-256 available' ✓ SHA-256 available + echo git-nostr-sign@nostr-svrn.codeberg.page + grep -q @ + hdr 'Resolving NIP-05 address' + printf '\n\033[1m%s\033[0m\n' 'Resolving NIP-05 address' Resolving NIP-05 address + echo git-nostr-sign@nostr-svrn.codeberg.page + cut -d@ -f1 + NIP05_NAME=git-nostr-sign + + cut -d@ -f2 echo git-nostr-sign@nostr-svrn.codeberg.page + NIP05_DOMAIN=nostr-svrn.codeberg.page + NIP05_URL='https://nostr-svrn.codeberg.page/.well-known/nostr.json?name=git-nostr-sign' + printf ' Fetching %s\n' 'https://nostr-svrn.codeberg.page/.well-known/nostr.json?name=git-nostr-sign' Fetching https://nostr-svrn.codeberg.page/.well-known/nostr.json?name=git-nostr-sign + curl -fsSL 'https://nostr-svrn.codeberg.page/.well-known/nostr.json?name=git-nostr-sign' + NIP05_JSON='{ "names": { "git-nostr-sign": "ea4e2c999d18c229c688e0cc31ae4c3d556f942537da0d8473d050097983d434" }, "relays": { "ea4e2c999d18c229c688e0cc31ae4c3d556f942537da0d8473d050097983d434": [ "wss://relay.damus.io", "wss://relay.nostr.band", "wss://nos.lol", "wss://relay.primal.net" ] } }' + node -e ' const j = JSON.parse(process.env.NIP05_JSON || '"'"'{}'"'"'); const hex = j.names?.['"'"'git-nostr-sign'"'"']; if (!hex) { process.stderr.write('"'"'Name not found\n'"'"'); process.exit(1); } import('"'"'nostr-tools/nip19'"'"').then(m => process.stdout.write(m.npubEncode(hex))); ' 'NIP05_JSON={ "names": { "git-nostr-sign": "ea4e2c999d18c229c688e0cc31ae4c3d556f942537da0d8473d050097983d434" }, "relays": { "ea4e2c999d18c229c688e0cc31ae4c3d556f942537da0d8473d050097983d434": [ "wss://relay.damus.io", "wss://relay.nostr.band", "wss://nos.lol", "wss://relay.primal.net" ] } }' Name not found + PUBLISHER_NPUB= + die 'Could not resolve NIP-05 name' + printf ' \033[31m✗\033[0m %s\n' 'Could not resolve NIP-05 name' ✗ Could not resolve NIP-05 name + exit 1Looks like you're missing the
--npubargument when piping toshThank for the feedback. Patched 0.2.2 is available. Want to give another try?
Like I indicated: missing
nostr-tools/nip19node:internal/modules/package_json_reader:301 throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null); ^ Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'nostr-tools' imported from /[eval] at Object.getPackageJSONURL (node:internal/modules/package_json_reader:301:9) at packageResolve (node:internal/modules/esm/resolve:768:81) at moduleResolve (node:internal/modules/esm/resolve:859:18) at defaultResolve (node:internal/modules/esm/resolve:991:11) at #cachedDefaultResolve (node:internal/modules/esm/loader:719:20) at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:736:38) at ModuleLoader.resolveSync (node:internal/modules/esm/loader:765:52) at #resolve (node:internal/modules/esm/loader:701:17) at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:621:35) at onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:650:32) { code: 'ERR_MODULE_NOT_FOUND' }It be good if you test it yourself. Just use my pipe, it's easy:
docker run -it alpine /bin/ash -c "apk update && apk add curl nodejs npm && curl -fsSL https://nostr-svrn.codeberg.page/install.sh | sed 's/set -e/set -ex/' | sh"I thought I did... 0.2.3 is now available.
I get to the wizard now!
But when I should input it already quit the script.
Is there a way to just manually configure?
╔══════════════════════════════════════════════╗ ║ git-nostr-sign · setup wizard ║ ╚══════════════════════════════════════════════╝ This wizard will: 1. Set publisher identity (import nsec, Amber, or new keypair) 2. Deploy a NIP-05 address to Codeberg Pages (free) 3. Configure your git to sign commits with your Nostr key Press Ctrl+C at any time to cancel. ─── Phase 1 · Tool Identity ───────────────────── Publisher identity (NIP-05 + release metadata) 1. Import existing nsec or hex — reuse a key you already have 2. Amber (NIP-46) — private key stays on your phone 3. Generate new dedicated keypair — fresh “project” publisher key Enter number: / # 1 /bin/sh: 1: not foundTested through
npm run setup, which doesn't terminate on the menu when ran like that.Findings from setup process:
new keypairroute (option 3), I get the npub echo'd to me, but then after skipping step 2 (because codeberg is still centralized, so let's not) I am asked to type the nsec. This is odd, because I was never given it. When I enter nothing, it terminates.You really, really, really need more testing.
That's true. Appreciate the feedback.
0.3.0:
➤ git-nostr-setup -h git-nostr-setup / npm run init -- … — interactive wizard (default) Non-interactive (--non-interactive or -N): Identity (pick one): --generate-identity New random keypair (commits + updater) --identity-nsec <nsec|hex> Existing key (also: GNS_IDENTITY_NSEC) --bunker-url <bunker://…> Amber NIP-46 (approve on phone; GNS_BUNKER_URL) --reuse-identity Keep identity.json, refresh signing + git only Signing override (optional — at most one): --signing-nsec … Different local key for commits --signing-bunker-url … Different Amber bunker for commits Git (pick one): --git-global Global gpg.program + signing --git-local This repo only (must be inside a git work tree) --no-git-config Only write config.json / identity.json --no-pre-push-hook Skip core.hooksPath .githooks Examples: git-nostr-setup -N --generate-identity --git-global git-nostr-setup -N --identity-nsec "$GNS_IDENTITY_NSEC" --git-global git-nostr-setup -N --bunker-url 'bunker://…' --git-local NIP-46 still needs Amber approval the first time. QR: set GNS_QR_SMALL=1 for a smaller pattern. Secrets in argv hit shell history — prefer env vars.Here's what the command does:
docker run -it alpine /bin/ash -c ...Get clean alpine image, call its shell (interactively) to execute a scriptapk update && apk add curl nodejs npm ...install the prerequisitescurl -fsSL https://nostr-svrn.codeberg.page/install.sh- your install script| sed 's/set -e/set -ex/' | sh- edit theset -eline to make it echo back so we can debugSame result with that. This command structure is taken from the site, not the post here
Right. Maybe you can set
PUBLISHER_NPUB=...for the last command in-line.It seems to look there.
Yeah. I can edit the script. There's other problems too that I foresee in the immediate next step... But it doesn't even get there.
(It expects to have
nostr-tools/nip19but as you see in the log, that wasn't fetched)