aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
blob: 453593f519a8f9122c4368ae13caf48bb3efe59c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# coinosv3-server

The server-side services behind [coinos v3](https://v3.coinos.io), the
bitcoin, Ark, Lightning and nostr wallet whose app lives in
[coinosv3](https://git.coinos.io/coinosv3.git). The wallet itself is
self-custodial and runs in the browser. These services hold no keys to user
funds; they give a wallet a payment address, wake it when a request arrives,
and store its encrypted sync data.

| Directory  | Service | Public at |
| ---------- | ------- | --------- |
| `names/`   | **Payment-name registrar.** Gives each wallet a `[email protected]` address: BIP-353 DNS records (written through Cloudflare's API), LNURL-pay, and a BOLT 12 offer per name on the Ark server's Lightning node. Settled payments are forwarded to the name's Ark address from a small float wallet. Also serves zap receipts, point-of-sale invoices, CLINK, payment webhooks, link previews and translation. Writes are authorized with NIP-98. | `names.coinos.io` |
| `nwcpush/` | **NWC push notifier.** Watches relays for Nostr Wallet Connect requests addressed to registered wallet-service keys and wakes the device with a Web Push, so a closed wallet can answer. | (internal, behind the app) |
| `blossom/` | **Blossom server for sync envelopes.** Stores only the wallet's encrypted cross-device state blobs, which can outgrow a relay's event size. Refuses anything else. | `blossom.coinos.io` |
| `feed/`    | **Public feed snapshot** (`/api/feed`) and server-rendered note and profile pages. | `v3.coinos.io` |
| `live/`    | **Live streams** sent from the app: the browser's recording over a WebSocket, out as HLS (a live window and the recording), for NIP-53 streams. Senders own a coinos name. | `v3.coinos.io/live/` |
| `vmedia/`  | **Media helper** for the feed: video posters, H.264 fallbacks and resized pictures. | `v3.coinos.io/vmedia/` |
| `bridge/`  | Ark to Lightning swap bridge. **Retired**: the app uses the Ark server's Lightning. `bridge/ln.js` (a minimal CLN RPC client) is still used by `names/`. | — |

## Shared code: the `coinosv3` submodule

The services use the wallet's own code for Ark, BOLT 12 and nostr helpers,
so the server speaks exactly the wire format and policy the app does. That
code comes from the `coinosv3/` submodule, **pinned to a specific commit**.
Moving the pin is a deliberate, reviewable commit of its own:

```sh
git -C coinosv3 fetch && git -C coinosv3 checkout <commit>
git add coinosv3 && git commit -m "coinosv3 → <commit>"
```

## Running

You need [Bun](https://bun.sh) (the deployment pins `oven/bun:1.3.14`) and,
for `names/`, a Core Lightning node's RPC socket and a Cloudflare zone.

```sh
git clone --recurse-submodules https://git.coinos.io/coinosv3-server.git
cd coinosv3-server
bun install
cp names/config.example.json names/config.json      # fill it in
NAMES_CONFIG=names/config.json bun names/server.js
```

Each service reads a JSON config. `names` uses `NAMES_CONFIG` and `nwcpush`
uses `NWCPUSH_CONFIG`; `blossom` reads `BLOSSOM_*` environment variables
(see the header of `blossom/server.js`). Example configs:
`names/config.example.json`, `nwcpush/config.example.json`,
`bridge/config.example.json`.

**Configs hold secrets and are never committed.** They include the DNS
provider's API key, admin tokens, the forwarder float's mnemonic, VAPID keys
and nostr secret keys. `.gitignore` excludes every `*config*.json` except the
examples, and all runtime state.

### With Docker

`compose.yml` runs `names`, `nwcpush` and `blossom` from the checkout,
mounted read-only. Configs and state live outside it, under a directory you
name in a `.env` file:

```sh
# .env
STATE=/srv/coinosv3-state
CLN_RPC=/path/to/lightning-rpc
```

```
$STATE/names/config.json     $STATE/names/data/      (names.db)
$STATE/nwcpush/config.json   $STATE/nwcpush/data/
                             $STATE/blossom/data/
```

```sh
docker network create net   # once; the services share it with the reverse proxy
docker compose up -d
```

## State

`names` keeps its state in SQLite (`names.db`, see `names/store.js`): one row
per name, offer, invoice and so on. Each change is a single transaction that
writes only the rows that changed. On its first start it imports the older
JSON state file named by `stateFile`, and a corrupt file stops startup rather
than starting empty. For a consistent backup while it runs, take a snapshot
through SQLite, not by copying the file:

```sh
sqlite3 names.db "VACUUM INTO 'names-backup.db'"
```

## Deploying

coinos deploys by pushing to a bare repository on the server.
`deploy/post-receive` is its hook. A push to `prod` checks the commit out,
installs dependencies, restarts the services whose files changed, and mirrors
the commit to [git.coinos.io](https://git.coinos.io) and gittr. Don't copy
files onto the server by hand: what runs should always be a commit.

```sh
git push prod HEAD:prod && git push origin HEAD:master
```

## Rolling back

The previous deploy is the previous commit: `git revert` and push, or push an
older commit to `prod` (`git push -f prod <commit>:prod`). `names` reads its
SQLite state either way. Before the SQLite change, the JSON file it was
imported from is still in the data directory, but it stops at the moment of
the import.

## Tests

```sh
bun test names/store.test.js
```

`tools/` holds the older end-to-end and unit scripts for the registrar, feed
and translation. They are run directly with `bun tools/<file>`.

## License

MIT, see [LICENSE](LICENSE).