@iamken/cloudtunnel 0.4.0 → 0.5.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/README.md +17 -11
- package/dist/{chunk-2TCFCMJS.js → chunk-CWJA4L7J.js} +2 -2
- package/dist/{chunk-YLTQB4F7.js → chunk-NZKGIDIW.js} +2 -2
- package/dist/{chunk-UPBVRXLF.js → chunk-RWR6VXNB.js} +7 -1
- package/dist/{chunk-UPBVRXLF.js.map → chunk-RWR6VXNB.js.map} +1 -1
- package/dist/{dns-PAPFSYFP.js → dns-5OXFAQ4D.js} +3 -3
- package/dist/index.js +315 -107
- package/dist/index.js.map +1 -1
- package/dist/zones-QYV3DSEY.js +11 -0
- package/package.json +1 -1
- package/dist/zones-YNGQYXAF.js +0 -11
- /package/dist/{chunk-2TCFCMJS.js.map → chunk-CWJA4L7J.js.map} +0 -0
- /package/dist/{chunk-YLTQB4F7.js.map → chunk-NZKGIDIW.js.map} +0 -0
- /package/dist/{dns-PAPFSYFP.js.map → dns-5OXFAQ4D.js.map} +0 -0
- /package/dist/{zones-YNGQYXAF.js.map → zones-QYV3DSEY.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
import {
|
|
3
3
|
listZones,
|
|
4
4
|
resolveZone
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CWJA4L7J.js";
|
|
6
6
|
import {
|
|
7
7
|
createCname,
|
|
8
8
|
deleteDnsRecord,
|
|
9
9
|
findCname,
|
|
10
10
|
isManagedDns
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-NZKGIDIW.js";
|
|
12
12
|
import {
|
|
13
13
|
CliError,
|
|
14
|
+
__export,
|
|
14
15
|
binDir,
|
|
15
16
|
cfPaginate,
|
|
16
17
|
cfRequest,
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
reportError,
|
|
25
26
|
resolveCf,
|
|
26
27
|
saveConfig
|
|
27
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-RWR6VXNB.js";
|
|
28
29
|
|
|
29
30
|
// src/index.ts
|
|
30
31
|
import { Command } from "commander";
|
|
@@ -32,7 +33,7 @@ import { createRequire } from "module";
|
|
|
32
33
|
import pc2 from "picocolors";
|
|
33
34
|
|
|
34
35
|
// src/config/legacy-migrate.ts
|
|
35
|
-
import { existsSync as
|
|
36
|
+
import { existsSync as existsSync3, readFileSync, renameSync, writeFileSync as writeFileSync4 } from "fs";
|
|
36
37
|
|
|
37
38
|
// src/ui/output.ts
|
|
38
39
|
import pc from "picocolors";
|
|
@@ -66,10 +67,10 @@ function printTable(head, rows) {
|
|
|
66
67
|
for (const row of rows) table.push(row);
|
|
67
68
|
console.log(table.toString());
|
|
68
69
|
}
|
|
69
|
-
async function selectOne(message, items,
|
|
70
|
+
async function selectOne(message, items, label4) {
|
|
70
71
|
const value = await select({
|
|
71
72
|
message,
|
|
72
|
-
options: items.map((item, i) => ({ value: String(i), label:
|
|
73
|
+
options: items.map((item, i) => ({ value: String(i), label: label4(item) }))
|
|
73
74
|
});
|
|
74
75
|
if (isCancel(value)) {
|
|
75
76
|
cancel("Cancelled.");
|
|
@@ -78,11 +79,10 @@ async function selectOne(message, items, label) {
|
|
|
78
79
|
return items[Number(value)];
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
// src/core/
|
|
82
|
-
import {
|
|
83
|
-
import
|
|
84
|
-
import
|
|
85
|
-
import { dirname, join } from "path";
|
|
82
|
+
// src/core/service-exec.ts
|
|
83
|
+
import { realpathSync } from "fs";
|
|
84
|
+
import os from "os";
|
|
85
|
+
import { join } from "path";
|
|
86
86
|
|
|
87
87
|
// src/core/ingress.ts
|
|
88
88
|
var HOSTNAME_RE = /^[a-zA-Z0-9.-]+$/;
|
|
@@ -157,29 +157,74 @@ function formatTunnelSpec(s) {
|
|
|
157
157
|
return `${s.subdomain}:${s.port}${s.host ? `@${s.host}` : ""}`;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
// src/core/
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
// src/core/service-exec.ts
|
|
161
|
+
var fqdnFor = (subdomain, zone) => subdomain === "@" ? zone : `${subdomain}.${zone}`;
|
|
162
|
+
var serviceSlug = (fqdn) => fqdn.replace(/[^a-zA-Z0-9]+/g, "-");
|
|
163
|
+
function buildUpArgs(p) {
|
|
164
|
+
const spec = formatTunnelSpec({ subdomain: p.subdomain, port: p.port, host: p.host });
|
|
165
|
+
return [
|
|
166
|
+
"up",
|
|
167
|
+
spec,
|
|
168
|
+
"-d",
|
|
169
|
+
p.zone,
|
|
170
|
+
...p.proto === "https" ? ["--proto", "https"] : [],
|
|
171
|
+
...p.protocol ? ["--protocol", p.protocol] : [],
|
|
172
|
+
"-f",
|
|
173
|
+
"-y"
|
|
174
|
+
];
|
|
163
175
|
}
|
|
164
|
-
function
|
|
165
|
-
|
|
176
|
+
function entryScript() {
|
|
177
|
+
const p = process.argv[1];
|
|
178
|
+
if (!p) throw new CliError("Cannot resolve the cloudtunnel executable path.");
|
|
179
|
+
return realpathSync(p);
|
|
166
180
|
}
|
|
167
|
-
function
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
181
|
+
function describeService(p) {
|
|
182
|
+
const fqdn = fqdnFor(p.subdomain, p.zone);
|
|
183
|
+
const slug = serviceSlug(fqdn);
|
|
184
|
+
return {
|
|
185
|
+
fqdn,
|
|
186
|
+
slug,
|
|
187
|
+
argv: buildUpArgs(p),
|
|
188
|
+
nodePath: process.execPath,
|
|
189
|
+
scriptPath: entryScript(),
|
|
190
|
+
user: os.userInfo().username,
|
|
191
|
+
home: os.homedir(),
|
|
192
|
+
logFile: join(logDir, `${slug}.service.log`)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/core/service-systemd.ts
|
|
197
|
+
var service_systemd_exports = {};
|
|
198
|
+
__export(service_systemd_exports, {
|
|
199
|
+
assertSupported: () => assertSupported,
|
|
200
|
+
buildUnit: () => buildUnit,
|
|
201
|
+
install: () => install,
|
|
202
|
+
label: () => label,
|
|
203
|
+
legacyUnitExists: () => legacyUnitExists,
|
|
204
|
+
removeLegacyUnit: () => removeLegacyUnit,
|
|
205
|
+
state: () => state,
|
|
206
|
+
uninstall: () => uninstall
|
|
207
|
+
});
|
|
208
|
+
import { execFileSync } from "child_process";
|
|
209
|
+
import { existsSync, writeFileSync } from "fs";
|
|
210
|
+
import { tmpdir } from "os";
|
|
211
|
+
import { dirname, join as join2 } from "path";
|
|
212
|
+
var label = (fqdn) => `cloudtunnel-${serviceSlug(fqdn)}.service`;
|
|
213
|
+
var unitPath = (fqdn) => `/etc/systemd/system/${label(fqdn)}`;
|
|
214
|
+
function buildUnit(d) {
|
|
215
|
+
const nodeBin = dirname(d.nodePath);
|
|
171
216
|
return [
|
|
172
217
|
"[Unit]",
|
|
173
|
-
`Description=cloudtunnel ${
|
|
218
|
+
`Description=cloudtunnel ${d.fqdn} (Cloudflare Tunnel)`,
|
|
174
219
|
"After=network-online.target",
|
|
175
220
|
"Wants=network-online.target",
|
|
176
221
|
"",
|
|
177
222
|
"[Service]",
|
|
178
223
|
"Type=simple",
|
|
179
|
-
`User=${
|
|
180
|
-
`Environment=HOME=${
|
|
224
|
+
`User=${d.user}`,
|
|
225
|
+
`Environment=HOME=${d.home}`,
|
|
181
226
|
`Environment=PATH=${nodeBin}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`,
|
|
182
|
-
`ExecStart=${
|
|
227
|
+
`ExecStart=${d.nodePath} ${d.scriptPath} ${d.argv.join(" ")}`,
|
|
183
228
|
"Restart=on-failure",
|
|
184
229
|
"RestartSec=5",
|
|
185
230
|
"",
|
|
@@ -188,18 +233,6 @@ function buildUnit(p) {
|
|
|
188
233
|
""
|
|
189
234
|
].join("\n");
|
|
190
235
|
}
|
|
191
|
-
function assertSystemd() {
|
|
192
|
-
if (process.platform !== "linux") {
|
|
193
|
-
throw new CliError("Service registration is Linux/systemd only.", {
|
|
194
|
-
hint: "on macOS/Windows run `cloudtunnel up <spec> --detach` at login instead"
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
try {
|
|
198
|
-
execFileSync("systemctl", ["--version"], { stdio: "ignore" });
|
|
199
|
-
} catch {
|
|
200
|
-
throw new CliError("systemd (systemctl) was not found on this host.");
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
236
|
function privileged(args) {
|
|
204
237
|
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
205
238
|
const argv = isRoot ? args : ["sudo", ...args];
|
|
@@ -213,40 +246,37 @@ function query(args) {
|
|
|
213
246
|
return out ? out.toString().trim() : "";
|
|
214
247
|
}
|
|
215
248
|
}
|
|
216
|
-
function
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
249
|
+
function assertSupported() {
|
|
250
|
+
try {
|
|
251
|
+
execFileSync("systemctl", ["--version"], { stdio: "ignore" });
|
|
252
|
+
} catch {
|
|
253
|
+
throw new CliError("systemd (systemctl) was not found on this host.");
|
|
254
|
+
}
|
|
220
255
|
}
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
spec: formatTunnelSpec({ subdomain: params.subdomain, port: params.port, host: params.host }),
|
|
227
|
-
zone: params.zone,
|
|
228
|
-
proto: params.proto,
|
|
229
|
-
user: os.userInfo().username,
|
|
230
|
-
home: os.homedir(),
|
|
231
|
-
nodePath: process.execPath,
|
|
232
|
-
scriptPath: entryScript(),
|
|
233
|
-
protocol: params.protocol
|
|
234
|
-
});
|
|
235
|
-
const tmp = join(tmpdir(), serviceName(fqdn));
|
|
236
|
-
writeFileSync(tmp, unit, { mode: 420 });
|
|
237
|
-
privileged(["install", "-m", "0644", tmp, unitPath(fqdn)]);
|
|
256
|
+
function install(d) {
|
|
257
|
+
assertSupported();
|
|
258
|
+
const tmp = join2(tmpdir(), label(d.fqdn));
|
|
259
|
+
writeFileSync(tmp, buildUnit(d), { mode: 420 });
|
|
260
|
+
privileged(["install", "-m", "0644", tmp, unitPath(d.fqdn)]);
|
|
238
261
|
privileged(["systemctl", "daemon-reload"]);
|
|
239
|
-
privileged(["systemctl", "enable", "--now",
|
|
262
|
+
privileged(["systemctl", "enable", "--now", label(d.fqdn)]);
|
|
240
263
|
}
|
|
241
|
-
function
|
|
242
|
-
assertSystemd();
|
|
264
|
+
function uninstall(fqdn) {
|
|
243
265
|
try {
|
|
244
|
-
privileged(["systemctl", "disable", "--now",
|
|
266
|
+
privileged(["systemctl", "disable", "--now", label(fqdn)]);
|
|
245
267
|
} catch {
|
|
246
268
|
}
|
|
247
269
|
privileged(["rm", "-f", unitPath(fqdn)]);
|
|
248
270
|
privileged(["systemctl", "daemon-reload"]);
|
|
249
271
|
}
|
|
272
|
+
function state(fqdn) {
|
|
273
|
+
const name = label(fqdn);
|
|
274
|
+
if (query(["is-active", name]) === "active") return "active";
|
|
275
|
+
const enabled = query(["is-enabled", name]);
|
|
276
|
+
if (enabled === "enabled" || enabled === "enabled-runtime") return "enabled";
|
|
277
|
+
if (enabled === "disabled" || enabled === "static") return "disabled";
|
|
278
|
+
return "none";
|
|
279
|
+
}
|
|
250
280
|
function legacyUnitExists(profile) {
|
|
251
281
|
return existsSync(`/etc/systemd/system/cloudtunnel-${profile}.service`);
|
|
252
282
|
}
|
|
@@ -259,27 +289,207 @@ function removeLegacyUnit(profile) {
|
|
|
259
289
|
privileged(["rm", "-f", `/etc/systemd/system/${name}`]);
|
|
260
290
|
privileged(["systemctl", "daemon-reload"]);
|
|
261
291
|
}
|
|
292
|
+
|
|
293
|
+
// src/core/service-launchd.ts
|
|
294
|
+
var service_launchd_exports = {};
|
|
295
|
+
__export(service_launchd_exports, {
|
|
296
|
+
assertSupported: () => assertSupported2,
|
|
297
|
+
buildPlist: () => buildPlist,
|
|
298
|
+
install: () => install2,
|
|
299
|
+
label: () => label2,
|
|
300
|
+
state: () => state2,
|
|
301
|
+
uninstall: () => uninstall2
|
|
302
|
+
});
|
|
303
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
304
|
+
import { existsSync as existsSync2, mkdirSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
305
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
306
|
+
import os2 from "os";
|
|
307
|
+
var label2 = (fqdn) => `com.cloudtunnel.${serviceSlug(fqdn)}`;
|
|
308
|
+
var agentsDir = () => join3(os2.homedir(), "Library", "LaunchAgents");
|
|
309
|
+
var plistPath = (fqdn) => join3(agentsDir(), `${label2(fqdn)}.plist`);
|
|
310
|
+
var xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
311
|
+
function buildPlist(d) {
|
|
312
|
+
const args = [d.nodePath, d.scriptPath, ...d.argv].map((a) => ` <string>${xml(a)}</string>`).join("\n");
|
|
313
|
+
const nodeBin = dirname2(d.nodePath);
|
|
314
|
+
const path = `${nodeBin}:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin`;
|
|
315
|
+
return [
|
|
316
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
317
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
318
|
+
'<plist version="1.0">',
|
|
319
|
+
"<dict>",
|
|
320
|
+
` <key>Label</key><string>${xml(label2(d.fqdn))}</string>`,
|
|
321
|
+
" <key>ProgramArguments</key>",
|
|
322
|
+
" <array>",
|
|
323
|
+
args,
|
|
324
|
+
" </array>",
|
|
325
|
+
" <key>RunAtLoad</key><true/>",
|
|
326
|
+
" <key>KeepAlive</key><true/>",
|
|
327
|
+
" <key>EnvironmentVariables</key>",
|
|
328
|
+
" <dict>",
|
|
329
|
+
` <key>PATH</key><string>${xml(path)}</string>`,
|
|
330
|
+
` <key>HOME</key><string>${xml(d.home)}</string>`,
|
|
331
|
+
" </dict>",
|
|
332
|
+
` <key>StandardOutPath</key><string>${xml(d.logFile)}</string>`,
|
|
333
|
+
` <key>StandardErrorPath</key><string>${xml(d.logFile)}</string>`,
|
|
334
|
+
"</dict>",
|
|
335
|
+
"</plist>",
|
|
336
|
+
""
|
|
337
|
+
].join("\n");
|
|
338
|
+
}
|
|
339
|
+
function launchctl(args) {
|
|
340
|
+
try {
|
|
341
|
+
return execFileSync2("launchctl", args, { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" });
|
|
342
|
+
} catch (err) {
|
|
343
|
+
const out = err.stdout;
|
|
344
|
+
return out ? out.toString() : "";
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function assertSupported2() {
|
|
348
|
+
}
|
|
349
|
+
function install2(d) {
|
|
350
|
+
ensureDirs();
|
|
351
|
+
mkdirSync(agentsDir(), { recursive: true });
|
|
352
|
+
const plist = plistPath(d.fqdn);
|
|
353
|
+
writeFileSync2(plist, buildPlist(d), { mode: 420 });
|
|
354
|
+
launchctl(["unload", "-w", plist]);
|
|
355
|
+
execFileSync2("launchctl", ["load", "-w", plist], { stdio: "inherit" });
|
|
356
|
+
}
|
|
357
|
+
function uninstall2(fqdn) {
|
|
358
|
+
const plist = plistPath(fqdn);
|
|
359
|
+
launchctl(["unload", "-w", plist]);
|
|
360
|
+
rmSync(plist, { force: true });
|
|
361
|
+
}
|
|
362
|
+
function state2(fqdn) {
|
|
363
|
+
const info = launchctl(["list", label2(fqdn)]);
|
|
364
|
+
if (/"PID"\s*=/.test(info)) return "active";
|
|
365
|
+
return existsSync2(plistPath(fqdn)) ? "enabled" : "none";
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/core/service-windows.ts
|
|
369
|
+
var service_windows_exports = {};
|
|
370
|
+
__export(service_windows_exports, {
|
|
371
|
+
assertSupported: () => assertSupported3,
|
|
372
|
+
buildTaskXml: () => buildTaskXml,
|
|
373
|
+
install: () => install3,
|
|
374
|
+
label: () => label3,
|
|
375
|
+
state: () => state3,
|
|
376
|
+
uninstall: () => uninstall3
|
|
377
|
+
});
|
|
378
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
379
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
380
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
381
|
+
import { join as join4 } from "path";
|
|
382
|
+
var label3 = (fqdn) => `cloudtunnel\\${serviceSlug(fqdn)}`;
|
|
383
|
+
var xml2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
384
|
+
function buildTaskXml(d) {
|
|
385
|
+
const args = `"${d.scriptPath}" ${d.argv.join(" ")}`;
|
|
386
|
+
return [
|
|
387
|
+
'<?xml version="1.0" encoding="UTF-16"?>',
|
|
388
|
+
'<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">',
|
|
389
|
+
` <RegistrationInfo><Description>cloudtunnel ${xml2(d.fqdn)} (Cloudflare Tunnel)</Description></RegistrationInfo>`,
|
|
390
|
+
` <Triggers><LogonTrigger><Enabled>true</Enabled><UserId>${xml2(d.user)}</UserId></LogonTrigger></Triggers>`,
|
|
391
|
+
` <Principals><Principal id="Author"><UserId>${xml2(d.user)}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>`,
|
|
392
|
+
" <Settings>",
|
|
393
|
+
" <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>",
|
|
394
|
+
" <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>",
|
|
395
|
+
" <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>",
|
|
396
|
+
" <StartWhenAvailable>true</StartWhenAvailable>",
|
|
397
|
+
" <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>",
|
|
398
|
+
" <RestartOnFailure><Interval>PT1M</Interval><Count>3</Count></RestartOnFailure>",
|
|
399
|
+
" <Enabled>true</Enabled>",
|
|
400
|
+
" </Settings>",
|
|
401
|
+
' <Actions Context="Author">',
|
|
402
|
+
` <Exec><Command>${xml2(d.nodePath)}</Command><Arguments>${xml2(args)}</Arguments></Exec>`,
|
|
403
|
+
" </Actions>",
|
|
404
|
+
"</Task>",
|
|
405
|
+
""
|
|
406
|
+
].join("\r\n");
|
|
407
|
+
}
|
|
408
|
+
function schtasks(args) {
|
|
409
|
+
try {
|
|
410
|
+
return execFileSync3("schtasks", args, { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" });
|
|
411
|
+
} catch (err) {
|
|
412
|
+
const out = err.stdout;
|
|
413
|
+
return out ? out.toString() : "";
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function assertSupported3() {
|
|
417
|
+
}
|
|
418
|
+
function install3(d) {
|
|
419
|
+
const file = join4(tmpdir2(), `${d.slug}.task.xml`);
|
|
420
|
+
writeFileSync3(file, "\uFEFF" + buildTaskXml(d), { encoding: "utf16le" });
|
|
421
|
+
execFileSync3("schtasks", ["/Create", "/TN", label3(d.fqdn), "/XML", file, "/F"], { stdio: "inherit" });
|
|
422
|
+
schtasks(["/Run", "/TN", label3(d.fqdn)]);
|
|
423
|
+
}
|
|
424
|
+
function uninstall3(fqdn) {
|
|
425
|
+
schtasks(["/Delete", "/TN", label3(fqdn), "/F"]);
|
|
426
|
+
}
|
|
427
|
+
function state3(fqdn) {
|
|
428
|
+
const out = schtasks(["/Query", "/TN", label3(fqdn), "/FO", "LIST"]);
|
|
429
|
+
if (!out) return "none";
|
|
430
|
+
if (/\bRunning\b/.test(out)) return "active";
|
|
431
|
+
if (/\bDisabled\b/.test(out)) return "disabled";
|
|
432
|
+
if (/\bReady\b/.test(out)) return "enabled";
|
|
433
|
+
return "enabled";
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/core/service.ts
|
|
437
|
+
function pick() {
|
|
438
|
+
switch (process.platform) {
|
|
439
|
+
case "linux":
|
|
440
|
+
return service_systemd_exports;
|
|
441
|
+
case "darwin":
|
|
442
|
+
return service_launchd_exports;
|
|
443
|
+
case "win32":
|
|
444
|
+
return service_windows_exports;
|
|
445
|
+
default:
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function required() {
|
|
450
|
+
const b = pick();
|
|
451
|
+
if (!b) {
|
|
452
|
+
throw new CliError(`Boot services aren't supported on ${process.platform}.`, {
|
|
453
|
+
hint: "run `cloudtunnel up <spec> --detach` and use your OS's own autostart"
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return b;
|
|
457
|
+
}
|
|
458
|
+
function assertServiceSupported() {
|
|
459
|
+
required().assertSupported();
|
|
460
|
+
}
|
|
461
|
+
function serviceName(fqdn) {
|
|
462
|
+
return pick()?.label(fqdn) ?? `cloudtunnel-${fqdn}`;
|
|
463
|
+
}
|
|
464
|
+
function installServiceForSpec(params) {
|
|
465
|
+
const b = required();
|
|
466
|
+
b.assertSupported();
|
|
467
|
+
b.install(describeService(params));
|
|
468
|
+
}
|
|
469
|
+
function uninstallService(fqdn) {
|
|
470
|
+
pick()?.uninstall(fqdn);
|
|
471
|
+
}
|
|
262
472
|
function serviceState(fqdn) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
473
|
+
return pick()?.state(fqdn) ?? "none";
|
|
474
|
+
}
|
|
475
|
+
function legacyUnitExists2(profile) {
|
|
476
|
+
return process.platform === "linux" ? legacyUnitExists(profile) : false;
|
|
477
|
+
}
|
|
478
|
+
function removeLegacyUnit2(profile) {
|
|
479
|
+
if (process.platform === "linux") removeLegacyUnit(profile);
|
|
270
480
|
}
|
|
271
481
|
|
|
272
482
|
// src/config/legacy-migrate.ts
|
|
273
483
|
var skipMarker = `${profilesFile}.migrate-skip`;
|
|
274
484
|
async function migrateLegacyProfiles() {
|
|
275
|
-
if (!
|
|
485
|
+
if (!existsSync3(profilesFile) || existsSync3(skipMarker)) return;
|
|
276
486
|
let profiles;
|
|
277
487
|
try {
|
|
278
488
|
profiles = JSON.parse(readFileSync(profilesFile, "utf8"));
|
|
279
489
|
} catch {
|
|
280
490
|
return;
|
|
281
491
|
}
|
|
282
|
-
const legacy = Object.entries(profiles).filter(([name]) =>
|
|
492
|
+
const legacy = Object.entries(profiles).filter(([name]) => legacyUnitExists2(name));
|
|
283
493
|
if (legacy.length === 0) {
|
|
284
494
|
try {
|
|
285
495
|
renameSync(profilesFile, `${profilesFile}.migrated`);
|
|
@@ -289,7 +499,7 @@ async function migrateLegacyProfiles() {
|
|
|
289
499
|
}
|
|
290
500
|
const ok = await confirm(`Found ${legacy.length} boot service(s) from an older cloudtunnel. Migrate them now? (needs sudo)`);
|
|
291
501
|
if (!ok) {
|
|
292
|
-
|
|
502
|
+
writeFileSync4(skipMarker, "");
|
|
293
503
|
say.dim(` Skipped. Delete ${skipMarker} to be asked again.`);
|
|
294
504
|
return;
|
|
295
505
|
}
|
|
@@ -309,12 +519,12 @@ async function migrateLegacyProfiles() {
|
|
|
309
519
|
});
|
|
310
520
|
migrated++;
|
|
311
521
|
}
|
|
312
|
-
|
|
522
|
+
removeLegacyUnit2(name);
|
|
313
523
|
}
|
|
314
524
|
renameSync(profilesFile, `${profilesFile}.migrated`);
|
|
315
525
|
say.ok(`Migrated ${migrated} boot service(s). See them with: cloudtunnel ls`);
|
|
316
526
|
} catch (err) {
|
|
317
|
-
|
|
527
|
+
writeFileSync4(skipMarker, "");
|
|
318
528
|
say.warn(`Migration incomplete: ${err.message}. Won't retry automatically (delete ${skipMarker} to retry).`);
|
|
319
529
|
}
|
|
320
530
|
}
|
|
@@ -478,10 +688,10 @@ async function ensureAuth() {
|
|
|
478
688
|
}
|
|
479
689
|
|
|
480
690
|
// src/connector/binary.ts
|
|
481
|
-
import { execFileSync as
|
|
691
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
482
692
|
import { createHash } from "crypto";
|
|
483
|
-
import { chmodSync, existsSync as
|
|
484
|
-
import { join as
|
|
693
|
+
import { chmodSync, existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
694
|
+
import { join as join5 } from "path";
|
|
485
695
|
var PINNED_VERSION = "2025.1.0";
|
|
486
696
|
var RELEASE_BASE = `https://github.com/cloudflare/cloudflared/releases/download/${PINNED_VERSION}`;
|
|
487
697
|
var ASSETS = {
|
|
@@ -493,14 +703,14 @@ var ASSETS = {
|
|
|
493
703
|
};
|
|
494
704
|
function binaryWorks(bin) {
|
|
495
705
|
try {
|
|
496
|
-
|
|
706
|
+
execFileSync4(bin, ["--version"], { stdio: "ignore" });
|
|
497
707
|
return true;
|
|
498
708
|
} catch {
|
|
499
709
|
return false;
|
|
500
710
|
}
|
|
501
711
|
}
|
|
502
712
|
function cachedPath() {
|
|
503
|
-
return
|
|
713
|
+
return join5(binDir, process.platform === "win32" ? "cloudflared.exe" : "cloudflared");
|
|
504
714
|
}
|
|
505
715
|
function isMusl() {
|
|
506
716
|
try {
|
|
@@ -512,7 +722,7 @@ function isMusl() {
|
|
|
512
722
|
async function ensureCloudflared() {
|
|
513
723
|
if (binaryWorks("cloudflared")) return "cloudflared";
|
|
514
724
|
const cached = cachedPath();
|
|
515
|
-
if (
|
|
725
|
+
if (existsSync4(cached) && binaryWorks(cached)) return cached;
|
|
516
726
|
return downloadCloudflared(cached);
|
|
517
727
|
}
|
|
518
728
|
async function downloadCloudflared(dest) {
|
|
@@ -540,7 +750,7 @@ async function downloadCloudflared(dest) {
|
|
|
540
750
|
}
|
|
541
751
|
ensureDirs();
|
|
542
752
|
const binary = asset.archive ? extractTgz(bytes) : bytes;
|
|
543
|
-
|
|
753
|
+
writeFileSync5(dest, binary, { mode: 493 });
|
|
544
754
|
chmodSync(dest, 493);
|
|
545
755
|
if (!binaryWorks(dest)) throw new CliError("Downloaded cloudflared is not runnable.");
|
|
546
756
|
return dest;
|
|
@@ -552,17 +762,17 @@ function extractTgz(_bytes) {
|
|
|
552
762
|
}
|
|
553
763
|
|
|
554
764
|
// src/core/up-runner.ts
|
|
555
|
-
import { join as
|
|
765
|
+
import { join as join6 } from "path";
|
|
556
766
|
import * as clack2 from "@clack/prompts";
|
|
557
767
|
|
|
558
768
|
// src/connector/process.ts
|
|
559
|
-
import { execFileSync as
|
|
769
|
+
import { execFileSync as execFileSync5, spawn as spawn2 } from "child_process";
|
|
560
770
|
import { openSync } from "fs";
|
|
561
771
|
|
|
562
772
|
// src/connector/registry.ts
|
|
563
|
-
import { existsSync as
|
|
773
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
564
774
|
import { readFile } from "fs/promises";
|
|
565
|
-
import
|
|
775
|
+
import os3 from "os";
|
|
566
776
|
import lockfile from "proper-lockfile";
|
|
567
777
|
function entryFqdn(e) {
|
|
568
778
|
return e.subdomain === "@" ? e.zone : `${e.subdomain}.${e.zone}`;
|
|
@@ -571,8 +781,8 @@ function currentBootId() {
|
|
|
571
781
|
try {
|
|
572
782
|
return readFileSync3("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
573
783
|
} catch {
|
|
574
|
-
const bootMinute = Math.floor((Date.now() -
|
|
575
|
-
return `boot-${bootMinute}-${
|
|
784
|
+
const bootMinute = Math.floor((Date.now() - os3.uptime() * 1e3) / 6e4);
|
|
785
|
+
return `boot-${bootMinute}-${os3.hostname()}`;
|
|
576
786
|
}
|
|
577
787
|
}
|
|
578
788
|
function readRegistry() {
|
|
@@ -585,12 +795,12 @@ function readRegistry() {
|
|
|
585
795
|
function writeRegistry(reg) {
|
|
586
796
|
ensureDirs();
|
|
587
797
|
const tmp = `${registryFile}.tmp`;
|
|
588
|
-
|
|
798
|
+
writeFileSync6(tmp, JSON.stringify(reg, null, 2), { mode: 384 });
|
|
589
799
|
renameSync2(tmp, registryFile);
|
|
590
800
|
}
|
|
591
801
|
async function mutateRegistry(fn) {
|
|
592
802
|
ensureDirs();
|
|
593
|
-
if (!
|
|
803
|
+
if (!existsSync5(registryFile)) writeFileSync6(registryFile, "{}", { mode: 384 });
|
|
594
804
|
const release = await lockfile.lock(registryFile, { retries: { retries: 10, minTimeout: 50 } });
|
|
595
805
|
try {
|
|
596
806
|
const reg = readRegistry();
|
|
@@ -698,7 +908,7 @@ async function stopConnector(entry) {
|
|
|
698
908
|
const pid = entry.pid;
|
|
699
909
|
if (process.platform === "win32") {
|
|
700
910
|
try {
|
|
701
|
-
|
|
911
|
+
execFileSync5("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
|
|
702
912
|
} catch {
|
|
703
913
|
return false;
|
|
704
914
|
}
|
|
@@ -829,10 +1039,10 @@ var NOUNS = [
|
|
|
829
1039
|
"tiger",
|
|
830
1040
|
"walnut"
|
|
831
1041
|
];
|
|
832
|
-
var
|
|
1042
|
+
var pick2 = (arr) => arr[randomInt(arr.length)];
|
|
833
1043
|
function randomSlug() {
|
|
834
1044
|
const suffix = randomInt(65536).toString(16).padStart(4, "0");
|
|
835
|
-
return `${
|
|
1045
|
+
return `${pick2(ADJECTIVES)}-${pick2(NOUNS)}-${suffix}`;
|
|
836
1046
|
}
|
|
837
1047
|
function resolveHostSpec(opts, defaultZone) {
|
|
838
1048
|
if (opts.hostname) {
|
|
@@ -889,8 +1099,8 @@ async function createTunnelSubdomain(cf, opts) {
|
|
|
889
1099
|
let dnsRecordId;
|
|
890
1100
|
try {
|
|
891
1101
|
const suffix = randomInt2(65536).toString(16).padStart(4, "0");
|
|
892
|
-
const
|
|
893
|
-
const tunnel = await createTunnel(cf, `${MANAGED_TUNNEL_PREFIX}${
|
|
1102
|
+
const label4 = host.subdomain === "@" ? "root" : host.subdomain;
|
|
1103
|
+
const tunnel = await createTunnel(cf, `${MANAGED_TUNNEL_PREFIX}${label4}-${suffix}`);
|
|
894
1104
|
tunnelId = tunnel.id;
|
|
895
1105
|
const token = await getTunnelToken(cf, tunnelId);
|
|
896
1106
|
await putIngress(cf, tunnelId, buildIngress({ hostname: host.hostname, port: opts.port, proto: opts.proto, host: opts.host }));
|
|
@@ -1037,8 +1247,8 @@ async function listAll(cf, opts = {}) {
|
|
|
1037
1247
|
};
|
|
1038
1248
|
});
|
|
1039
1249
|
if (opts.all) {
|
|
1040
|
-
const { listCargoCnames } = await import("./dns-
|
|
1041
|
-
const { listZones: listZones3 } = await import("./zones-
|
|
1250
|
+
const { listCargoCnames } = await import("./dns-5OXFAQ4D.js");
|
|
1251
|
+
const { listZones: listZones3 } = await import("./zones-QYV3DSEY.js");
|
|
1042
1252
|
const tracked = new Set(entries.map(entryFqdn));
|
|
1043
1253
|
for (const zone of await listZones3(cf.token)) {
|
|
1044
1254
|
for (const rec of await listCargoCnames(cf.token, zone.id)) {
|
|
@@ -1053,7 +1263,7 @@ async function listAll(cf, opts = {}) {
|
|
|
1053
1263
|
|
|
1054
1264
|
// src/core/up-runner.ts
|
|
1055
1265
|
function logFileFor(subdomain) {
|
|
1056
|
-
return
|
|
1266
|
+
return join6(logDir, `${subdomain === "@" ? "root" : subdomain}.log`);
|
|
1057
1267
|
}
|
|
1058
1268
|
async function startTunnels(cf, bin, items, opts = {}) {
|
|
1059
1269
|
const started = [];
|
|
@@ -1202,7 +1412,7 @@ async function runUp(specArgs, opts) {
|
|
|
1202
1412
|
await startTunnels(cf, bin, items, { detach: opts.detach, protocol });
|
|
1203
1413
|
}
|
|
1204
1414
|
function registerServices(items, domain, proto, protocol) {
|
|
1205
|
-
|
|
1415
|
+
assertServiceSupported();
|
|
1206
1416
|
if (!protocol) {
|
|
1207
1417
|
say.warn("No edge protocol set \u2014 cloudflared will pick QUIC, which some networks drop.");
|
|
1208
1418
|
say.dim(" \u2192 add --protocol http2 for UDP-hostile networks");
|
|
@@ -1219,7 +1429,7 @@ function registerServices(items, domain, proto, protocol) {
|
|
|
1219
1429
|
say.dim(" \u2192 check them: cloudtunnel ls \xB7 remove: cloudtunnel delete <#>");
|
|
1220
1430
|
}
|
|
1221
1431
|
function registerUp(program) {
|
|
1222
|
-
program.command("up", { isDefault: true }).argument("[specs...]", "tunnels to start: [subdomain:]port[@host] (e.g. api:8080 web:8081@localhost)").description("Start one or more tunnels (also: `cloudtunnel 8080`)").option("-d, --domain <domain>", "domain for the subdomains (prompted from a list if unset)").option("--proto <proto>", "local service protocol: http | https", "http").option("--protocol <proto>", "cloudflared edge transport: auto | http2 | quic (http2 for UDP-hostile networks)").option("--detach", "run the connectors in the background").option("--service", "register each subdomain as a
|
|
1432
|
+
program.command("up", { isDefault: true }).argument("[specs...]", "tunnels to start: [subdomain:]port[@host] (e.g. api:8080 web:8081@localhost)").description("Start one or more tunnels (also: `cloudtunnel 8080`)").option("-d, --domain <domain>", "domain for the subdomains (prompted from a list if unset)").option("--proto <proto>", "local service protocol: http | https", "http").option("--protocol <proto>", "cloudflared edge transport: auto | http2 | quic (http2 for UDP-hostile networks)").option("--detach", "run the connectors in the background").option("--service", "register each subdomain as a boot service (Linux systemd \xB7 macOS launchd \xB7 Windows Task Scheduler)").option("-f, --force", "replace a non-tunnel DNS record occupying the hostname").option("-y, --yes", "don't prompt; don't ask before replacing an existing record").action((specs, opts) => runUp(specs, opts));
|
|
1223
1433
|
}
|
|
1224
1434
|
|
|
1225
1435
|
// src/commands/ls.ts
|
|
@@ -1241,14 +1451,12 @@ function registerLs(program) {
|
|
|
1241
1451
|
|
|
1242
1452
|
// src/commands/delete.ts
|
|
1243
1453
|
async function deleteOne(cf, fqdn, opts) {
|
|
1454
|
+
const hasService = serviceState(fqdn) !== "none";
|
|
1455
|
+
if (hasService && !opts.dryRun) uninstallService(fqdn);
|
|
1244
1456
|
await removeTunnelSubdomain(cf, fqdn, { force: opts.force, dryRun: opts.dryRun });
|
|
1245
|
-
if (
|
|
1246
|
-
if (opts.dryRun) {
|
|
1247
|
-
|
|
1248
|
-
return;
|
|
1249
|
-
}
|
|
1250
|
-
uninstallService(fqdn);
|
|
1251
|
-
say.ok(`Removed boot service ${serviceName(fqdn)}`);
|
|
1457
|
+
if (!hasService) return;
|
|
1458
|
+
if (opts.dryRun) say.info(`Would also remove boot service ${serviceName(fqdn)}`);
|
|
1459
|
+
else say.ok(`Removed boot service ${serviceName(fqdn)}`);
|
|
1252
1460
|
}
|
|
1253
1461
|
function registerDelete(program) {
|
|
1254
1462
|
program.command("delete").argument("[targets...]", "subdomains to remove by # / name / URL (omit with --all)").description("Release tunnel(s) \u2014 deletes the tunnel + DNS, and any systemd boot service").option("--all", "release every tracked subdomain").option("-f, --force", "release even a resource not created by cloudtunnel").option("--dry-run", "show what would be released without doing it").action(async (targets, opts) => {
|
|
@@ -1279,7 +1487,7 @@ function registerDelete(program) {
|
|
|
1279
1487
|
}
|
|
1280
1488
|
|
|
1281
1489
|
// src/commands/logs.ts
|
|
1282
|
-
import { closeSync, existsSync as
|
|
1490
|
+
import { closeSync, existsSync as existsSync6, openSync as openSync2, readFileSync as readFileSync4, readSync, statSync, watch } from "fs";
|
|
1283
1491
|
function printTail(file, n) {
|
|
1284
1492
|
const lines = readFileSync4(file, "utf8").split("\n");
|
|
1285
1493
|
const tail = lines.slice(-n).join("\n");
|
|
@@ -1313,7 +1521,7 @@ function follow(file, fromPos) {
|
|
|
1313
1521
|
function registerLogs(program) {
|
|
1314
1522
|
program.command("logs").argument("<target>", "subdomain name / hostname / id / #").description("Show the connector log for a subdomain (use -f to follow)").option("-f, --follow", "keep printing new log lines (like tail -f)").option("-n, --lines <n>", "number of lines to show", "50").action((name, opts) => {
|
|
1315
1523
|
const { fqdn, entry } = resolveTarget(name);
|
|
1316
|
-
if (!entry?.logFile || !
|
|
1524
|
+
if (!entry?.logFile || !existsSync6(entry.logFile)) {
|
|
1317
1525
|
throw new CliError(`No logs for ${fqdn} yet.`, { hint: "start it with `cloudtunnel up` or `cloudtunnel run`" });
|
|
1318
1526
|
}
|
|
1319
1527
|
const n = Math.max(1, Number(opts.lines) || 50);
|