Skip to content

Add BrollyZapper - #6049

Open
davotoula wants to merge 2 commits into
getumbrel:masterfrom
davotoula:brollyzapper
Open

davotoula wants to merge 2 commits into
getumbrel:masterfrom
davotoula:brollyzapper

Conversation

@davotoula

@davotoula davotoula commented Sep 3, 2026

Copy link
Copy Markdown

Type

New app

App

Summary

BrollyZapper adds Nostr zap receiving (NIP-57) and Nostr Wallet Connect (NIP-47)
to an LND node the user already runs. It depends on lightning and adds no node
of its own.

The store's dedicated NWC app, alby-nostr-wallet-connect, is deprecated by its
developers and carries disabled: true. This fills that gap, and does so with a
materially tighter credential posture than any existing app that talks to LND —
see Notes.

Verification

Umbrel testing performed (2026-09-02, 0.1.17): fresh install — uninstall, then install
through the Umbrel dashboard with lightning already running (30 s to Open, both images pulled
from GHCR, digests matching the manifest); setup through app_proxy; the proxy whitelist probed
from a session-less client (/health and the LNURL paths answer anonymously, every other path
redirects to Umbrel's login) and the address fetched from the public internet through a tunnel;
real workflow — one zap received from Amethyst with its kind-9735 receipt read back off a
public relay, and one plain LNURL payment from Primal; restart through the dashboard
(apps.restart.mutate, 28 s, containers recreated) with settings, wallet history and the receive
credential surviving unchanged; ten idle minutes of settled logs with nothing above INFO. The
receive-only posture was verified by inspection: recv.macaroon present, no spend.macaroon.
In-place update (2026-09-03, 0.1.17 → 0.1.18 through the dashboard's Update button, 30 s):
digests on the box match the manifest, no migration, both credentials survived the container
recreate byte-identical with no re-bake, the guard re-registered its spend-cap middleware, the
24-hour spend window carried across, one zap and one plain payment received and one NWC payment
sent afterwards, a further restart with everything intact, ten idle minutes with nothing above
INFO.

Environment tested:

  • Umbrel device — umbrelOS 1.7.4, Raspberry Pi 4
  • Local umbrelOS test environment
  • Not runtime tested

Architecture tested:

  • amd64
  • arm64 — 6.18.34+rpt-rpi-v8 aarch64

Known lint warnings or caveats: none. npm run lint:apps -- brollyzapper --check-images is clean, 0 warnings. Both images are pinned by multi-arch index
digest and carry linux/amd64 + linux/arm64.

Notes

Dependency. lightning. The transitive bitcoin export supplies
APP_BITCOIN_NETWORK, used to locate the macaroon.

LND credential access — the part worth reviewing. The package mounts two
individual files from the lightning app's data dir, read-only:

${APP_LIGHTNING_NODE_DATA_DIR}/tls.cert
${APP_LIGHTNING_NODE_DATA_DIR}/data/chain/bitcoin/${APP_BITCOIN_NETWORK}/admin.macaroon

Twenty apps in the store mount admin.macaroon. Of those, btcpay-server,
bluewallet, bolt12-pay, fedimint-gateway and alby-nostr-wallet-connect
mount ${APP_LIGHTNING_NODE_DATA_DIR}:/lnd:ro — the whole directory, including
wallet.db and channel.backup. With Umbrel's default wallet password,
wallet.db is the seed. albyhub mounts the same directory by hardcoded path
while declaring dependencies: [].

This package is the only one in the store that mounts admin.macaroon at file
granularity, and it never puts it in the container with the attack surface:

  • guard holds admin.macaroon and has no listener of any kind. Its only
    input is a unix socket in a shared volume, with a fixed four-operation API.
  • server holds all of it — HTTP, nostr relays, sqlite — and never sees
    admin.macaroon. It consumes a receive-only macaroon the guard bakes: five RPC
    methods, none able to move a satoshi.

A repository lint asserts the split rather than trusting review, and it is
verified by planting the violation.

No host access. No Docker socket, no privileged, no device mounts, no extra
capabilities, no network_mode: host, no raw published ports.

Umbrel auth stays on. PROXY_AUTH_WHITELIST is narrowed to the three routes
anonymous LNURL clients on the public internet must reach and cannot send an
Umbrel cookie to: /health, /.well-known/lnurlp/*, /lnurlp/*. Everything else
sits behind Umbrel auth and the app's own login. A lint asserts the whitelist
equals the app's public route set exactly, because a public route Umbrel still
gates breaks anonymous clients with no visible error.

Credentials. deterministicPassword: true, and the package genuinely wires
ADMIN_PASSWORD to ${APP_PASSWORD}, so what Umbrel displays is what logs in.
Login is password-only, hence defaultUsername: "". The session secret is a
purpose-labelled derive_entropy in exports.sh, not APP_SEED reused and not a
constant.

backupIgnore: [data/credentials]. That volume holds the receive macaroon,
which in a stolen backup would stream every invoice on the node over LND's
Tor-published gRPC. Nothing is lost by excluding it: the guard re-bakes on start,
so a restore comes back with a fresh, correctly caveated credential rather than a
stale one. The database stays in backup — it holds the zap-receipt signing key,
and losing it changes the nostr pubkey the lightning address advertises.

Static IP. 10.21.21.14, needed because the spend macaroon carries an
ipaddr caveat that must match the server container exactly. Verified free
against the whole store on 2026-09-03 (getumbrel/umbrel-apps@318cdcf, 391 apps); 3033 likewise free across all manifests
and raw port mappings.


Icon

Logo reference: https://github.com/davotoula/brollyzapper/blob/main/assets/icon.svg
(an upturned umbrella catching zaps, in nostr purple, with BZ on the canopy; letterforms
are path data, so it renders without the font). A 256×256 PNG raster is attached in the
first comment below.

Screenshots

Five screenshots of the running UI at 1440×900 (Wallet, Connections, Sending, Security,
Settings) are attached in the first comment below. They are rendered from fixture data on
purpose — deterministic, and no operator's own npubs or amounts in a public listing. Nothing
artwork-shaped is committed here and gallery: [] stays empty for Umbrel to create the final
assets.



🤖 Generated with Claude Code

davotoula added a commit to davotoula/brollyzapper that referenced this pull request Sep 3, 2026
… against upstream HEAD

getumbrel/umbrel-apps#6049 is open; the manifest's submission field is
the one thing the linter could not accept before the PR existed. The
README's last-verified line moves to today's upstream HEAD (318cdcf,
391 apps): IP, neighbours and port all still free.
@davotoula

Copy link
Copy Markdown
Author
sending-1440x900@2x icon-256 settings-1440x900@2x security-1440x900@2x connections-1440x900@2x wallet-1440x900@2x

@davotoula

Copy link
Copy Markdown
Author

@nmfretz and the team, is there any other information needed for adding apps to umbrel?

Or maybe you are all busy with umbrel 2.0 preparations?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant