> **Nostr Agent Onboarding** · [start here](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/start.md) · [index](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/llms.txt) · source: `nostr-dev/docs/patterns/going-public.md` · snapshot 2026-10-10
>
> Paths such as `~/Documents/…`, `~/Production Environment/…`, `repos/…` and services on `localhost` refer to the author's workstation and are **not available to you** — read them as worked examples of a setup you can recreate.

# Pattern: Going public — exposing local Nostr-native artifacts on the open internet

> **Source:** the runbook `~/work/nostr-archive/docs/going-public.md` (sibling
> agent's work). That document covers an archive specifically; this one
> generalizes the recipe so any artifact built on this workspace's local
> stack can be made publicly reachable without exposing this machine to
> inbound traffic.

You built something against the local GRASP / relay / Blossom
(`http://localhost:8081`). It works in this environment. Now you want
anyone with a browser to be able to use it, ideally without poking holes
in your firewall, NAT, or DNS.

The pattern is **publish to public infrastructure**. Local stays
canonical for development; public is a mirror. The visitor's browser
only ever talks to public services. Bytes are content-addressed, events
are signed — replicating to public losses no provenance.

## Three things to mirror

1. **Events** (kind:1, kind:30617, anything addressable) → public Nostr
   relays.
2. **Blobs** (Blossom) → public Blossom servers.
3. **Git repos** (GRASP `nostr://` URLs) → a public GRASP host.

If your artifact is a static web client, there's a fourth piece:

4. **The static site itself** → publish as an **nsite** (NIP-5A, kind 15128;
   the old per-file kind 34128 is dead). An nsite gateway (`<npub>.nsite.lol`)
   serves it from public Blossom. Full recipe:
   [`nsite-publishing.md`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/patterns/nsite-publishing.md).

## The recipe

Adapted, generalized, and condensed from `~/work/nostr-archive/docs/going-public.md`.
Every step is idempotent — re-running won't hurt.

### 1. Mirror events to public relays

Pick the kinds your artifact uses, pull from local relays, push to public:

```bash
PUBHEX=$(jq -r .pub_hex /dev/shm/nostr-dev-session/keys.json)
PUBLIC=("wss://relay.ngit.dev" "wss://nos.lol" "wss://relay.damus.io")

# Replace 30617 with whatever kinds your artifact uses
{ for k in 30617 30618 1; do
    nak req -k "$k" -a "$PUBHEX" ws://localhost:8081 2>/dev/null
  done
} | jq -c 'select(.id != null)' | sort -u > /tmp/events.jsonl

while IFS= read -r ev; do
  echo "$ev" | nak event "${PUBLIC[@]}" >/dev/null 2>&1
done < /tmp/events.jsonl
```

Expect 2-of-3 success on any given event. Public relays sometimes
rate-limit, content-policy, or blip. As long as one accepts each event,
discovery still works.

### 2. Mirror Blossom blobs to a public server

```bash
NSEC=$(jq -r .nsec /dev/shm/nostr-dev-session/keys.json)

# Collect every sha256 your events reference (`x` tags, `primary`, `ots`,
# whatever your kinds use):
SHAS=$(jq -r 'select(.kind==<your-kind>) | .tags[] | select(.[0]=="x") | .[1]' \
        /tmp/events.jsonl | sort -u)

for sha in $SHAS; do
  curl -sLf -o /tmp/blob "http://localhost:8081/$sha"
  nak blossom --sec "$NSEC" -s https://blossom.primal.net upload /tmp/blob >/dev/null
done
rm /tmp/blob
unset NSEC
```

Recommended public Blossom servers as of writing:
- `https://blossom.primal.net` — large, reliable, no auth quirks
- `https://blossom.band` — index-friendly
- Run `blossom-audit` against a server before relying on it: `npx blossom-audit audit https://server bitcoin --sec <ephemeral nsec>`

### 3. Push the GRASP repo to a public host

`relay.ngit.dev` runs the same `ngit-relay` software as the local stack
and accepts pushes from any pubkey, validated against on-relay
`kind:30617` events. So the same `ngit init` workflow you used locally
also works against the public GRASP — just point it there:

```bash
cd <repo-working-tree>
NSEC=$(jq -r .nsec /dev/shm/nostr-dev-session/keys.json)
ngit init -d \
    --name "<repo-name>" \
    --identifier "<d-tag>" \
    --description "Public mirror." \
    -g wss://relay.ngit.dev \
    -g ws://localhost:8081 \
    -n "$NSEC"
unset NSEC
```

After this, the kind:30617 announcement on relay.ngit.dev lists *both*
clone URLs — public first, local as fallback. A browser visiting the
public URL clones from relay.ngit.dev. A consumer on this machine can
still use localhost.

### 4. (If applicable) Publish the client as an nsite

For a static-site artifact (HTML/CSS/JS/wasm), this step has its own pattern
doc and its own script — **[`nsite-publishing.md`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/patterns/nsite-publishing.md)** and
`bin/nsite-deploy.py`. Don't hand-roll it; five
people already did and each hit the same three traps.

```bash
~/Documents/nostr-dev/bin/nsite-deploy.py --dir dist \
    --keys ~/Documents/nostr-dev/secrets/<site>/keys.json \
    --title "…" --description "…"
```

Result: anyone visits `https://<your-npub>.nsite.lol/` and the gateway
fetches `kind:15128`, resolves each path's blob from public Blossom, and
serves it.

The three traps, in one line each: publish under a **dedicated key** (15128 is
replaceable per pubkey, so the wrong key destroys an existing site); the events
**must** reach `wss://relay.nsite.lol`; and `https://nostr.download` goes
**first** in the server list because `blossom.primal.net` serves every `text/*`
asset as `text/plain`, which silently kills stylesheets and service workers.

### 5. Update the client's defaults to point at public infrastructure

If your client has hardcoded `ws://localhost:8081`, switch to
`wss://relay.ngit.dev` (or whichever public relays apply) before
publishing. Keep the localhost URL as a *fallback in the user's UI*, not
the default.

## What still requires this machine

- **None of the above for visitors.** Once mirrored, the artifact is
  publicly reachable.
- **Producing new content.** Whatever pipeline writes to the local
  Blossom + signs events still runs here. Add `NAP_BLOSSOM_MIRRORS` (or
  your equivalent env var) so each new blob auto-mirrors to public on
  the way out.

## Failure modes to expect

| Symptom | Cause | Mitigation |
|---|---|---|
| Public Blossom 404 on a blob you uploaded yesterday | GC; some servers expire un-pinned blobs | Re-upload (idempotent — same sha256). Use a Blossom server that doesn't GC. |
| Public relay drops a kind:1 you sent | Rate limit or content policy | Mirror to ≥3 public relays. |
| `relay.ngit.dev` is down | Service outage | The kind:30617's `clone` tag lists local fallback (only useful from this network). For real production, mirror the repo to a second public GRASP. |
| `nsite.lol` is down | Gateway outage | Try `<npub>.nsite-host.com` or any other nsite gateway. The kind:15128 lives on relays; any gateway implementation can serve it. |
| Browser CORS error against the local GRASP | The local container's nginx + Khatru both emit CORS headers | See [`docs/known-issues.md`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/design/known-issues.md) § "ngit-relay double CORS headers" |

## What this pattern doesn't solve

- **Inbound producer access.** If you want others to push *to* your
  machine, you need a tunnel or a real public IP. This pattern is
  specifically about making locally-produced artifacts *consumable*
  publicly.
- **DMs and inbox-routed content.** NIP-65 inbox semantics still mean
  you have to publish to the recipient's INBOX, not your own outbox.
  Going public for outbox-routed content is what this pattern handles.
- **Bandwidth costs at scale.** If the public Blossom server you mirror
  to charges for bandwidth, viral content is your problem. Mirror to
  multiple servers; if one buckles the others stay up.

## Related

- `configs/local-services.json` — where the local services live
- [`docs/sources/blossom.md`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/sources/blossom.md) — Blossom mechanics (the blob mirror step)
- [`docs/sources/outbox-model.md`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/sources/outbox-model.md) — relay routing semantics (the event
  mirror step)
- [`docs/known-issues.md`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/design/known-issues.md) — the CORS quirk, Khatru kind:30617 eviction, and
  §7 the nsite trap set; all relevant when going public
- [`patterns/nsite-publishing.md`](https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/patterns/nsite-publishing.md) — step 4 in full
