upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/relay
diff options
context:
space:
mode:
authorBitflash-sh <btf01@proton.me>2026-07-24 11:18:11 -0300
committerBitflash-sh <btf01@proton.me>2026-07-24 11:18:11 -0300
commit51d617e70f90a972185917ea0096c1ecef2bfa8b (patch)
tree9fbbcd867de9de9d0776afdd02f594ba312c46eb /relay
Bitflash: fair-launch release
Satoshi's bitcoin-0.1.0 reborn with RandomX CPU proof of work, anonymous .btf addressing over Nostr with CGNAT rendezvous, automatic peer discovery, a headless Linux node, and Windows GUI wallet. Fair launch, no premine, 21M cap.
Diffstat (limited to 'relay')
-rw-r--r--relay/README.md57
-rw-r--r--relay/bitflashd.cpp45
-rw-r--r--relay/btfrv.cpp250
-rw-r--r--relay/btfrv.h59
-rw-r--r--relay/build.sh6
-rw-r--r--relay/install-bitflash-relay.sh326
6 files changed, 743 insertions, 0 deletions
diff --git a/relay/README.md b/relay/README.md
new file mode 100644
index 0000000..abf0f8d
--- /dev/null
+++ b/relay/README.md
@@ -0,0 +1,57 @@
1# bitflashd — Bitflash rendezvous relay (the "meeting node")
2
3This is the small always-on server that lets Bitflash nodes behind CGNAT reach
4each other. Both sides connect **out** to this relay; it pairs them by their
5`.btf` pubkey and forwards ciphertext. It holds **no keys** and reads nothing —
6the two nodes are encrypted end-to-end (X25519 + ChaCha20-Poly1305) over it.
7
8Run it on any machine with a public IP (a cheap VPS). It has **no dependencies**
9beyond a C++ compiler.
10
11## Build (on the VPS, Linux)
12
13```
14sudo apt-get update && sudo apt-get install -y g++ # if needed
15bash build.sh
16```
17
18## Run
19
20```
21./bitflashd 8434
22```
23
24Open the port in the firewall (both the VPS provider's panel and the OS):
25
26```
27sudo ufw allow 8434/tcp # if ufw is enabled
28```
29
30## Keep it running (systemd)
31
32Create `/etc/systemd/system/bitflashd.service`:
33
34```
35[Unit]
36Description=Bitflash rendezvous relay
37After=network.target
38
39[Service]
40ExecStart=/root/bitflash/relay/bitflashd 8434
41Restart=always
42User=root
43
44[Install]
45WantedBy=multi-user.target
46```
47
48Then:
49
50```
51sudo systemctl daemon-reload
52sudo systemctl enable --now bitflashd
53sudo systemctl status bitflashd
54```
55
56The relay's address for clients is then `YOUR_VPS_IP:8434` — that is what goes
57into a node's descriptor as its `meeting_node`.
diff --git a/relay/bitflashd.cpp b/relay/bitflashd.cpp
new file mode 100644
index 0000000..d4ced77
--- /dev/null
+++ b/relay/bitflashd.cpp
@@ -0,0 +1,45 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3//
4// bitflashd -- the headless Bitflash rendezvous relay (the "meeting node").
5// Runs on a machine with a public IP (a cheap VPS) and lets nodes behind CGNAT
6// reach each other: both sides connect OUT to this relay, which pairs them by
7// `.btf` pubkey and forwards ciphertext (it holds no keys and reads nothing).
8//
9// Build on the VPS (Linux):
10// g++ -std=gnu++14 -O2 -pthread bitflashd.cpp btfrv.cpp -o bitflashd
11// Run:
12// ./bitflashd 8434 # or any port you open in the firewall
13//
14// Cross-builds unchanged on Windows (for local testing):
15// g++ -std=gnu++14 -O2 -pthread bitflashd.cpp btfrv.cpp -lws2_32 -o bitflashd.exe
16
17#include "btfrv.h"
18#include <cstdio>
19#include <cstdlib>
20#include <cstring>
21
22int main(int argc, char* argv[])
23{
24 unsigned short port = 8434;
25 if (argc >= 2)
26 port = (unsigned short)atoi(argv[1]);
27
28 printf("bitflashd -- Bitflash rendezvous relay\n");
29 printf("listening on 0.0.0.0:%u (open this port in your firewall)\n", port);
30 fflush(stdout);
31
32 if (!btf::RvInit())
33 {
34 fprintf(stderr, "bitflashd: socket init failed\n");
35 return 1;
36 }
37
38 // Blocks forever, forwarding between paired peers.
39 if (!btf::RvRelayRun(port))
40 {
41 fprintf(stderr, "bitflashd: could not bind/listen on port %u\n", port);
42 return 1;
43 }
44 return 0;
45}
diff --git a/relay/btfrv.cpp b/relay/btfrv.cpp
new file mode 100644
index 0000000..ffda08c
--- /dev/null
+++ b/relay/btfrv.cpp
@@ -0,0 +1,250 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3//
4// Bitflash rendezvous transport. See btfrv.h.
5// Windows/Winsock implementation (Layer 6 will #ifdef this for Linux/BSD sockets).
6
7#include "btfrv.h"
8
9// Portable sockets: Winsock on Windows, BSD sockets on Linux/POSIX (for the
10// headless relay `bitflashd` that runs on the VPS).
11#ifdef _WIN32
12#include <winsock2.h>
13#include <ws2tcpip.h>
14#define CLOSESOCK closesocket
15#else
16#include <sys/socket.h>
17#include <netinet/in.h>
18#include <arpa/inet.h>
19#include <netdb.h>
20#include <unistd.h>
21#include <csignal>
22typedef int SOCKET;
23#define INVALID_SOCKET (-1)
24#define SOCKET_ERROR (-1)
25#define CLOSESOCK ::close
26#ifndef SD_BOTH
27#define SD_BOTH SHUT_RDWR
28#endif
29#endif
30
31#include <cstring>
32#include <cstdio>
33#include <string>
34#include <map>
35#include <thread>
36#include <mutex>
37#include <atomic>
38
39namespace btf
40{
41
42static std::atomic<bool> g_stop(false);
43static SOCKET g_listen = INVALID_SOCKET;
44
45// Services that have registered and are waiting for a client, keyed by pubkey.
46static std::map<std::string, SOCKET> g_waiting;
47static std::mutex g_mtx;
48
49bool RvInit()
50{
51 static bool done = false;
52 if (done) return true;
53#ifdef _WIN32
54 WSADATA wsa;
55 if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return false;
56#else
57 signal(SIGPIPE, SIG_IGN); // don't die when writing to a closed peer
58#endif
59 done = true;
60 return true;
61}
62
63// ---- low-level helpers ----
64
65static bool ReadN(SOCKET s, void* buf, int n)
66{
67 char* p = (char*)buf;
68 int off = 0;
69 while (off < n)
70 {
71 int r = recv(s, p + off, n - off, 0);
72 if (r <= 0) return false;
73 off += r;
74 }
75 return true;
76}
77
78static bool WriteN(SOCKET s, const void* buf, int n)
79{
80 const char* p = (const char*)buf;
81 int off = 0;
82 while (off < n)
83 {
84 int r = send(s, p + off, n - off, 0);
85 if (r <= 0) return false;
86 off += r;
87 }
88 return true;
89}
90
91static SOCKET ConnectTo(const char* host, unsigned short port)
92{
93 struct addrinfo hints, *res = NULL;
94 memset(&hints, 0, sizeof(hints));
95 hints.ai_family = AF_INET;
96 hints.ai_socktype = SOCK_STREAM;
97 char portstr[16]; sprintf(portstr, "%u", (unsigned)port);
98 if (getaddrinfo(host, portstr, &hints, &res) != 0 || !res) return INVALID_SOCKET;
99 SOCKET s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
100 if (s == INVALID_SOCKET) { freeaddrinfo(res); return INVALID_SOCKET; }
101 if (connect(s, res->ai_addr, (int)res->ai_addrlen) != 0)
102 {
103 CLOSESOCK(s); freeaddrinfo(res); return INVALID_SOCKET;
104 }
105 freeaddrinfo(res);
106 return s;
107}
108
109// ---- relay side ----
110
111// Pump bytes from `from` to `to`. Owns closing `from`; shuts down `to` on exit
112// so the peer-direction thread unblocks and closes its own socket exactly once.
113static void Forward(SOCKET from, SOCKET to)
114{
115 char buf[32 * 1024];
116 for (;;)
117 {
118 int r = recv(from, buf, sizeof(buf), 0);
119 if (r <= 0) break;
120 if (!WriteN(to, buf, r)) break;
121 }
122 shutdown(to, SD_BOTH);
123 CLOSESOCK(from);
124}
125
126static void HandleIncoming(SOCKET s)
127{
128 unsigned char hdr[33];
129 if (!ReadN(s, hdr, 33)) { CLOSESOCK(s); return; }
130 char role = (char)hdr[0];
131 std::string pubkey((char*)hdr + 1, 32);
132
133 if (role == 'S')
134 {
135 // Register and wait; a future client will pair us.
136 std::lock_guard<std::mutex> lk(g_mtx);
137 // If a stale service for this key exists, drop it.
138 std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey);
139 if (it != g_waiting.end()) CLOSESOCK(it->second);
140 g_waiting[pubkey] = s;
141 // leave the socket open in the map; do not close here.
142 }
143 else if (role == 'C')
144 {
145 SOCKET svc = INVALID_SOCKET;
146 {
147 std::lock_guard<std::mutex> lk(g_mtx);
148 std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey);
149 if (it != g_waiting.end()) { svc = it->second; g_waiting.erase(it); }
150 }
151 if (svc == INVALID_SOCKET)
152 {
153 unsigned char no = 0x00;
154 WriteN(s, &no, 1);
155 CLOSESOCK(s);
156 return;
157 }
158 // Pair: tell both sides, then forward raw bytes bidirectionally.
159 unsigned char ok = 0x01;
160 bool a = WriteN(svc, &ok, 1);
161 bool b = WriteN(s, &ok, 1);
162 if (!a || !b) { CLOSESOCK(svc); CLOSESOCK(s); return; }
163 std::thread(Forward, s, svc).detach();
164 std::thread(Forward, svc, s).detach();
165 }
166 else
167 {
168 CLOSESOCK(s);
169 }
170}
171
172bool RvRelayRun(unsigned short port)
173{
174 if (!RvInit()) return false;
175 g_stop = false;
176
177 g_listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
178 if (g_listen == INVALID_SOCKET) return false;
179 int yes = 1;
180 setsockopt(g_listen, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes));
181
182 struct sockaddr_in addr;
183 memset(&addr, 0, sizeof(addr));
184 addr.sin_family = AF_INET;
185 addr.sin_addr.s_addr = INADDR_ANY;
186 addr.sin_port = htons(port);
187 if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; }
188 if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; }
189
190 while (!g_stop)
191 {
192 struct sockaddr_in cli;
193 socklen_t len = sizeof(cli);
194 SOCKET s = accept(g_listen, (struct sockaddr*)&cli, &len);
195 if (s == INVALID_SOCKET)
196 {
197 if (g_stop) break;
198 continue;
199 }
200 std::thread(HandleIncoming, s).detach();
201 }
202 CLOSESOCK(g_listen);
203 g_listen = INVALID_SOCKET;
204 return true;
205}
206
207void RvRelayStop()
208{
209 g_stop = true;
210 if (g_listen != INVALID_SOCKET)
211 CLOSESOCK(g_listen); // unblock accept()
212}
213
214// ---- service / client helpers ----
215
216RvSocket RvServiceRegister(const char* relay_host, unsigned short port,
217 const unsigned char my_pubkey[32])
218{
219 if (!RvInit()) return RV_INVALID;
220 SOCKET s = ConnectTo(relay_host, port);
221 if (s == INVALID_SOCKET) return RV_INVALID;
222 unsigned char hdr[33];
223 hdr[0] = (unsigned char)'S';
224 memcpy(hdr + 1, my_pubkey, 32);
225 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; }
226 unsigned char paired = 0;
227 if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; }
228 return (RvSocket)s;
229}
230
231RvSocket RvClientConnect(const char* relay_host, unsigned short port,
232 const unsigned char target_pubkey[32])
233{
234 if (!RvInit()) return RV_INVALID;
235 SOCKET s = ConnectTo(relay_host, port);
236 if (s == INVALID_SOCKET) return RV_INVALID;
237 unsigned char hdr[33];
238 hdr[0] = (unsigned char)'C';
239 memcpy(hdr + 1, target_pubkey, 32);
240 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; }
241 unsigned char paired = 0;
242 if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; }
243 return (RvSocket)s;
244}
245
246bool RvReadN(RvSocket s, void* buf, int n) { return ReadN((SOCKET)s, buf, n); }
247bool RvWriteN(RvSocket s, const void* buf, int n) { return WriteN((SOCKET)s, buf, n); }
248void RvClose(RvSocket s) { if (s != RV_INVALID) CLOSESOCK((SOCKET)s); }
249
250} // namespace btf
diff --git a/relay/btfrv.h b/relay/btfrv.h
new file mode 100644
index 0000000..c776201
--- /dev/null
+++ b/relay/btfrv.h
@@ -0,0 +1,59 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3//
4// Bitflash rendezvous transport -- the "meeting node" that lets two nodes behind
5// CGNAT connect without a public IP or port forwarding. Both sides connect OUT
6// to the relay (which HAS a public IP, e.g. the VPS); the relay pairs them by
7// `.btf` pubkey and forwards raw bytes between them. The endpoints then run the
8// echan end-to-end channel on top, so the relay sees only ciphertext and cannot
9// MITM (the client boxes to the service's STATIC key from its self-certified
10// descriptor -- a substituting relay just breaks the channel, never reads it).
11//
12// This is the server that runs on the VPS (headless, Layer 6 ports it to Linux)
13// plus the client/service helpers the desktop node uses.
14
15#ifndef BITFLASH_BTFRV_H
16#define BITFLASH_BTFRV_H
17
18#include <stdint.h>
19
20namespace btf
21{
22
23// A connected, paired rendezvous socket (opaque). Read/write raw bytes; the
24// caller layers echan on top. Close with RvClose.
25typedef intptr_t RvSocket;
26static const RvSocket RV_INVALID = (RvSocket)-1;
27
28// Initialize sockets (Winsock on Windows). Idempotent.
29bool RvInit();
30
31// ---- relay side (runs on the VPS) ----
32// Run the rendezvous relay forever on `port`, forwarding between paired peers.
33// Blocks; run it in a thread. Returns false if it can't bind/listen.
34bool RvRelayRun(unsigned short port);
35
36// Stop a running relay (signals RvRelayRun to return).
37void RvRelayStop();
38
39// ---- service side (the node being reached by its .btf address) ----
40// Register at the relay under `my_pubkey` (32 bytes) and block until a client is
41// paired to us. Returns the paired socket, or RV_INVALID on error.
42RvSocket RvServiceRegister(const char* relay_host, unsigned short port,
43 const unsigned char my_pubkey[32]);
44
45// ---- client side (the node dialing a .btf address) ----
46// Connect through the relay to the service registered under `target_pubkey`.
47// Returns the paired socket, or RV_INVALID on error.
48RvSocket RvClientConnect(const char* relay_host, unsigned short port,
49 const unsigned char target_pubkey[32]);
50
51// Blocking read/write of exactly n bytes over a rendezvous socket. Return false
52// on error/EOF.
53bool RvReadN(RvSocket s, void* buf, int n);
54bool RvWriteN(RvSocket s, const void* buf, int n);
55void RvClose(RvSocket s);
56
57} // namespace btf
58
59#endif
diff --git a/relay/build.sh b/relay/build.sh
new file mode 100644
index 0000000..7796523
--- /dev/null
+++ b/relay/build.sh
@@ -0,0 +1,6 @@
1#!/usr/bin/env bash
2# Build the Bitflash rendezvous relay (bitflashd) on Linux. No dependencies
3# beyond a C++ compiler -- the relay only forwards bytes, it holds no keys.
4set -e
5g++ -std=gnu++14 -O2 -pthread bitflashd.cpp btfrv.cpp -o bitflashd
6echo "built ./bitflashd -- run it with: ./bitflashd <port> (e.g. ./bitflashd 8434)"
diff --git a/relay/install-bitflash-relay.sh b/relay/install-bitflash-relay.sh
new file mode 100644
index 0000000..77a46b2
--- /dev/null
+++ b/relay/install-bitflash-relay.sh
@@ -0,0 +1,326 @@
1#!/usr/bin/env bash
2# Bitflash rendezvous relay -- one-shot installer for Linux.
3# Anyone can run this on a machine with a PUBLIC IP (a cheap VPS) to become a
4# meeting node that lets Bitflash users behind CGNAT reach each other.
5# The relay holds NO keys and reads NOTHING -- it only forwards ciphertext.
6#
7# Usage:
8# sudo bash install-bitflash-relay.sh [PORT]
9# PORT defaults to 8434. Remember to open that port in your firewall/panel.
10
11set -e
12PORT="${1:-8434}"
13DIR="$HOME/bitflash-relay"
14mkdir -p "$DIR" && cd "$DIR"
15
16echo ">> writing sources to $DIR"
17
18cat > btfrv.h <<'BTFRV_H_EOF'
19// Copyright (c) 2026 Bitflash developers
20// Distributed under the MIT/X11 software license.
21#ifndef BITFLASH_BTFRV_H
22#define BITFLASH_BTFRV_H
23#include <stdint.h>
24namespace btf
25{
26typedef intptr_t RvSocket;
27static const RvSocket RV_INVALID = (RvSocket)-1;
28bool RvInit();
29bool RvRelayRun(unsigned short port);
30void RvRelayStop();
31RvSocket RvServiceRegister(const char* relay_host, unsigned short port,
32 const unsigned char my_pubkey[32]);
33RvSocket RvClientConnect(const char* relay_host, unsigned short port,
34 const unsigned char target_pubkey[32]);
35bool RvReadN(RvSocket s, void* buf, int n);
36bool RvWriteN(RvSocket s, const void* buf, int n);
37void RvClose(RvSocket s);
38} // namespace btf
39#endif
40BTFRV_H_EOF
41
42cat > btfrv.cpp <<'BTFRV_CPP_EOF'
43// Copyright (c) 2026 Bitflash developers
44// Distributed under the MIT/X11 software license.
45#include "btfrv.h"
46#ifdef _WIN32
47#include <winsock2.h>
48#include <ws2tcpip.h>
49#define CLOSESOCK closesocket
50#else
51#include <sys/socket.h>
52#include <netinet/in.h>
53#include <arpa/inet.h>
54#include <netdb.h>
55#include <unistd.h>
56#include <csignal>
57typedef int SOCKET;
58#define INVALID_SOCKET (-1)
59#define SOCKET_ERROR (-1)
60#define CLOSESOCK ::close
61#ifndef SD_BOTH
62#define SD_BOTH SHUT_RDWR
63#endif
64#endif
65#include <cstring>
66#include <cstdio>
67#include <string>
68#include <map>
69#include <thread>
70#include <mutex>
71#include <atomic>
72namespace btf
73{
74static std::atomic<bool> g_stop(false);
75static SOCKET g_listen = INVALID_SOCKET;
76static std::map<std::string, SOCKET> g_waiting;
77static std::mutex g_mtx;
78bool RvInit()
79{
80 static bool done = false;
81 if (done) return true;
82#ifdef _WIN32
83 WSADATA wsa;
84 if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return false;
85#else
86 signal(SIGPIPE, SIG_IGN);
87#endif
88 done = true;
89 return true;
90}
91static bool ReadN(SOCKET s, void* buf, int n)
92{
93 char* p = (char*)buf;
94 int off = 0;
95 while (off < n)
96 {
97 int r = recv(s, p + off, n - off, 0);
98 if (r <= 0) return false;
99 off += r;
100 }
101 return true;
102}
103static bool WriteN(SOCKET s, const void* buf, int n)
104{
105 const char* p = (const char*)buf;
106 int off = 0;
107 while (off < n)
108 {
109 int r = send(s, p + off, n - off, 0);
110 if (r <= 0) return false;
111 off += r;
112 }
113 return true;
114}
115static SOCKET ConnectTo(const char* host, unsigned short port)
116{
117 struct addrinfo hints, *res = NULL;
118 memset(&hints, 0, sizeof(hints));
119 hints.ai_family = AF_INET;
120 hints.ai_socktype = SOCK_STREAM;
121 char portstr[16]; sprintf(portstr, "%u", (unsigned)port);
122 if (getaddrinfo(host, portstr, &hints, &res) != 0 || !res) return INVALID_SOCKET;
123 SOCKET s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
124 if (s == INVALID_SOCKET) { freeaddrinfo(res); return INVALID_SOCKET; }
125 if (connect(s, res->ai_addr, (int)res->ai_addrlen) != 0)
126 {
127 CLOSESOCK(s); freeaddrinfo(res); return INVALID_SOCKET;
128 }
129 freeaddrinfo(res);
130 return s;
131}
132static void Forward(SOCKET from, SOCKET to)
133{
134 char buf[32 * 1024];
135 for (;;)
136 {
137 int r = recv(from, buf, sizeof(buf), 0);
138 if (r <= 0) break;
139 if (!WriteN(to, buf, r)) break;
140 }
141 shutdown(to, SD_BOTH);
142 CLOSESOCK(from);
143}
144static void HandleIncoming(SOCKET s)
145{
146 unsigned char hdr[33];
147 if (!ReadN(s, hdr, 33)) { CLOSESOCK(s); return; }
148 char role = (char)hdr[0];
149 std::string pubkey((char*)hdr + 1, 32);
150 if (role == 'S')
151 {
152 std::lock_guard<std::mutex> lk(g_mtx);
153 std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey);
154 if (it != g_waiting.end()) CLOSESOCK(it->second);
155 g_waiting[pubkey] = s;
156 }
157 else if (role == 'C')
158 {
159 SOCKET svc = INVALID_SOCKET;
160 {
161 std::lock_guard<std::mutex> lk(g_mtx);
162 std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey);
163 if (it != g_waiting.end()) { svc = it->second; g_waiting.erase(it); }
164 }
165 if (svc == INVALID_SOCKET)
166 {
167 unsigned char no = 0x00;
168 WriteN(s, &no, 1);
169 CLOSESOCK(s);
170 return;
171 }
172 unsigned char ok = 0x01;
173 bool a = WriteN(svc, &ok, 1);
174 bool b = WriteN(s, &ok, 1);
175 if (!a || !b) { CLOSESOCK(svc); CLOSESOCK(s); return; }
176 std::thread(Forward, s, svc).detach();
177 std::thread(Forward, svc, s).detach();
178 }
179 else
180 {
181 CLOSESOCK(s);
182 }
183}
184bool RvRelayRun(unsigned short port)
185{
186 if (!RvInit()) return false;
187 g_stop = false;
188 g_listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
189 if (g_listen == INVALID_SOCKET) return false;
190 int yes = 1;
191 setsockopt(g_listen, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes));
192 struct sockaddr_in addr;
193 memset(&addr, 0, sizeof(addr));
194 addr.sin_family = AF_INET;
195 addr.sin_addr.s_addr = INADDR_ANY;
196 addr.sin_port = htons(port);
197 if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; }
198 if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; }
199 while (!g_stop)
200 {
201 struct sockaddr_in cli;
202 socklen_t len = sizeof(cli);
203 SOCKET s = accept(g_listen, (struct sockaddr*)&cli, &len);
204 if (s == INVALID_SOCKET)
205 {
206 if (g_stop) break;
207 continue;
208 }
209 std::thread(HandleIncoming, s).detach();
210 }
211 CLOSESOCK(g_listen);
212 g_listen = INVALID_SOCKET;
213 return true;
214}
215void RvRelayStop()
216{
217 g_stop = true;
218 if (g_listen != INVALID_SOCKET)
219 CLOSESOCK(g_listen);
220}
221RvSocket RvServiceRegister(const char* relay_host, unsigned short port,
222 const unsigned char my_pubkey[32])
223{
224 if (!RvInit()) return RV_INVALID;
225 SOCKET s = ConnectTo(relay_host, port);
226 if (s == INVALID_SOCKET) return RV_INVALID;
227 unsigned char hdr[33];
228 hdr[0] = (unsigned char)'S';
229 memcpy(hdr + 1, my_pubkey, 32);
230 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; }
231 unsigned char paired = 0;
232 if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; }
233 return (RvSocket)s;
234}
235RvSocket RvClientConnect(const char* relay_host, unsigned short port,
236 const unsigned char target_pubkey[32])
237{
238 if (!RvInit()) return RV_INVALID;
239 SOCKET s = ConnectTo(relay_host, port);
240 if (s == INVALID_SOCKET) return RV_INVALID;
241 unsigned char hdr[33];
242 hdr[0] = (unsigned char)'C';
243 memcpy(hdr + 1, target_pubkey, 32);
244 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; }
245 unsigned char paired = 0;
246 if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; }
247 return (RvSocket)s;
248}
249bool RvReadN(RvSocket s, void* buf, int n) { return ReadN((SOCKET)s, buf, n); }
250bool RvWriteN(RvSocket s, const void* buf, int n) { return WriteN((SOCKET)s, buf, n); }
251void RvClose(RvSocket s) { if (s != RV_INVALID) CLOSESOCK((SOCKET)s); }
252} // namespace btf
253BTFRV_CPP_EOF
254
255cat > bitflashd.cpp <<'BITFLASHD_EOF'
256// Copyright (c) 2026 Bitflash developers
257// Distributed under the MIT/X11 software license.
258#include "btfrv.h"
259#include <cstdio>
260#include <cstdlib>
261#include <cstring>
262int main(int argc, char* argv[])
263{
264 unsigned short port = 8434;
265 if (argc >= 2)
266 port = (unsigned short)atoi(argv[1]);
267 printf("bitflashd -- Bitflash rendezvous relay\n");
268 printf("listening on 0.0.0.0:%u (open this port in your firewall)\n", port);
269 fflush(stdout);
270 if (!btf::RvInit())
271 {
272 fprintf(stderr, "bitflashd: socket init failed\n");
273 return 1;
274 }
275 if (!btf::RvRelayRun(port))
276 {
277 fprintf(stderr, "bitflashd: could not bind/listen on port %u\n", port);
278 return 1;
279 }
280 return 0;
281}
282BITFLASHD_EOF
283
284# g++ (Debian/Ubuntu: build-essential; RHEL/Alma: gcc-c++)
285if ! command -v g++ >/dev/null 2>&1; then
286 echo ">> installing g++"
287 if command -v apt-get >/dev/null 2>&1; then apt-get update && apt-get install -y g++
288 elif command -v dnf >/dev/null 2>&1; then dnf install -y gcc-c++
289 elif command -v yum >/dev/null 2>&1; then yum install -y gcc-c++
290 else echo "!! install a C++ compiler (g++) manually and re-run"; exit 1
291 fi
292fi
293
294echo ">> building bitflashd"
295g++ -std=gnu++14 -O2 -pthread bitflashd.cpp btfrv.cpp -o bitflashd
296echo ">> build OK: $DIR/bitflashd"
297
298# systemd service so it survives reboots (skipped if systemd is absent)
299if command -v systemctl >/dev/null 2>&1 && [ -d /etc/systemd/system ]; then
300 cat > /etc/systemd/system/bitflashd.service <<EOF
301[Unit]
302Description=Bitflash rendezvous relay
303After=network.target
304
305[Service]
306ExecStart=$DIR/bitflashd $PORT
307Restart=always
308User=$(id -un)
309
310[Install]
311WantedBy=multi-user.target
312EOF
313 systemctl daemon-reload
314 systemctl enable --now bitflashd
315 sleep 1
316 systemctl status bitflashd --no-pager || true
317 echo ">> relay running as a service on port $PORT"
318else
319 echo ">> systemd not found; run it manually: $DIR/bitflashd $PORT"
320fi
321
322echo
323echo "==================================================================="
324echo " DONE. Open TCP port $PORT in your firewall / cloud panel."
325echo " Your relay address for Bitflash users: <YOUR_PUBLIC_IP>:$PORT"
326echo "==================================================================="