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
|
# GRASP Mirror NIP-46 Remote Signing — Implementation Plan
## Goal
Build and deploy the grasp-mirror daemon with NIP-46 remote signing so it can publish authenticated `kind:30618` state events and push git data to GRASP servers, without storing nsec keys on the VPS.
## Architecture
```
┌─────────────┐
│ Amber │
│ (phone) │
└──────┬──────┘
│ NIP-46 (kind:24133)
│ NIP-04 encrypted
┌──────┴──────┐
│ Nostr Relay │
│ (relay/ngit)│
└──────┬──────┘
│
┌──────┴──────┐
│ grasp-mirror│
│ daemon │
│ (VPS) │
└──────┬──────┘
│ git push + kind:30618
┌──────┴──────┐
│ GRASP │
│ servers │
└─────────────┘
```
- Daemon generates client keypairs per npub, persists in SQLite
- Produces `nostrconnect://` pairing URIs shown in health endpoint
- User opens URI in Amber → NIP-46 session established
- Before git push, daemon builds unsigned `kind:30618` from repo refs
- Sends `sign_event` request to signer via NIP-46 relay protocol
- Amber signs on phone → daemon receives signed event → publishes + pushes
## Checklist
### Phase 1: Fix compile errors locally
- [ ] Fix `nip46.rs` — `notification.as_ref()` → match on owned `RelayPoolNotification`
- [ ] Fix `nip46.rs` — ensure `message.id()` borrow is cloned before consuming message
- [ ] Fix `git_mirror.rs` — accept shared `nostr_sdk::Client` instead of creating throwaway per server
- [ ] Fix `main.rs` — pass `nostr_client` to `git_mirror.mirror_repo_to_servers`
- [ ] Verify `db.rs` — `bool` FromRow works with sqlx 0.8 sqlite INTEGER
- [ ] Pin Rust 1.95.0 in Ansible role `tasks/main.yml`
- [ ] Commit and push to GRASP origin
### Phase 2: Build on VPS
- [ ] GRASP server running on target VPS (dependency: migration session)
- [ ] Push SSH key to VPS for Ansible key-based auth
- [ ] Run `ansible-playbook playbooks/30-grasp-mirror.yml -v`
- [ ] Fix any remaining compile errors iteratively
- [ ] Binary installed at `/usr/local/bin/grasp-mirror`
- [ ] Systemd service `grasp-mirror` running
### Phase 3: Verify and pair
- [ ] Health endpoint responds: `curl http://localhost:7335/health`
- [ ] Response includes `nip46` array with session statuses
- [ ] Each unpaired npub shows `pairing_uri`
- [ ] Pair npub 1 (`npub12m5...`) via Amber
- [ ] Pair npub 2 (`npub19jx...`) via Amber
- [ ] Pair npub 3 (`npub1c03...`) via Amber
- [ ] Health endpoint shows all 3 sessions `connected: true`
- [ ] Test signing: daemon builds and signs a `kind:30618` event
- [ ] Test push: git data pushes to a target GRASP server
### Phase 4: Document roadmap
- [ ] Add QEMU/OpenWRT orchestration roadmap to PLAN.md or PROGRESS.md
## Key Files
| File | Role |
|---|---|
| `src/nip46.rs` | NIP-46 client: sessions, NIP-04 encrypt/decrypt, relay listener, `sign_event()` |
| `src/db.rs` | `nip46_sessions` table + CRUD methods |
| `src/config.rs` | `Nip46Config` with relays + signing_timeout_secs |
| `src/git_mirror.rs` | Builds unsigned `kind:30618`, signs via NIP-46, publishes before push |
| `src/http_health.rs` | NIP-46 session status in `/health` JSON |
| `src/main.rs` | Wires NIP-46 init + listener into daemon startup |
| `ansible/roles/grasp_mirror/tasks/main.yml` | Ansible deploy tasks (build + install) |
| `ansible/roles/grasp_mirror/defaults/main.yml` | NIP-46 relay + timeout vars |
| `ansible/roles/grasp_mirror/templates/config.toml.j2` | Config with `[nip46]` section |
## Constraints
- No `any` type in TypeScript (N/A — Rust project)
- No comments in code unless requested
- Use `pnpm` for JS, `cargo` for Rust
- Rust 1.95.0 required (nostr-sdk 0.39 doesn't compile on 1.85)
- NIP-04 encryption for NIP-46 messages (not NIP-44 — simpler, Amber supports it)
- Signing timeout: 7 days (604800 seconds) — queue-and-wait model
- GRASP push auth requires signed `kind:30618` by maintainer, not HTTP auth
## Future Roadmap: QEMU/OpenWRT Test Orchestration
After migration, repurpose old VPS (2 vCPU, 8GB RAM, 99GB disk) as dedicated OpenWRT test runner:
1. **Deploy loom-worker** on old VPS — decentralized compute marketplace via Nostr
2. **Write QEMU execution adapter** for loom — Unix domain socket that spins up OpenWRT instances, runs tests, streams results
3. **Use existing act-runner** custom pipeline to submit loom jobs from CI
4. **Do NOT build custom orchestration** — loom already has standardized event kinds (5001/5100/5101), Cashu payments, Blossom result storage, encrypted communication
5. **Evaluate loom from budabit** (`nostr://npub1hw6amg8p24ne08c9gdq8hhpqx0t0pwanpae9z25crn7m9uy7yarse465gr/relay.ngit.dev/loom-worker`) before building anything custom
Loom's pluggable adapter architecture means we write ~200 lines of Deno/TypeScript for the QEMU adapter and get the entire Nostr job queue, payment, and result streaming for free.
|