rdrop 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rshare contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,264 @@
1
+ # rshare
2
+
3
+ Send files and folders from one computer to another (Windows, macOS or Linux) with
4
+ one command on each side: across the room or across the country. Fast, end-to-end
5
+ encrypted, no accounts.
6
+
7
+ ```text
8
+ computer A computer B
9
+ $ rshare add .
10
+ $ rshare share
11
+ rshare receive 04fccatxfc… ──────► $ rshare receive 04fccatxfc…
12
+ ✓ Received 505 files (1.0 GB) in 9.4s
13
+ ```
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ npm install -g rdrop
19
+ ```
20
+
21
+ The package is called `rdrop` on npm, and it installs the `rshare` command. This needs
22
+ Node.js 16 or newer. The package contains a small native program for Windows,
23
+ macOS and Linux (x64 and ARM), and Node is only used to start it. `pip install rdrop` is
24
+ planned.
25
+
26
+ ## Use
27
+
28
+ On the computer that has the files:
29
+
30
+ ```sh
31
+ cd my-project
32
+ rshare add . # stage the folder you're in
33
+ rshare add ~/notes.pdf # stage more files or folders, from anywhere
34
+ rshare share
35
+ ```
36
+
37
+ `rshare share` prints a command like `rshare receive 04fccatxfcqn51s22qkh0x33crye0sbz00003g8g`
38
+ and copies it to your clipboard. Send it to the other person any way you like (chat, email),
39
+ and they run it on their computer:
40
+
41
+ ```sh
42
+ rshare receive 04fccatxfcqn51s22qkh0x33crye0sbz00003g8g
43
+ ```
44
+
45
+ The files land in the folder where they ran the command (or in `--out <folder>`).
46
+
47
+ On the same network the two computers connect directly. To send to someone on a
48
+ **different network** (a friend at their home, say), you need a relay. Set it up once, as
49
+ described in [Sharing over the internet](#sharing-over-the-internet).
50
+
51
+ ### Commands
52
+
53
+ | Command | What it does |
54
+ |---|---|
55
+ | `rshare add <path>...` | Stage files or folders. `.` is the current folder. Wildcards work (`rshare add *.pdf`), even in cmd and PowerShell. |
56
+ | `rshare list` | Show what's staged, with sizes. |
57
+ | `rshare remove <path or #>` | Unstage something, by path or by its number in `list`. |
58
+ | `rshare clear` | Unstage everything. |
59
+ | `rshare share [path...]` | Share the staged items, or only the paths given (`rshare share movie.mp4`). |
60
+ | `rshare receive <code>` | Download everything the other computer is sharing. |
61
+ | `rshare config` | Show settings. `rshare config relay <address>` sets the relay to use. |
62
+ | `rshare relay` | Run a relay server (see below). |
63
+
64
+ | Option | |
65
+ |---|---|
66
+ | `share --keep` | Keep sharing after the first download. By default a code works once. |
67
+ | `share --relay <address>` | Use this relay for this share. |
68
+ | `share --local` | Don't use the relay this time. |
69
+ | `share --relay-only` | Skip the direct connection and always go through the relay. |
70
+ | `share --port <n>` | Port to listen on locally (default 7878, or any free port if that's taken). |
71
+ | `share --host <ip>` | Listen only on this local address. |
72
+ | `receive --out <dir>` | Where to save files. |
73
+ | `receive --overwrite` | Replace files that differ instead of saving the new copy as `name (1).ext`. |
74
+ | `add/share --force` | Allow sharing your home folder or another broad folder. |
75
+
76
+ ### Good to know
77
+
78
+ - **Interrupted transfers resume.** Run the same `rshare receive` command again. Finished files
79
+ are skipped and a half-finished file continues where it stopped. This also works with a new
80
+ code if the sender restarted `rshare share`.
81
+ - **Nothing gets overwritten.** If the receiver already has a different file with the same
82
+ name, the new one is saved as `name (1).ext`. Identical files are skipped.
83
+ - **Safe folders only.** `rshare add .` won't share your home folder (where a new terminal
84
+ opens) unless you add `--force`, and it never shares a whole drive or an operating-system
85
+ folder.
86
+ - **Left out:** shortcuts/symlinks and OS clutter (`.DS_Store`, `Thumbs.db`, `desktop.ini`).
87
+ File names that Windows can't store (`what?.txt` from a Mac) are adjusted on arrival.
88
+ - **Firewalls (same-network sharing).** The first time you share on Windows, Windows Defender
89
+ Firewall asks whether to allow rshare: allow it on **Private networks**. If your Wi-Fi is
90
+ marked as a *Public* network, other computers on it can't connect; switch it to Private in
91
+ Settings → Network & internet. macOS may ask to accept incoming connections: click Allow.
92
+ Sharing through a relay needs no firewall changes, because both computers connect out.
93
+
94
+ ## Sharing over the internet
95
+
96
+ Two computers in different homes usually can't connect to each other: home routers block
97
+ incoming connections, and many internet providers put whole neighborhoods behind one shared
98
+ address (carrier-grade NAT). The fix that always works is a **relay**: a small server both
99
+ computers can reach, which passes the data between them. Tools like croc and AnyDesk work
100
+ the same way.
101
+
102
+ The relay is built into rshare (`rshare relay`). You run it once on a server with a public
103
+ address, then point rshare at it:
104
+
105
+ ```sh
106
+ rshare config relay wss://your-relay.example.com # on each computer that shares
107
+ ```
108
+
109
+ That's all. `rshare share` now prints a code that works from anywhere, and **the receiver
110
+ doesn't configure anything**, because the code says where the relay is. When both
111
+ computers turn out to be on the same network, they still connect directly, which is faster
112
+ and doesn't touch the relay.
113
+
114
+ The relay can't read anything. The encryption runs end to end between the two computers,
115
+ straight through it, and the relay never sees the code's secret. It does see both
116
+ computers' IP addresses, when a transfer happens and how big it is.
117
+
118
+ ### Where to run the relay
119
+
120
+ Pick whichever suits you. The relay uses very little memory or CPU. What it does use is
121
+ bandwidth: every byte sent goes through it once in and once out.
122
+
123
+ **A. Any small Linux server.** This means a VPS ($4–6/month at DigitalOcean, Hetzner, Vultr
124
+ and others), the free Oracle Cloud VM, or a home server with a port forwarded.
125
+
126
+ ```sh
127
+ # from this repository, after `npm run build` (use linux-arm64 for ARM servers):
128
+ scp packages/npm/dist/linux-x64/rshare you@SERVER:/tmp/rshare
129
+ ssh you@SERVER
130
+ sudo install -m 755 /tmp/rshare /usr/local/bin/rshare
131
+ rshare relay # try it; Ctrl+C to stop
132
+ ```
133
+
134
+ To keep it running and have it start on boot, copy
135
+ `deploy/relay/rshare-relay.service` (in the source repository) to
136
+ `/etc/systemd/system/` and run `sudo systemctl enable --now rshare-relay`. Then open port
137
+ 8080 in the provider's firewall ("security group"), and on your computer:
138
+
139
+ ```sh
140
+ rshare config relay ws://SERVER_IP:8080
141
+ ```
142
+
143
+ **B. Render, free, with no server to manage.** Push this repository to GitHub, then on
144
+ [render.com](https://render.com) choose **New → Blueprint** and pick the repository. It reads
145
+ `render.yaml`, builds the relay and gives you an address like
146
+ `https://rshare-relay-abcd.onrender.com`. Then run:
147
+
148
+ ```sh
149
+ rshare config relay wss://rshare-relay-abcd.onrender.com
150
+ ```
151
+
152
+ Free Render services go to sleep after about 15 minutes without traffic. The first share
153
+ after that waits up to a minute while it wakes up, and the free plan has a monthly
154
+ bandwidth allowance. Any other host that runs Docker images and supports WebSockets works
155
+ too (Railway, Fly.io, Koyeb…).
156
+
157
+ **C. Docker, anywhere:**
158
+
159
+ ```sh
160
+ docker build -f deploy/relay/Dockerfile -t rshare-relay .
161
+ docker run -d -p 8080:8080 --restart unless-stopped rshare-relay
162
+ ```
163
+
164
+ ### Make it the default for everyone
165
+
166
+ Put the relay's address in the `DEFAULT_RELAY` file, run `npm run build`,
167
+ and publish. Every copy of rshare installed from that build uses your relay automatically:
168
+ nobody runs `rshare config`, and share codes stay short because they don't need to spell out
169
+ the address.
170
+
171
+ ### Keeping your relay to yourself
172
+
173
+ An open relay lets anyone relay through it. They still can't read anyone else's files, but
174
+ they would use your bandwidth. To restrict it, start it with a key and include the key in
175
+ the address:
176
+
177
+ ```sh
178
+ rshare relay --key s3cret # or set RSHARE_RELAY_KEY
179
+ rshare config relay wss://your-relay.example.com/?key=s3cret
180
+ ```
181
+
182
+ The key travels inside share codes, so receivers don't need to know it. (If you publish a
183
+ build with the key in `DEFAULT_RELAY`, assume it's public.)
184
+
185
+ ### No server at all?
186
+
187
+ If both people install [Tailscale](https://tailscale.com) (free for personal use), their
188
+ computers behave as if they were on the same network. rshare then connects directly without
189
+ a relay.
190
+
191
+ ## Security
192
+
193
+ - Each `rshare share` creates a new random 128-bit secret. It exists only inside the code.
194
+ Without it nobody can download anything, and it can't be guessed.
195
+ - Everything is encrypted with TLS 1.3. Both sides also prove they know the secret, tied to
196
+ that particular encrypted connection. So nobody in the middle (including a relay) can read
197
+ the traffic or pretend to be the sender. (Details: `core/internal/transfer/secure.go`.)
198
+ - On the local network, the sender listens only on private addresses (192.168.x.x,
199
+ 10.x.x.x, 172.16–31.x.x, 100.64–127.x.x for VPNs like Tailscale, 169.254.x.x for direct
200
+ cables), never on a public internet address. Across networks it connects *out* to the
201
+ relay; nothing is opened up on your router.
202
+ - The relay knows a share only by a one-way hash of its secret, so it can't work the secret
203
+ out. Someone who learned that hash could knock on the share, but without the secret the
204
+ sender turns them away.
205
+ - A receiver can only download what was shared. It can't browse the sender's disk.
206
+ Incoming file names are checked so a malicious sender can't write outside the
207
+ destination folder.
208
+ - While a share is running, treat the code like a password.
209
+
210
+ ## How it works, and why it's fast
211
+
212
+ - The core is one native program written in Go: no runtime to install, about 8 MB, and it
213
+ starts instantly. The npm package is a thin launcher for it, and the pip package will wrap
214
+ the same program.
215
+ - On the same network the computers connect directly, so the speed is set by your network
216
+ and disks. Over loopback it moved 1 GB (encrypted) at about 500 MB/s. Through a relay, the
217
+ limit is usually the sender's upload speed.
218
+ - The code contains all of the sender's local addresses plus the relay. The receiver tries
219
+ the direct addresses at once and the relay a moment later, then uses whichever works first.
220
+ Nobody has to work out which IP address to use.
221
+ - Big files stream in large chunks. Folders full of small files are fetched in batches over
222
+ 4 parallel connections, so they don't crawl.
223
+ - If the connection drops, the receiver reconnects and resumes mid-file by itself.
224
+ - The relay speaks WebSocket, so it can sit behind ordinary HTTPS web hosting on port 443.
225
+ Even strict office and campus networks allow that.
226
+
227
+ ## Develop
228
+
229
+ You need Go 1.22+ and Node.js 16+.
230
+
231
+ ```sh
232
+ npm run build:dev # build for this machine only
233
+ npm test # Go unit and end-to-end tests (including the relay)
234
+ npm run build # cross-compile all 6 platforms into packages/npm/dist
235
+ npm run pack # build, then create packages/npm/rshare-<version>.tgz
236
+ ```
237
+
238
+ To try the packed tarball: `npm i -g ./packages/npm/rdrop-0.1.0.tgz`.
239
+ To release: bump `VERSION`, run `npm run build`, then `cd packages/npm && npm publish`.
240
+
241
+ ```text
242
+ core/ Go source of the rshare program (all the logic)
243
+ cmd/rshare/ entry point
244
+ internal/cli/ commands and output
245
+ internal/config/ per-user settings (which relay to use)
246
+ internal/relay/ the relay server, and the client side senders/receivers use
247
+ internal/stage/ staging list, and the "don't share my whole drive" guard
248
+ internal/transfer/ share codes, encryption, sender, receiver
249
+ internal/ui/ colors, progress bar, clipboard
250
+ deploy/relay/ Dockerfile and systemd service for running a relay
251
+ packages/npm/ npm package: launcher + prebuilt binaries (dist/ is generated)
252
+ packages/python/ pip package skeleton (not published yet)
253
+ scripts/build.mjs cross-compiles core/ for every platform
254
+ VERSION one version number for every package
255
+ DEFAULT_RELAY relay address built into every copy (empty = none)
256
+ render.yaml one-click relay deployment on Render
257
+ ```
258
+
259
+ ## Roadmap
260
+
261
+ - `pip install rdrop` (one wheel per platform; the skeleton is in `packages/python`)
262
+ - Direct connections between different networks (NAT hole punching) where routers allow it,
263
+ so the relay is only the fallback
264
+ - One npm package per platform, so an install downloads only the binary it needs
package/bin/rshare.js ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Thin launcher: all the real work happens in the native rshare binary.
5
+
6
+ const { spawn } = require('child_process');
7
+ const { binaryPath } = require('../lib/binary');
8
+
9
+ let bin;
10
+ try {
11
+ bin = binaryPath();
12
+ } catch (err) {
13
+ console.error(err.message);
14
+ process.exit(1);
15
+ }
16
+
17
+ const child = spawn(bin, process.argv.slice(2), { stdio: 'inherit' });
18
+
19
+ // Ctrl+C reaches the binary directly (it shares the terminal) and the binary
20
+ // shuts down cleanly on its own, so the launcher just waits for it.
21
+ process.on('SIGINT', () => {});
22
+ for (const sig of ['SIGTERM', 'SIGHUP']) {
23
+ process.on(sig, () => child.kill(sig));
24
+ }
25
+
26
+ child.on('error', (err) => {
27
+ console.error(`rshare: couldn't start ${bin}: ${err.message}`);
28
+ process.exit(1);
29
+ });
30
+
31
+ child.on('exit', (code, signal) => {
32
+ if (signal) {
33
+ process.removeAllListeners(signal);
34
+ process.kill(process.pid, signal);
35
+ return;
36
+ }
37
+ process.exit(code === null ? 1 : code);
38
+ });
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/install.js ADDED
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ // Runs after `npm install` to check the binary for this machine is present
4
+ // and executable. It never fails the install: the launcher repeats these
5
+ // checks and explains any problem when rshare is actually run.
6
+
7
+ try {
8
+ require('./lib/binary').binaryPath();
9
+ } catch (err) {
10
+ console.warn(String((err && err.message) || err));
11
+ }
package/lib/binary.js ADDED
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ // Finds the prebuilt rshare binary for this machine. scripts/build.mjs puts
4
+ // one per platform under dist/<platform>-<arch>/ before the package is
5
+ // published.
6
+
7
+ const fs = require('fs');
8
+ const os = require('os');
9
+ const path = require('path');
10
+
11
+ const SUPPORTED = ['win32-x64', 'win32-arm64', 'darwin-x64', 'darwin-arm64', 'linux-x64', 'linux-arm64'];
12
+
13
+ function platformKey() {
14
+ return `${process.platform}-${process.arch}`;
15
+ }
16
+
17
+ function bundledPath() {
18
+ const exe = process.platform === 'win32' ? 'rshare.exe' : 'rshare';
19
+ return path.join(__dirname, '..', 'dist', platformKey(), exe);
20
+ }
21
+
22
+ // A package packed on Windows loses the executable bit on everything that
23
+ // isn't listed in "bin", so restore it. If the install folder is read-only
24
+ // (a global install made as root), run a private copy from the user's cache
25
+ // folder instead.
26
+ function ensureExecutable(file) {
27
+ if (process.platform === 'win32') return file;
28
+ try {
29
+ fs.accessSync(file, fs.constants.X_OK);
30
+ return file;
31
+ } catch {}
32
+ try {
33
+ fs.chmodSync(file, 0o755);
34
+ return file;
35
+ } catch {}
36
+ const { version } = require('../package.json');
37
+ const dir = path.join(os.homedir(), '.cache', 'rshare', version);
38
+ const copy = path.join(dir, 'rshare');
39
+ try {
40
+ if (fs.statSync(copy).size === fs.statSync(file).size) return copy;
41
+ } catch {}
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ fs.copyFileSync(file, copy);
44
+ fs.chmodSync(copy, 0o755);
45
+ return copy;
46
+ }
47
+
48
+ function binaryPath() {
49
+ if (process.env.RSHARE_BINARY) return process.env.RSHARE_BINARY;
50
+ const key = platformKey();
51
+ const file = bundledPath();
52
+ if (!fs.existsSync(file)) {
53
+ throw new Error(
54
+ SUPPORTED.includes(key)
55
+ ? `rshare: the program for ${key} is missing from this install (${file}).\nReinstall with: npm i -g rdrop`
56
+ : `rshare: ${key} isn't supported yet (supported: ${SUPPORTED.join(', ')}).`
57
+ );
58
+ }
59
+ return ensureExecutable(file);
60
+ }
61
+
62
+ module.exports = { binaryPath, platformKey, SUPPORTED };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "rdrop",
3
+ "version": "0.1.0",
4
+ "description": "Send files and folders between computers on the same network: fast, encrypted, one command.",
5
+ "keywords": [
6
+ "file-sharing",
7
+ "file-transfer",
8
+ "share",
9
+ "send",
10
+ "lan",
11
+ "p2p",
12
+ "ftp",
13
+ "airdrop",
14
+ "cli"
15
+ ],
16
+ "license": "MIT",
17
+ "bin": {
18
+ "rshare": "bin/rshare.js"
19
+ },
20
+ "files": [
21
+ "bin/",
22
+ "lib/",
23
+ "dist/",
24
+ "install.js"
25
+ ],
26
+ "scripts": {
27
+ "postinstall": "node install.js"
28
+ },
29
+ "engines": {
30
+ "node": ">=16"
31
+ },
32
+ "os": [
33
+ "win32",
34
+ "darwin",
35
+ "linux"
36
+ ],
37
+ "cpu": [
38
+ "x64",
39
+ "arm64"
40
+ ]
41
+ }