derphole 0.0.1
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 +29 -0
- package/README.md +325 -0
- package/bin/derphole.js +66 -0
- package/package.json +30 -0
- package/vendor/aarch64-apple-darwin/derphole/derphole +0 -0
- package/vendor/aarch64-unknown-linux-musl/derphole/derphole +0 -0
- package/vendor/x86_64-apple-darwin/derphole/derphole +0 -0
- package/vendor/x86_64-unknown-linux-musl/derphole/derphole +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shayne
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# derpcat
|
|
2
|
+
|
|
3
|
+
This repository ships two standalone CLIs:
|
|
4
|
+
|
|
5
|
+
- `derpcat` for raw byte streams and temporary local TCP service sharing
|
|
6
|
+
- `derphole` for wormhole-shaped text, file, directory, and SSH invite flows on the same transport stack
|
|
7
|
+
|
|
8
|
+
Both use the public Tailscale [DERP](#what-is-derp) relay network for rendezvous and relay fallback, but they are **not** affiliated with Tailscale, do **not** require a Tailscale account or tailnet, and do **not** use `tailscaled` for transport.
|
|
9
|
+
|
|
10
|
+
`derpcat` and `derphole` are **not** WireGuard overlays and **not** VPNs. Tailscale builds a general-purpose secure network on WireGuard. These tools are optimized for a different job: one session, one token, one transfer or shared service, on the shortest secure path they can find for that session. See [Transport Model](#transport-model), [Why It Is Fast](#why-it-is-fast), and [Security Model](#security-model).
|
|
11
|
+
|
|
12
|
+
For one-shot transfers and temporary service sharing, `derpcat` can beat sending the same traffic through a WireGuard-based overlay because it does not first build a general-purpose encrypted network path and then send application traffic through it. `derphole` uses the same session and transport machinery, but wraps it in a more human-oriented CLI. Both use DERP for rendezvous and fallback, then move the live session onto the best direct path they can establish for that workload. Details are in [Transport Model](#transport-model) and [How This Differs From Tailscale / WireGuard](#how-this-differs-from-tailscale--wireguard).
|
|
13
|
+
|
|
14
|
+
It does **not** require:
|
|
15
|
+
|
|
16
|
+
- a Tailscale account
|
|
17
|
+
- a tailnet
|
|
18
|
+
- `tailscaled`
|
|
19
|
+
- any separate control plane you have to run yourself
|
|
20
|
+
|
|
21
|
+
The token printed by `listen` or `share` carries session authorization. Public sessions still fetch the DERP map at runtime so both sides can find relay/bootstrap nodes. See [Security Model](#security-model) for what the token authorizes and what intermediaries can and cannot see.
|
|
22
|
+
|
|
23
|
+
## Pick the CLI
|
|
24
|
+
|
|
25
|
+
Use `derpcat` when you want transport primitives:
|
|
26
|
+
|
|
27
|
+
- one-shot byte-stream transfer with `listen` and `send`
|
|
28
|
+
- long-lived local service sharing with `share` and `open`
|
|
29
|
+
|
|
30
|
+
Use `derphole` when you want wormhole-shaped workflows:
|
|
31
|
+
|
|
32
|
+
- text transfer
|
|
33
|
+
- file transfer
|
|
34
|
+
- directory transfer
|
|
35
|
+
- SSH public key exchange for `authorized_keys`
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
`listen` receives bytes and prints a token. `send` pipes bytes into that token. `share` and `open` do the same thing for a local TCP service instead of a byte stream.
|
|
40
|
+
|
|
41
|
+
### Transfer a File
|
|
42
|
+
|
|
43
|
+
On the receiving machine:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx -y derpcat@latest listen > received.img
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`listen` prints a token to stderr. Copy that token to the sending machine.
|
|
50
|
+
|
|
51
|
+
On the sending machine:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cat ./disk.img | npx -y derpcat@latest send <token>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For a quick text example:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
printf 'hello\n' | npx -y derpcat@latest send <token>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Send a File with `derphole`
|
|
64
|
+
|
|
65
|
+
On the sending machine:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx -y derphole@latest send ./photo.jpg
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`send` prints a command for the receiving machine. Run that command there:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx -y derphole@latest receive <code>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Text uses the same shape:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx -y derphole@latest send hello
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Directories stream as tar on the wire and re-materialize on the receiver:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx -y derphole@latest send ./project-dir
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
For SSH access exchange, the host receiving access runs:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx -y derphole@latest ssh invite --user deploy
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The other side accepts with:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx -y derphole@latest ssh accept <token>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Watch Progress with `pv`
|
|
102
|
+
|
|
103
|
+
`derpcat` is plain stdin/stdout, so `pv` fits naturally in the pipe.
|
|
104
|
+
|
|
105
|
+
Install `pv` if needed:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
brew install pv
|
|
109
|
+
sudo apt install -y pv
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
On the receiving machine:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npx -y derpcat@latest listen | pv -brt > received.img
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
On the sending machine:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
cat ./disk.img | pv -brt | npx -y derpcat@latest send <token>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Want a concrete Internet/NAT version of the same idea? See [Real-World Example: Tar Pipe Over Internet](#real-world-example-tar-pipe-over-internet).
|
|
125
|
+
|
|
126
|
+
### Share a Local TCP Service
|
|
127
|
+
|
|
128
|
+
On the machine running the local web app or API:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npx -y derpcat@latest share 127.0.0.1:3000
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`share` prints a token to stderr. Copy that token to the machine that will open the shared service.
|
|
135
|
+
|
|
136
|
+
On another machine, expose that shared service locally:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npx -y derpcat@latest open <token>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`open` prints the local listening address to stderr.
|
|
143
|
+
|
|
144
|
+
Bind `open` to a specific local port if you want:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npx -y derpcat@latest open <token> 127.0.0.1:8080
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Useful Extras
|
|
151
|
+
|
|
152
|
+
Use the development channel for the latest commit published from `main`:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npx -y derpcat@dev version
|
|
156
|
+
npx -y derphole@dev version
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
By default, `listen`, `send`, `share`, and `open` keep transport status quiet. `listen` and `share` still print the token you need, and `open` still prints the local listening address. `derphole` keeps the same quiet default and only prints the user-facing instruction or token needed to complete the transfer. Use `--verbose` to see state transitions like `connected-relay` and `connected-direct`:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npx -y derpcat@latest --verbose listen
|
|
163
|
+
npx -y derphole@latest --verbose send ./photo.jpg
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Want transport details after the examples? Jump to [Transport Model](#transport-model), [Behavior](#behavior), or [Security Model](#security-model).
|
|
167
|
+
|
|
168
|
+
## Transport Model
|
|
169
|
+
|
|
170
|
+
High level:
|
|
171
|
+
|
|
172
|
+
1. `listen` or `share` creates an ephemeral session and prints an opaque bearer token.
|
|
173
|
+
2. That token contains the session ID, expiry, DERP bootstrap hints, the listener's public peer identity, a bearer secret, and the allowed session capability.
|
|
174
|
+
3. `send` or `open` uses the token to contact the listener through DERP and claim the session.
|
|
175
|
+
4. The listener validates the claim, checks the requested capability, and returns its current direct-path candidates.
|
|
176
|
+
5. Both sides start on the first working path immediately, including DERP relay if direct connectivity is not ready yet.
|
|
177
|
+
6. In parallel, both sides continue NAT traversal and direct-path probing. If a direct path succeeds, the live session upgrades in place without restarting the transfer.
|
|
178
|
+
|
|
179
|
+
### Data Plane Selection
|
|
180
|
+
|
|
181
|
+
DERP is used for **rendezvous** and **relay fallback**. If the term is new, see [What Is DERP?](#what-is-derp):
|
|
182
|
+
|
|
183
|
+
- rendezvous: exchange initial claim, decision, and direct-path coordination messages without a separate account-backed control plane
|
|
184
|
+
- relay fallback: keep the session working even when NAT traversal fails or a direct path is not ready yet
|
|
185
|
+
|
|
186
|
+
The data plane is selected per session:
|
|
187
|
+
|
|
188
|
+
- `share/open` uses multiplexed QUIC streams over `derpcat`'s relay/direct UDP transport, so one claimed session can carry many independent TCP connections to the shared service.
|
|
189
|
+
- `listen/send` uses a one-shot byte stream. By default, `derpcat` coordinates through DERP, promotes to a rate-adaptive direct UDP blast when traversal succeeds, and stays on encrypted relay fallback when no direct path is available.
|
|
190
|
+
|
|
191
|
+
Candidate discovery splits into two phases:
|
|
192
|
+
|
|
193
|
+
- fast local candidates first: immediately advertise local socket/interface candidates and any cached port mapping
|
|
194
|
+
- background traversal discovery: run STUN and UPnP / NAT-PMP / PCP refresh, then send updated candidates and `call-me-maybe` probes if a new direct endpoint appears
|
|
195
|
+
|
|
196
|
+
That keeps startup latency low while still allowing relay-to-direct promotion.
|
|
197
|
+
|
|
198
|
+
## How This Differs From Tailscale / WireGuard
|
|
199
|
+
|
|
200
|
+
Tailscale uses WireGuard to build a secure general-purpose network between peers. That is the right abstraction when you want durable machine-to-machine connectivity, stable private addressing, ACLs, subnet routing, exit nodes, and a long-lived encrypted overlay.
|
|
201
|
+
|
|
202
|
+
`derpcat` does something narrower and faster for its target workload. It creates session-scoped transport for a single transfer or a single shared service:
|
|
203
|
+
|
|
204
|
+
- no WireGuard tunnel device
|
|
205
|
+
- no overlay network interface
|
|
206
|
+
- no persistent mesh control plane
|
|
207
|
+
- no need to route arbitrary traffic through a general encrypted network
|
|
208
|
+
|
|
209
|
+
Instead, `derpcat` uses a bearer token to authorize exactly one session, uses DERP to get both peers talking immediately, and then promotes the session onto the best direct path it can establish for that workload. Supporting details are in [Transport Model](#transport-model) and [Security Model](#security-model).
|
|
210
|
+
|
|
211
|
+
For `send/listen` and `share/open`, that can beat routing the same traffic through a WireGuard-based overlay because `derpcat` is purpose-built for the active session, not for a general secure network abstraction. See [Why It Is Fast](#why-it-is-fast) for the concrete transport reasons.
|
|
212
|
+
|
|
213
|
+
## Why It Is Fast
|
|
214
|
+
|
|
215
|
+
`derpcat` gets its performance from the transport design:
|
|
216
|
+
|
|
217
|
+
- DERP is for rendezvous and relay fallback, not the preferred steady-state data plane.
|
|
218
|
+
- Sessions can start relayed immediately, then promote in place to direct without restarting the transfer.
|
|
219
|
+
- `listen/send` can scale from one to multiple direct UDP lanes, runs a short path-rate probe, then uses paced sending, adaptive rate control, and targeted replay/repair. That lets fast links run near their WAN ceiling without forcing slower links into the same send rate.
|
|
220
|
+
- Direct UDP payload packets are AEAD-protected with a per-session key derived from the bearer secret. The packet header stays visible for sequencing and repair, while user bytes stay encrypted and authenticated.
|
|
221
|
+
- `share/open` keeps QUIC stream multiplexing for service sharing, where many independent TCP streams need one claimed session.
|
|
222
|
+
- Candidate discovery is front-loaded with local interface candidates and cached mappings, then refined in the background with STUN and port mapping refresh. That keeps the first byte moving quickly instead of stalling the session until every traversal probe finishes.
|
|
223
|
+
|
|
224
|
+
In practice: get bytes moving early, keep them moving through relay if needed, then shift the live session onto a faster direct path as soon as direct connectivity is ready.
|
|
225
|
+
|
|
226
|
+
## Security Model
|
|
227
|
+
|
|
228
|
+
The session token is a **bearer capability**. Anyone who has the token can claim the session until it expires, so share it over a channel you trust. Tokens expire after one hour.
|
|
229
|
+
|
|
230
|
+
DERP relays do **not** get the secret material needed to read or impersonate the session:
|
|
231
|
+
|
|
232
|
+
- On the default `listen/send` direct UDP path, payload packets are encrypted and authenticated with session AEAD derived from the bearer secret in the token.
|
|
233
|
+
- On `share/open`, stream traffic is carried over authenticated QUIC streams for the claimed session.
|
|
234
|
+
- If packets are relayed through DERP, DERP only forwards encrypted session bytes.
|
|
235
|
+
|
|
236
|
+
Important security property: `derpcat` does not trade speed for plaintext shortcuts:
|
|
237
|
+
|
|
238
|
+
- the token authorizes the session, but does not turn DERP into a trusted decrypting proxy
|
|
239
|
+
- direct UDP data packets are encrypted and authenticated per session
|
|
240
|
+
- QUIC stream-mode peers are pinned to the expected public identity from the token
|
|
241
|
+
- DERP forwards encrypted traffic but does not have the keys required to decrypt or impersonate the session
|
|
242
|
+
|
|
243
|
+
Simple rule: possession of the token authorizes the session, but intermediaries that only see DERP traffic do not have the keys needed to decrypt it.
|
|
244
|
+
|
|
245
|
+
## Behavior
|
|
246
|
+
|
|
247
|
+
Sessions can start on DERP relay and later promote to a direct path without restarting. In default mode, the CLI keeps transport status quiet and only prints the token or local bind address needed to use the session. Use `--verbose` to inspect path changes, NAT traversal state, and direct-path tuning details.
|
|
248
|
+
|
|
249
|
+
## Use Cases
|
|
250
|
+
|
|
251
|
+
- easy cross-host transfer with no account setup
|
|
252
|
+
- useful behind NATs where direct connectivity may or may not work
|
|
253
|
+
- good for quick sharing of local web apps, APIs, and admin interfaces
|
|
254
|
+
- can be used entirely through `npx` without a manual install
|
|
255
|
+
|
|
256
|
+
## Real-World Example: Tar Pipe Over Internet
|
|
257
|
+
|
|
258
|
+
Classic tar pipe is fast because it streams bytes directly from `tar` on one host into `tar` on another host. Good reference: [Using netcat and tar to quickly transfer files between machines, aka tar pipe](https://toast.djw.org.uk/tarpipe.html).
|
|
259
|
+
|
|
260
|
+
Problem: classic `tar | nc` assumes receiver can expose a listening port and sender can reach it. That breaks down fast when both hosts are on the public Internet, both sit behind NAT, and neither side should expose an inbound port.
|
|
261
|
+
|
|
262
|
+
`derpcat` keeps the same streaming shape, but removes the open-port requirement.
|
|
263
|
+
|
|
264
|
+
Receiver:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
npx -y derpcat@latest listen | tar -xpf - -C /restore/path
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`listen` prints a token on stderr. Copy that token to the sender over a channel you trust.
|
|
271
|
+
|
|
272
|
+
Sender:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
tar -cpf - /srv/data | npx -y derpcat@latest send <token>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
This is still tar pipe. Difference: no public listener to expose, no SSH daemon required for data path, no VPN to join, and no permanent mesh to set up. `derpcat` starts with DERP if needed, then promotes the live transfer onto direct UDP when a faster direct path becomes available.
|
|
279
|
+
|
|
280
|
+
## Development
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
mise install
|
|
284
|
+
mise run install-githooks
|
|
285
|
+
mise run check
|
|
286
|
+
mise run build
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
`mise run build` writes both `dist/derpcat` and `dist/derphole`.
|
|
290
|
+
|
|
291
|
+
## Verification
|
|
292
|
+
|
|
293
|
+
Local smoke test:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
mise run smoke-local
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Remote smoke tests against a host you control:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
REMOTE_HOST=my-server.example.com mise run smoke-remote
|
|
303
|
+
REMOTE_HOST=my-server.example.com mise run smoke-remote-share
|
|
304
|
+
REMOTE_HOST=my-server.example.com mise run promotion-1g
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Releases
|
|
308
|
+
|
|
309
|
+
- npm packages: `derpcat`, `derphole`
|
|
310
|
+
- production channel: `@latest` on each package
|
|
311
|
+
- development channel: `@dev` on each package
|
|
312
|
+
- bootstrap runbook: [docs/releases/npm-bootstrap.md](docs/releases/npm-bootstrap.md)
|
|
313
|
+
|
|
314
|
+
## What Is DERP?
|
|
315
|
+
|
|
316
|
+
DERP stands for **Designated Encrypted Relay for Packets**. In plain terms, it is a globally reachable relay network that both peers can talk to even when they cannot yet talk directly to each other.
|
|
317
|
+
|
|
318
|
+
DERP was built by Tailscale for the Tailscale networking stack, and the public Tailscale-operated DERP network is reachable without running your own relays. The same DERP model is also used by Headscale, the open-source Tailscale control server implementation, which can serve its own DERP map and DERP servers.
|
|
319
|
+
|
|
320
|
+
In `derpcat`, DERP has two jobs:
|
|
321
|
+
|
|
322
|
+
- rendezvous: carry the initial claim, decision, and direct-path coordination messages so the two peers can find each other without a separate account-backed control plane
|
|
323
|
+
- fallback relay: carry encrypted session traffic when NAT traversal has not succeeded yet or when direct connectivity is unavailable
|
|
324
|
+
|
|
325
|
+
DERP is not the preferred steady-state path. It is the safety net that gets the session started and keeps it working. If a direct UDP path becomes available, `derpcat` promotes the live session onto that direct path. DERP only forwards bytes; it does not get the session keys needed to decrypt the traffic.
|
package/bin/derphole.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
const triples = new Map([
|
|
13
|
+
["linux:x64", "x86_64-unknown-linux-musl"],
|
|
14
|
+
["linux:arm64", "aarch64-unknown-linux-musl"],
|
|
15
|
+
["darwin:x64", "x86_64-apple-darwin"],
|
|
16
|
+
["darwin:arm64", "aarch64-apple-darwin"]
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const triple = triples.get(`${process.platform}:${process.arch}`);
|
|
20
|
+
if (!triple) {
|
|
21
|
+
console.error(`Unsupported platform: ${process.platform} (${process.arch})`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const binaryName = process.platform === "win32" ? "derphole.exe" : "derphole";
|
|
26
|
+
const binaryPath = path.join(__dirname, "..", "vendor", triple, "derphole", binaryName);
|
|
27
|
+
if (!existsSync(binaryPath)) {
|
|
28
|
+
console.error(`Missing vendored binary: ${binaryPath}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
33
|
+
stdio: "inherit",
|
|
34
|
+
env: { ...process.env, DERPHOLE_MANAGED_BY_NPM: "1" }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
child.on("error", (err) => {
|
|
38
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
39
|
+
console.error(`Failed to launch vendored binary: ${reason}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => {
|
|
44
|
+
process.on(sig, () => {
|
|
45
|
+
if (!child.killed) {
|
|
46
|
+
child.kill(sig);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const result = await new Promise((resolve) => {
|
|
52
|
+
child.on("exit", (code, signal) => {
|
|
53
|
+
if (signal) {
|
|
54
|
+
resolve({ signal });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
resolve({ code: code ?? 1 });
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (result.signal) {
|
|
62
|
+
const signalNumber = os.constants.signals[result.signal];
|
|
63
|
+
process.exit(typeof signalNumber === "number" ? 128 + signalNumber : 1);
|
|
64
|
+
} else {
|
|
65
|
+
process.exit(result.code);
|
|
66
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "derphole",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "BSD-3-Clause",
|
|
5
|
+
"bin": {
|
|
6
|
+
"derphole": "bin/derphole.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"os": [
|
|
10
|
+
"linux",
|
|
11
|
+
"darwin"
|
|
12
|
+
],
|
|
13
|
+
"cpu": [
|
|
14
|
+
"x64",
|
|
15
|
+
"arm64"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=16"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"bin",
|
|
22
|
+
"vendor",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/shayne/derpcat.git"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|