Commit Graph

16 Commits

Author SHA1 Message Date
2ro 45c6941b80 Add an interactive first-run setup wizard to the name authority
A complement to the existing FLOONET_* env config, never a replacement. When
FLOONET_DOMAIN is already set (docker compose and the systemd EnvironmentFile
both set it) the binary runs headless exactly as before, including validate()
fail-fast. Only when nothing is configured AND stdin/stdout is an interactive
TTY does it prompt for the essentials (domain, bind address, data dir, pay
mode, name transfers), derive base_url and relays from the domain so validate()
always passes, write a conventional env file, load it, and continue starting
up. A non-TTY with no config stays headless.

The env file is written where the service convention consumes it: FLOONET_ENV_FILE
if set, else /etc/floonet-authority.env when writable (the systemd default), else
./.env (the docker-compose default). On startup the binary also loads the first
existing candidate non-overriding, so a wizard-written file is picked up on this
run and subsequent runs while the real systemd/docker environment always wins.

Tests in tests/setup.rs cover the headless guarantee (config present never runs
the wizard), the prompt flow, env-file rendering/parsing, and non-overriding load.
2026-07-06 01:47:58 -04:00
2ro 6143fce8ac Add the name-transfer (marketplace) feature to the bundled authority, off by default
Ports the Goblin Name Transfer Protocol v1 into the colocated Floonet name
authority: a seller lodges a signed kind-3402 offer to reassign a name to a
buyer for a Grin price, and the buyer claims it with an on-chain payment proof.
Strictly non-custodial: the buyer pays the seller wallet to wallet, the
authority verifies two artifacts and swaps one names row. Keys never move.

New routes, mounted only when FLOONET_TRANSFERS is set (else they 404):
  POST   /api/v1/transfer/offer          NIP-98 (seller), body {offer}
  GET    /api/v1/transfer/offer/{id}      public, read-limited, CORS *
  DELETE /api/v1/transfer/offer/{id}      NIP-98 (seller)
  POST   /api/v1/transfer/claim           NIP-98 (buyer), body {offer_id, proof}

Claim verification runs in the spec's order: NIP-98 + replay set, offer exists
(consumed -> idempotent 200 or 409), revoked/expired grace + end_height gate,
buyer key == p tag, seller still owner, both proof signatures over the canonical
73-byte message (amount_BE || excess || sender), recipient address and exact
amount match the offer, kernel confirmed on chain >= min_conf, excess never used
(durable UNIQUE), buyer holds no name (exempt from the name-change cooldown).
The reassignment is one atomic SQLite transaction that also records the transfer,
consumes the offer, and arms the seller's cooldown. The lodge ambiguity rule
keys on proof_address alone among live offers (offer_ambiguous).

The transfer path is fully independent of the fork's GoblinPay paid-names
integration: zero calls into src/paid.rs, no GOBLINPAY_URL dependency, pure
in-process crypto plus the read-only Grin node foreign API. Transfers and paid
names toggle in any combination. Config follows the repo's FLOONET_ pattern
(from_env/validate/summary/for_test), fail-fast when transfers are on without
FLOONET_GRIN_NODE_URL, and is documented in .env.example, docker-compose.yml,
and the us-east env template.

Tests: 27 transfer integration tests (offer state machine, revoke, expiry,
late-claim grace and end_height rules, every claim failure mode with real
ed25519 proofs, successful reassign + idempotent retry + cooldown, plus a
transfers-with-pay-mode-active combination) adapted to tests/http.rs helpers,
plus proof, node and config unit tests. cargo test green (32 + 17 + 27).
2026-07-06 00:47:59 -04:00
2ro 3b47097579 .env.example: comment out FLOONET_ALLOWED_KINDS so the full 24-kind plugin default applies 2026-07-04 22:23:43 -04:00
2ro 11c304f935 README: public-note lockdown + real 24-kind default whitelist
Kinds 1 and 30023 accepted only from FLOONET_AUTHORIZED_AUTHORS (or the
floonet.env sidecar; npub or hex; closed by default; hot reload via
plugin mtime). Whitelist table now matches the plugin's actual 24-kind
Goblin + Magick Market default. Self-hoster guarantee: no public-note
spam, the operator chooses who posts.
2026-07-04 22:20:21 -04:00
2ro b35ddb29d3 scripts: smoke test covers gift wraps and long-form (4 checks)
Extend smoke_test_lockdown.py from two checks to four, all signed by one
throwaway unauthorized key:

  kind 1     text note          -> BLOCKED  (lockdown)
  kind 0     profile            -> ACCEPTED (profiles stay open)
  kind 1059  gift wrap          -> ACCEPTED (money path ungated; p-tag a
                                   random recipient, opaque base64 content)
  kind 30023 long-form article  -> BLOCKED  (lockdown)

Prints all four results; RESULT: PASS and exit 0 only if every expectation
holds, nonzero otherwise. Verified offline end to end against a local mock
relay driven by the real plugin decide() (PASS) and against an accept-all
relay (FAIL, exit 1).
2026-07-04 20:07:15 -04:00
2ro 65ec8fcef2 relay: lock public notes (kinds 1, 30023) to operator-authorized authors
Public-note kinds are now accepted only from an operator-configured list of
author pubkeys (FLOONET_AUTHORIZED_AUTHORS, hex or npub). Closed by default:
with no authors set, kinds 1 and 30023 are rejected for everyone, so random
notes cannot be spammed to the relay. Every other kind is unaffected and
kind 0 profiles stay open.

- add 30023 (long-form article) to the default kind whitelist
- check_authorized_authors runs right after check_kind; LOCKED_KINDS {1,30023}
- pure-python bech32 npub decoder (no new deps); invalid entries logged/skipped
- load_config also reads a KEY=VALUE floonet.env next to the plugin
  (FLOONET_ENV_FILE), env vars win, so config changes need no container
  recreate; strfry reloads the plugin on mtime change (no restart)
- extend test_policy.py (32 tests green)
- scripts/purge_public_notes.sh (dry-run default) to clean pre-existing notes
- scripts/smoke_test_lockdown.py post-deploy check
- README + .env.example config docs
2026-07-04 19:58:31 -04:00
2ro 36836f9392 Merge pull request #1 from 2ro/tor-migration
Retire the mixnet exit; document Tor as the transport
2026-07-04 08:41:03 -07:00
2ro ab1a72d652 Retire the mixnet exit; document Tor as the transport
The Goblin wallet moved off the Nym mixnet to Tor: it now reaches relays
over a Tor circuit to their clearnet endpoint, so the co-located mixnet
exit this package bundled is retired.

- delete the vendored `mixexit/` crate and the hardened
  `deploy/systemd/floonet-mixexit.service` bare-metal unit
- docker-compose: drop the `mixexit` service (COMPOSE_PROFILES=exit) and
  its `mixexit-data` volume; fix the top-of-file service list
- .env.example: replace the "Mixnet exit" block (COMPOSE_PROFILES=exit,
  FLOONET_EXIT_UPSTREAM) with the Tor onion toggle
- README: replace the "Mixnet exit" section with "Tor onion", fix the
  feature table, deploy paths, and the COMPOSE_PROFILES reference row

Add Tor as the first-class replacement deploy option, the same recipe
already proven in production: an optional `tor` compose service
(COMPOSE_PROFILES=tor) plus deploy/tor/torrc, a stock system tor daemon
whose hidden service forwards straight to strfry's websocket listener
(no TLS on that hop, the onion is already encrypted end to end). strfry
core stays stock; this is packaging and docs, no relay patch.

The default stack is unchanged (the onion is opt-in, as the exit was).
name-authority builds green; cargo test and clippy pass. docker-compose
validated (structure + YAML).
2026-07-04 06:30:51 -04:00
2ro 785d2008c8 docs(internal): Tor migration plan (retire mixexit, onion, relay-side Poisson) 2026-07-04 03:40:08 -04:00
2ro 37d067e825 docs(readme): call out name authority co-location in the piece table
Matches the detail already in the "Co-locating names on the relay
domain" section below — on by default via the Compose/Caddy stack.
2026-07-03 13:20:51 -04:00
2ro 7205ddbafd floonet-strfry: co-locate the name authority on the relay domain (toggle)
Serve the authority's NIP-05 lookup on the relay's own domain so
`name@relay.example` resolves, without giving the authority a second
vhost/cert. Live on us-east: relay.floonet.dev now answers
/.well-known/nostr.json from the co-located authority (127.0.0.1:8193)
while the WebSocket relay and NIP-11 stay untouched.

  * deploy/us-east/colocated-authority.conf
        The nginx opt-in: an exact-match `location = /.well-known/nostr.json`
        proxied to the authority ahead of the relay's WebSocket catch-all.
        Only the READ path is exposed; registration and the rest of /api/*
        stay on the authority's own domain. Sets X-Real-IP (the per-IP rate
        limiter keys off it). Same proxy shape as nm.floonet.dev.conf.
  * README.md — "Co-locating names on the relay domain": the Caddy/compose
        stack is co-located by default (single FLOONET_DOMAIN); a split nginx
        deploy opts in with the snippet. Documents FLOONET_AUTHORITY_COLOCATED.
  * deploy/Caddyfile, .env.example — note the single-domain stack is
        co-located by default and point split deploys at the snippet.

The box vhost mirrors this snippet exactly; applied with nginx -t + reload
(no restart), firewalld untouched.
2026-07-03 04:11:19 -04:00
2ro 868cc84b20 floonet-strfry: raise strfry maxEventSize to 131072
Match the advertised maxWebsocketPayloadSize so a maximum-size NIP-59
gift-wrapped slatepack payment (~66 KiB worst case) is accepted; the old
64 KiB cap could silently reject the largest payments on the money path.
2026-07-03 03:20:48 -04:00
2ro be15c78121 floonet-strfry: us-east production deploy bundle for the nm.floonet.dev name authority
Bundle that stands the bundled name authority up behind nginx at
https://nm.floonet.dev in paid-name mode wired to the on-box GoblinPay:

  * nm.floonet.dev.conf         nginx vhost mirroring the relay.floonet.dev
                                pattern (same listen IP + certbot webroot),
                                TLS -> 127.0.0.1:8193, sets X-Real-IP (which
                                the per-IP rate limiter keys off).
  * floonet-authority.service.d/10-us-east.conf
                                drop-in over the generic hardened unit: swaps
                                DynamicUser for the stable unprivileged goblin
                                account and relocates the DB into the
                                /opt/goblin tree (one backup root), inheriting
                                every other sandbox directive.
  * floonet-authority.env.example
                                FLOONET_PAY_MODE=name, GOBLINPAY_URL at the
                                loopback GoblinPay; the real GP token is filled
                                from goblinpay.env at deploy time, never here.
  * deploy.sh                   idempotent runbook: build on-box, install,
                                two-phase certbot (acme :80 -> cert -> :443),
                                start. Never touches goblin-nip05d or firewalld.
2026-07-03 03:15:38 -04:00
2ro fb62ed2bf2 floonet-strfry: add a NIP-98 header-minting example for the name authority
Operating a NIP-98-gated endpoint (register / unregister / quote) needs signed
kind-27235 Authorization headers, and there is no nak on the target hosts. This
example reuses the crate's existing nostr/base64/sha2 deps to mint a
"Nostr <base64-event>" header for curl/CI: generate a throwaway identity or
reuse one via NIP98_SK, sign over the method/path/body, print the header to
stdout. The u-tag is built from FLOONET_BASE_URL to match server verification.
2026-07-03 03:15:38 -04:00
2ro c5ca6860d7 floonet-strfry: whitelist the marketplace kind set
Extend the write-policy whitelist from the Goblin-wallet-only kinds to the
union with Magick Market so one relay serves both apps (default-deny for
everything else). Adds 1 note, 7 reaction, 14/16/17 order+receipt (Gamma),
1111 comment, 10000 mute/blacklist, 24133 remote signing, 30000/30003
NIP-51 sets, 30078 app data, 30402/30405/30406 listing/collection/shipping,
31990 handler info. Keeps the Goblin base including 13 seal and 27235
NIP-98. Plugin default, docstring, and tests updated together.
2026-07-02 22:19:29 -04:00
Goblin 16302ed309 floonet-strfry: hardened strfry relay for the Grin community
Stock strfry + a default-deny write-policy plugin (kinds 0,3,5,13,1059,
10002,10050,27235 only), NIP-42 auth, neutral NIP-11, a bundled name
authority (paid names/uses via GoblinPay), and a config-toggled co-located
mixnet exit. Docker Compose + Caddy + hardened systemd. strfry core stays
stock (plugin + config only). Validated end to end against real strfry.
2026-07-02 08:20:30 -04:00