@iamken/cloudtunnel 0.10.3 → 0.10.5

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/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  <div align="center">
2
2
 
3
- # cloudtunnel
3
+ # cloudtunnel
4
4
 
5
5
  **Expose any local port at a public HTTPS subdomain — on _your own_ Cloudflare domains.**
6
6
 
7
- Instant, self-owned tunnel sharing — the tunnel and DNS live in **your** Cloudflare account, so you keep clean, stable URLs and full control.
7
+ The tunnel and DNS live in **your** Cloudflare account, so you keep clean, stable URLs and full control.
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/@iamken/cloudtunnel?color=cb3837&logo=npm&style=flat-square)](https://www.npmjs.com/package/@iamken/cloudtunnel)
10
10
  [![CI](https://img.shields.io/github/actions/workflow/status/thanhken/cloudtunnel/ci.yml?branch=main&label=CI&logo=github&style=flat-square)](https://github.com/thanhken/cloudtunnel/actions)
11
- [![license](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](#-license)
12
- [![node](https://img.shields.io/badge/node-%E2%89%A520-brightgreen?style=flat-square&logo=node.js)](#-requirements)
11
+ [![license](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](#license)
12
+ [![node](https://img.shields.io/badge/node-%E2%89%A520-brightgreen?style=flat-square&logo=node.js)](#requirements)
13
13
 
14
14
  </div>
15
15
 
@@ -17,25 +17,24 @@ Instant, self-owned tunnel sharing — the tunnel and DNS live in **your** Cloud
17
17
  npm i -g @iamken/cloudtunnel
18
18
 
19
19
  cloudtunnel login # once — paste a Cloudflare token; account + domain auto-resolved
20
- cloudtunnel 8080 # → https://brave-otter-1a2b.example.com is live
20
+ cloudtunnel 8080 # → https://brave-otter-1a2b.example.com is live
21
21
  ```
22
22
 
23
- > 💨 Prefer less typing? **`ctun`** is a built-in short alias — `ctun 8080`, `ctun ls`, `ctun delete 1`.
23
+ > Prefer less typing? **`ctun`** is a built-in short alias — `ctun 8080`, `ctun ls`, `ctun delete 1`.
24
24
 
25
25
  ---
26
26
 
27
- ## Why cloudtunnel
27
+ ## Why cloudtunnel
28
28
 
29
- - 🔗 **Your domains, real subdomains** — routes through native Cloudflare Tunnel to `*.your-domain.com`, not a shared third-party host.
30
- - **One command, one or many** — `cloudtunnel api:8080 web:5173` brings up several tunnels at once, each a live HTTPS URL.
31
- - 🎯 **Spec-driven** — a tunnel is just `[subdomain:]port[@host]`. No profiles to define, nothing to save.
32
- - 🧭 **Two states** — `up` brings subdomains online; `delete` (or Ctrl-C) releases them (removes the tunnel + DNS). Re-running `up` always starts clean.
33
- - 🌙 **Background & boot** — `--detach` keeps connectors running after you close the terminal; `--service` registers a native OS service (systemd · launchd · Task Scheduler) that survives restarts.
34
- - 🔒 **Secure by default** — token passed via env (never argv), stored `0600`, destructive ops are ownership-gated and re-verified.
29
+ - **Your domains, real subdomains** — native Cloudflare Tunnel to `*.your-domain.com`, not a shared third-party host.
30
+ - **One command, one or many** — `cloudtunnel api:8080 web:5173` brings up several tunnels at once, each a live HTTPS URL.
31
+ - **Spec-driven** — a tunnel is just `[subdomain:]port[@host]`. No profiles to define, nothing to save.
32
+ - **Two states** — `up` brings subdomains online; `delete` (or <kbd>Ctrl-C</kbd>) releases them (tunnel + DNS + any boot service). Re-running `up` always starts clean.
33
+ - **Secure by default** — token passed via env (never argv), stored `0600`, destructive ops ownership-gated and re-verified.
35
34
 
36
35
  ---
37
36
 
38
- ## 🚀 Quickstart
37
+ ## Quickstart
39
38
 
40
39
  ```bash
41
40
  cloudtunnel login # authenticate once
@@ -44,7 +43,7 @@ cloudtunnel api:8080 # api.<your-domain> → localhost:808
44
43
  cloudtunnel api:8080@192.168.1.20 # forward to another host/IP (IPv4/IPv6)
45
44
  cloudtunnel api:8080 web:5173 -d foo.io # several tunnels at once, under foo.io
46
45
  cloudtunnel api:8080 --detach # run in the background
47
- cloudtunnel api:8080 --service # register a boot service (Linux/macOS/Windows)
46
+ cloudtunnel api:8080 --service # register a boot service
48
47
  ```
49
48
 
50
49
  A **spec** is `[subdomain:]port[@host]`:
@@ -56,22 +55,22 @@ A **spec** is `[subdomain:]port[@host]`:
56
55
  | `api:8080@192.168.1.20` | `api.<domain>` → `192.168.1.20:8080` (a LAN device, container, another server) |
57
56
  | `@:8080` | the root/apex domain itself |
58
57
 
59
- Run `cloudtunnel` with no arguments and it guides you (port → subdomain → domain). A missing subdomain becomes a friendly random name; a missing host is `localhost`. The local-service protocol is `--proto http|https` (default `http`); replacing an existing DNS record asks first — pass `-y` to skip, `-f` to also replace a non-tunnel record.
58
+ Run `cloudtunnel` with no arguments and it guides you (port → subdomain → domain). A missing subdomain becomes a random name; a missing host is `localhost`. The local-service protocol is `--proto http|https` (default `http`); replacing an existing DNS record asks first — `-y` to skip, `-f` to also replace a non-tunnel record.
60
59
 
61
60
  ---
62
61
 
63
- ## 📦 Commands
62
+ ## Commands
64
63
 
65
64
  | Command | What it does |
66
65
  | --- | --- |
67
- | `cloudtunnel login` | Authenticate; resolve account + list your domains. `--status` to inspect. |
68
- | `cloudtunnel <spec…>` · `up` | Bring one or more tunnels online. `-d/--domain`, `--proto`, `--protocol`, `--detach`, `--service`, `-f/--force`, `-y/--yes`. |
69
- | `cloudtunnel ls` · `ps` | List tunnels — `# · URL · TARGET · PROTOCOL · STATE · SERVICE · PID`. `--all` scans the whole account and numbers unmanaged tunnels too. |
70
- | `cloudtunnel delete <target…>` | Release tunnel(s) — stop connector + delete tunnel + DNS + any boot service. `--all`, `--dry-run`, `-f`. |
71
- | `cloudtunnel logs <target>` | Show a connector's log. `-f` to follow, `-n` for line count. |
72
- | `cloudtunnel relay [sub]` | Expose a locked Cloudflare-API reverse proxy via a tunnel lets a client behind a CF-API block manage tunnels through this host. `-d`, `--detach`, `--service`. |
66
+ | `login` | Authenticate; resolve account + list your domains. `--status` to inspect. |
67
+ | `<spec…>` · `up` | Bring one or more tunnels online. `-d`, `--proto`, `--protocol`, `--detach`, `--service`, `-f`, `-y`. |
68
+ | `ls` · `ps` | List tunnels — `# · URL · TARGET · PROTOCOL · STATE · SERVICE · PID`. `--all` scans the whole account. |
69
+ | `delete <target…>` | Release tunnel(s) — connector + tunnel + DNS + boot service. `--all`, `--dry-run`, `-f`. |
70
+ | `logs <target>` | Show a connector's log. `-f` to follow, `-n` for line count. |
71
+ | `relay [sub]` | Expose a locked Cloudflare-API proxy — manage tunnels from behind a CF-API block (see below). |
73
72
 
74
- > A **`<target>`** is a `#` number, a subdomain name, a full hostname/URL, or a tunnel-id prefix. Tunnels **not tracked locally** (created elsewhere, or leaked by a failed run) work too: use the `#` a `ls --all` scan gave them, or their id — and pair with `-f` when they aren't cloudtunnel-managed.
73
+ A **`<target>`** is a `#` number, a subdomain, a full hostname/URL, or a tunnel-id prefix. `ls --all` also numbers tunnels created elsewhere (or leaked by a failed run); pair with `-f` when they aren't cloudtunnel-managed.
75
74
 
76
75
  ```
77
76
  $ cloudtunnel ls
@@ -88,161 +87,40 @@ $ cloudtunnel delete --all # release everything
88
87
 
89
88
  ---
90
89
 
91
- ## 🚄 Edge transport (`--protocol`)
90
+ ## More features
92
91
 
93
- cloudflared connects to Cloudflare over **QUIC** (UDP) by default — fastest, but
94
- some networks drop idle UDP sessions, which surfaces as intermittent **530/502**
95
- errors. Force **`http2`** (TCP) there:
92
+ Each is just a flag or subcommand away:
96
93
 
97
- ```bash
98
- cloudtunnel up 8080 --protocol http2
99
- ```
100
-
101
- Values: `auto` (default) · `http2` · `quic`. Works with `--detach` and `--service`
102
- alike, and each tunnel's transport shows in the `ls`/`ps` **PROTOCOL** column.
94
+ - **Edge transport** — `--protocol auto|http2|quic`. cloudflared uses QUIC (UDP) by default; force `http2` (TCP) on networks that drop idle UDP and cause intermittent 530/502. Shows in the `ls` PROTOCOL column, works with `--detach`/`--service`.
95
+ - **Run on boot** — `--service` registers a native OS service **per subdomain** so it comes back after a restart: systemd (Linux, sudo) · launchd (macOS) · Task Scheduler (Windows). The subdomain is baked in, so the URL stays stable; `delete` removes it. After install it waits (≤20s) for each connector and prints `ls`.
96
+ - **Background** — `--detach` keeps connectors running after the CLI exits. Release with `delete <target>`, tail with `logs <target> -f`.
97
+ - **Behind a proxy** — auto-routes `fetch` through `HTTPS_PROXY`/`HTTP_PROXY` (and, on Linux/GNOME, the system proxy). Set `HTTPS_PROXY` once if needed.
103
98
 
104
99
  ---
105
100
 
106
- ## 🛰️ Blocked Cloudflare API? Relay it (`relay`)
107
-
108
- Some networks block **`api.cloudflare.com`** (the management API cloudtunnel calls
109
- to create tunnels + DNS) while leaving the **data plane** (`*.your-domain.com` via
110
- the Cloudflare edge) reachable. Run a **relay** on a host that _can_ reach the API,
111
- then point the blocked client's API base at it — every `login`/`up`/`ls`/`delete`
112
- rides the data-plane tunnel instead of hitting the API directly.
113
-
114
- ```
115
- CLIENT (api.cloudflare.com blocked) RELAY (api.cloudflare.com OK)
116
- CLOUDTUNNEL_API_BASE=https://cfapi.you.com/client/v4
117
- │ data-plane tunnel — NOT blocked
118
-
119
- cfapi.you.com ─(CF edge)─► relay reverse-proxy ─► https://api.cloudflare.com
120
- ```
121
-
122
- **On the relay host** — one command starts a locked, secret-gated reverse proxy on
123
- loopback and exposes it through a normal tunnel:
124
-
125
- ```bash
126
- cloudtunnel relay cfapi -d you.com # → https://cfapi.you.com + prints a shared secret
127
- cloudtunnel relay cfapi -d you.com --detach # background
128
- cloudtunnel relay cfapi -d you.com --service # run on boot
129
- ```
130
-
131
- **On the blocked client** — one command. `cloudtunnel relay` prints a **relay key**
132
- (base + secret in one blob); paste it into `login` and everything is saved to config
133
- (`0600`) for good — no env vars to export, ever:
134
-
135
- ```bash
136
- cloudtunnel login --relay ctr_XXXXXXXX… # mint/paste the CF token when asked
137
- cloudtunnel ls # from now on: nothing to set
138
- cloudtunnel up web:8080 -d you.com # manages tunnels through the relay
139
- ```
140
-
141
- (Prefer env/flags? `CLOUDTUNNEL_API_BASE` + `CLOUDTUNNEL_RELAY_SECRET`, or
142
- `login --api-base <url> --relay-secret-stdin`, work too.) `cloudtunnel login --status`
143
- shows the active **Base** and whether a **Relay secret** is set (never the value).
144
-
145
- The proxy forwards to **`api.cloudflare.com` only** (no open-proxy / SSRF) and
146
- returns `403` to any request missing the shared secret.
147
-
148
- > 🔒 **Trust model.** The relay sees the client's Cloudflare **token in plaintext**
149
- > (it terminates the tunnel) — only relay through a host **you control**, and use a
150
- > least-privilege token (the four scopes under [Authentication](#-authentication)).
151
- > The token-mint page (`dash.cloudflare.com`) is usually blocked on the client too —
152
- > create the token on the relay/any unblocked host and paste it via `--token-stdin`.
153
-
154
- **Precedence** — API base: `CLOUDTUNNEL_API_BASE` env → `config.apiBase` → default
155
- `api.cloudflare.com`. Relay secret: `CLOUDTUNNEL_RELAY_SECRET` env → `config.relaySecret`.
156
-
157
- > ℹ️ The secret is printed only to an interactive terminal (never to a logfile or
158
- > journald). A **non-interactive** `relay --detach`/`--service` first run won't show
159
- > the auto-generated secret — set `CLOUDTUNNEL_RELAY_SECRET` yourself in scripts, or
160
- > read it back from `config.json`.
161
-
162
- **Debug / confirm traffic actually goes through the relay:**
163
-
164
- ```bash
165
- # CLIENT: trace every CF API call (method · URL · status · relay|direct) to stderr
166
- CLOUDTUNNEL_DEBUG=1 cloudtunnel ls
167
- # [cf] GET https://cfapi.you.com/client/v4/accounts?... -> 200 (relay)
168
- ```
169
-
170
- The **relay** process logs each request it forwards (method · path · status only —
171
- never headers): `[relay] 200 GET /client/v4/accounts`. A rejected call is tagged
172
- with the reason (`secret`, `ssrf`, `bad-form`, `upstream-error`). A relay rejection
173
- now surfaces as `Relay rejected the request (403): …` on the client instead of a
174
- misleading "token" error.
101
+ ## Relay: blocked Cloudflare API
175
102
 
176
- **Calling the relay directly** (curl / your own client)send the CF token as usual
177
- plus the relay secret header:
103
+ Some networks block `api.cloudflare.com` (used to create tunnels + DNS) but leave `*.your-domain.com` reachable. Run a **relay** on a host that _can_ reach the API, then point the blocked client at it every `login`/`up`/`ls`/`delete` rides a data-plane tunnel instead of hitting the API directly.
178
104
 
179
105
  ```bash
180
- curl https://cfapi.you.com/client/v4/accounts \
181
- -H "Authorization: Bearer $CF_TOKEN" \
182
- -H "X-CT-Relay-Secret: <secret>"
183
- ```
184
-
185
- The relay validates `X-CT-Relay-Secret`, strips it, and forwards everything else to
186
- `https://api.cloudflare.com/client/v4/accounts`. Missing/wrong header → `403`.
106
+ # On the relay host (API reachable):
107
+ cloudtunnel relay cfapi -d you.com # https://cfapi.you.com + prints a relay key
108
+ # add --detach / --service to run in background / on boot
187
109
 
188
- **Behind a proxy?** Node's `fetch` ignores proxies by default, so on a proxied
189
- network the client fails with `UND_ERR_CONNECT_TIMEOUT` even though curl/Postman
190
- work. cloudtunnel now routes through a proxy automatically from `HTTPS_PROXY`/
191
- `HTTP_PROXY` (and, on Linux/GNOME, the system proxy). Set it once if needed:
192
-
193
- ```bash
194
- export HTTPS_PROXY=http://your-proxy:port
195
- CLOUDTUNNEL_DEBUG=1 cloudtunnel ls # logs "[proxy] routing fetch through …"
110
+ # On the blocked client:
111
+ cloudtunnel login --relay ctr_XXXXXXXX… # paste the key + mint a CF token when asked
112
+ cloudtunnel up web:8080 -d you.com # now manages tunnels through the relay
196
113
  ```
197
114
 
198
- ---
199
-
200
- ## 🔁 Run on boot (`--service`)
201
-
202
- Add **`--service`** to register each subdomain as a native OS service so it comes
203
- back automatically — cross-platform:
115
+ The proxy forwards to `api.cloudflare.com` only (no open-proxy / SSRF) and returns `403` to any request missing the shared secret.
204
116
 
205
- | OS | Backend | Autostart | Privilege |
206
- | --- | --- | --- | --- |
207
- | Linux | systemd unit (`/etc/systemd/system`) | boot | sudo |
208
- | macOS | launchd LaunchAgent (`~/Library/LaunchAgents`) | login | none |
209
- | Windows | Task Scheduler (`cloudtunnel\<name>`) | logon | none |
210
-
211
- ```bash
212
- cloudtunnel api:8080 --service --protocol http2 # install + enable + start now
213
- cloudtunnel api:8080 web:5173 --service # one service per subdomain
214
- cloudtunnel ls # the SERVICE column shows each one's state
215
- cloudtunnel delete api # stops + removes the tunnel and its service
216
- ```
217
-
218
- Each subdomain gets its own service, so deleting one never touches the others.
219
- The concrete subdomain is baked in, so the URL stays stable across restarts. All
220
- backends restart the connector on failure.
221
-
222
- After installing, `--service` waits for each service to bring its connector up
223
- (up to 20s) and prints the `ls` view, so the tunnels show in `cloudtunnel ls`/`ps`
224
- right away. A service that doesn't report up in time is flagged with where to read
225
- its logs (e.g. it failed to start, or resolved a different config dir).
117
+ **Security:** the relay terminates the tunnel and sees the client's CF token in **plaintext** — only relay through a host **you control**, with a least-privilege token (the four scopes in [Authentication](#authentication)). Env/flag alternatives (`CLOUDTUNNEL_API_BASE`, `CLOUDTUNNEL_RELAY_SECRET`) and `CLOUDTUNNEL_DEBUG=1` request tracing are also supported.
226
118
 
227
119
  ---
228
120
 
229
- ## 🧭 Two states: up & delete
230
-
231
- There are only two states. **`up`** brings subdomains online (creating the tunnel
232
- + DNS). **`delete`** — or pressing <kbd>Ctrl-C</kbd> in a foreground `up` —
233
- **releases** them: it stops the connector and deletes the tunnel + DNS on
234
- Cloudflare (and any `--service` unit). Running `up` again recreates cleanly (any
235
- leftover tunnel record for that name is cleaned up first, so you never hit conflicts).
121
+ ## Authentication
236
122
 
237
- Add **`--detach`** to keep connectors running in the **background** after the CLI
238
- exits. Release them later with `cloudtunnel delete <target>` (or `--all`) and tail
239
- their output with `cloudtunnel logs <target> -f`.
240
-
241
- ---
242
-
243
- ## 🔑 Authentication
244
-
245
- Create a **Cloudflare API token** with these scopes (least-privilege — a token limited to the domains you tunnel into is recommended):
123
+ `cloudtunnel login` opens the Cloudflare API-tokens page and walks you through **Create Token → Create Custom Token**, then add these scopes (least-privilege — limit the token to the domains you tunnel into):
246
124
 
247
125
  | Resource | Permission |
248
126
  | --- | --- |
@@ -251,50 +129,28 @@ Create a **Cloudflare API token** with these scopes (least-privilege — a token
251
129
  | Zone | DNS · **Edit** |
252
130
  | Zone | Zone · **Read** |
253
131
 
254
- Provide it via (highest precedence first): `CLOUDFLARE_API_TOKEN` env → `cloudtunnel login --token-stdin` → the hidden prompt from `cloudtunnel login`.
255
-
256
- > 🔒 The token is **never** passed on the command line (that would leak into `ps` / shell history) and is stored `0600`. Rotate it if a shared host is compromised.
132
+ Provide the token via (highest precedence first): `CLOUDFLARE_API_TOKEN` env → `cloudtunnel login --token-stdin` → the hidden prompt from `cloudtunnel login`. The token is **never** passed on the command line and is stored `0600`.
257
133
 
258
134
  ---
259
135
 
260
- ## Requirements
136
+ ## Requirements
261
137
 
262
138
  - **Node.js ≥ 20**
263
- - **`cloudflared`** — auto-installed on first run (pinned version, checksum-verified, cached under `~/.config/cloudtunnel/bin`). No manual step needed. If `cloudflared` is already on your `PATH`, that copy is used instead.
264
- - _Alpine/musl, Windows-on-ARM edge cases, or other unsupported targets:_ install manually from the [releases page](https://github.com/cloudflare/cloudflared/releases) (or `brew install cloudflared`).
265
-
266
- ---
267
-
268
- ## 🧳 Upgrading from 0.3.x (profiles removed)
269
-
270
- `save`, `run`, `profiles`, `zones`, and the `service …` sub-commands are gone —
271
- everything is now inline specs on `up`, and reads live in `ls`:
272
-
273
- | Old | New |
274
- | --- | --- |
275
- | `run mb` | `cloudtunnel api:8080 web:5173` (pass the specs directly) |
276
- | `service enable mb --protocol http2` | `cloudtunnel <spec…> --service --protocol http2` |
277
- | `service disable mb` / `profiles --rm mb` | `cloudtunnel delete <target>` |
278
- | `service status` / `profiles` | `cloudtunnel ls` (SERVICE column) |
279
- | `zones` | `cloudtunnel login --status` (also a ZONE in each `ls` URL) |
280
- | `down <t>` / `rm <t>` | `cloudtunnel delete <t>` |
281
-
282
- Profiles that were registered as boot services are **migrated automatically** to
283
- the new per-subdomain units the first time you run cloudtunnel in a terminal.
139
+ - **`cloudflared`** — auto-installed on first run (pinned version, checksum-verified, cached under `~/.config/cloudtunnel/bin`). A copy already on `PATH` is used instead. On Alpine/musl, Windows-on-ARM, or other unsupported targets, install manually from the [releases page](https://github.com/cloudflare/cloudflared/releases) (or `brew install cloudflared`).
284
140
 
285
141
  ---
286
142
 
287
- ## 🩺 Troubleshooting
143
+ ## Troubleshooting
288
144
 
289
145
  | Symptom | Cause & fix |
290
146
  | --- | --- |
291
147
  | **HTTP 1016** / subdomain won't load | The connector isn't running (`STATE = down` in `ls`). Bring it back up: `cloudtunnel <spec>`. |
292
- | **`delete` says "active connections"** | Handled automatically — cloudtunnel cleans up the connections and retries the delete. |
293
- | **"grey-clouded" error** | The zone couldn't proxy the record; cfargotunnel routing needs an orange-cloud (proxied) CNAME. |
148
+ | **`delete` says "active connections"** | Handled automatically — cloudtunnel cleans up the connections and retries. |
149
+ | **"grey-clouded" error** | cfargotunnel routing needs an orange-cloud (proxied) CNAME. |
294
150
  | **A DNS record already occupies the name** | Pick another subdomain/domain, or pass `-f/--force` to replace a non-tunnel record. |
295
151
 
296
152
  ---
297
153
 
298
- ## 📄 License
154
+ ## License
299
155
 
300
156
  [MIT](LICENSE) © thanhken
package/dist/index.js CHANGED
@@ -706,7 +706,15 @@ async function acquireToken(opts) {
706
706
  hint: "pipe it: `printf %s $TOKEN | cloudtunnel login --token-stdin`"
707
707
  });
708
708
  }
709
- clack.note(REQUIRED_SCOPES.map((s) => `\u2022 ${s}`).join("\n"), "Create a token with these scopes");
709
+ clack.note(
710
+ [
711
+ "1. Click Create Token \u2192 Create Custom Token (Get started)",
712
+ "2. Under Permissions, add these four:",
713
+ ...REQUIRED_SCOPES.map((s) => ` \u2022 ${s}`),
714
+ "3. Continue to summary \u2192 Create Token \u2192 copy it, then paste below"
715
+ ].join("\n"),
716
+ "Create a Cloudflare API token"
717
+ );
710
718
  openBrowser(tokenCreateUrl());
711
719
  say.dim(`(opened ${tokenCreateUrl()})`);
712
720
  const token = await clack.password({ message: "Paste your Cloudflare API token", mask: "\u2022" });