Damus
Derek Ross profile picture
Derek Ross
@Derek Ross

Opal: Nostr in Your Omarchy Bar

Article header image

There was no Nostr signer for the Linux desktop, so every app and script wanted my nsec. Opal is a small daemon and a gem in the Omarchy bar that keeps the key in the keyring, signs for apps under rules I set, and brings notifications and status along.

#opal#omarchy#nostr#signer#nip46#linux

On my phone, Amber holds my Nostr key and signs for every app. In the browser, an extension does the same. On the Linux desktop there was nothing. Every desktop client, every script, every little tool I wrote wanted the nsec pasted into a config file, and each copy was one more place the key could leak from.

Notifications had the same shape of problem. To know someone replied or zapped me, I had to keep a client open in a browser tab all day.

So I built Opal. It is one small daemon, one gem icon in the Omarchy bar, and three modules you switch on as you need them.

The Omarchy bar with the Opal gem next to the tray icons

Opal's settings: the signer, notifications and status modules, each with its own switch

Signer

The signer is a NIP-46 bunker. Apps log in with a bunker link, a nostrconnect link, or a QR code from the panel, and my key never leaves Opal. Each app gets its own key on Opal's side, so an app does not learn my npub until it asks, and one app's relay cannot tie my other apps together.

Every app gets a policy. Basic signs everyday things like notes, reactions and reposts on its own. Ask prompts for everything. Trust signs it all. The approval dialog shows exactly what is about to be signed: the content, the key tags, and a warning if the date looks odd. I can remember an answer from once to always.

Some things always ask, whatever the policy: profile, follow list, relay list and mute list changes, deletions, Blossom and HTTP auth tokens, wallet events, and anything dated more than ten minutes from now. Those can be remembered for an hour at most. An activity log records what each app did and why it was allowed, and a kill switch stops answering every app at once.

The Activity tab: Peridot, Opal Status and Carnelian signatures, each marked allowed by full trust, automatic, or you

Programs on this computer can use the key too. Peridot pairs over a local socket instead of a relay, and the same dialog shows which program is asking, down to its systemd unit. It then lives under Apps with the same policies and log as a remote app, and can be revoked there.

The Apps tab: a bunker link button, a nostrconnect field, and Carnelian and Peridot listed with their policies

Notifications

Replies, mentions, reposts, reactions, zaps and NIP-17 DMs land in an Inbox with filters and an unread dot on the gem, and as desktop popups with avatars. The mute list is honored, private entries included. A zap is only shown after the zap request checks out and its amount matches the invoice.

The Inbox: reactions and replies from five relays, with filters for replies, zaps and DMs

Status

The status module publishes NIP-38 statuses. Now playing comes from Spotify, a browser, mpv or any MPRIS player, with a link to the song, and clears on pause. I can set a status by hand with an expiry. Automatic ones switch on if wanted: In a meeting during khal events, Away while the screen is locked, Focusing during Do Not Disturb. It keeps a local listening history and can publish scrobbles as kind 1073, a draft NIP that grew out of noscrobble.

The Status tab: now playing, a status with an expiry, and this week's listening

Holding a key carefully

Opal holds the nsec, so it is built to be careful about it.

  • Keys go into the system keyring only as NIP-49 ncryptsec, encrypted with a passphrase that has to pass a strength check.
  • Keys are wiped from memory on lock: after a timeout, when the screen locks, and before suspend. Only my own approvals count as activity, so an app cannot keep it unlocked.
  • Core dumps are off, and other processes cannot read the daemon's memory. The systemd unit has no capabilities, a seccomp filter, and a home it can only write its own directories in.
  • Changes that weaken protection, like full trust for an app or turning auto-lock off, need the passphrase.
  • The installer records the hash of every file it writes and only replaces or removes what it can prove is its own. Prebuilt binaries are accepted only if they match a hash pinned in the checkout.

Opal locked: the panel asks for the passphrase before it will sign

Together

Opal is what makes Carnelian painless: Carnelian pairs with it once, and every article signature shows up as a dialog with the article in it. Peridot uses it to sign the encrypted settings it syncs between my computers. Both have their own articles.

Install

omarchy plugin add https://github.com/derekross/opal.git --enable
~/.config/omarchy/plugins/derekross.opal/dist/install.sh

Omarchy plugins only copy files, so the second line installs the daemon, builds it if Rust is there or downloads a pinned release if not, enables the user service, and puts the gem in the bar. Click it to add a key, or to watch someone's notifications with just an npub.

Opal is the gem that started the row in my bar. MIT licensed, Rust, version 0.3.1 as I write this, at github.com/derekross/opal.