@iamken/cloudtunnel 0.11.3 → 0.11.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/dist/{chunk-BIGZFMQ6.js → chunk-FILVDOPL.js} +10 -1
- package/dist/chunk-FILVDOPL.js.map +1 -0
- package/dist/{dns-3WHXRL6V.js → dns-BNDEFUCM.js} +4 -2
- package/dist/index.js +157 -116
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-BIGZFMQ6.js.map +0 -1
- /package/dist/{dns-3WHXRL6V.js.map → dns-BNDEFUCM.js.map} +0 -0
|
@@ -29,6 +29,14 @@ async function createCname(token, zoneId, name, tunnelId) {
|
|
|
29
29
|
}
|
|
30
30
|
return env.result;
|
|
31
31
|
}
|
|
32
|
+
async function findDnsRecords(token, zoneId, fqdn) {
|
|
33
|
+
const env = await cfRequest(
|
|
34
|
+
token,
|
|
35
|
+
"GET",
|
|
36
|
+
`/zones/${zoneId}/dns_records?name=${encodeURIComponent(fqdn)}`
|
|
37
|
+
);
|
|
38
|
+
return env.result ?? [];
|
|
39
|
+
}
|
|
32
40
|
async function findCname(token, zoneId, fqdn) {
|
|
33
41
|
const env = await cfRequest(
|
|
34
42
|
token,
|
|
@@ -55,8 +63,9 @@ export {
|
|
|
55
63
|
cargoTarget,
|
|
56
64
|
isManagedDns,
|
|
57
65
|
createCname,
|
|
66
|
+
findDnsRecords,
|
|
58
67
|
findCname,
|
|
59
68
|
listCargoCnames,
|
|
60
69
|
deleteDnsRecord
|
|
61
70
|
};
|
|
62
|
-
//# sourceMappingURL=chunk-
|
|
71
|
+
//# sourceMappingURL=chunk-FILVDOPL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cloudflare/dns.ts"],"sourcesContent":["import { cfPaginate, cfRequest } from \"./client.js\";\nimport type { DnsRecord } from \"./types.js\";\nimport { CliError } from \"../ui/errors.js\";\n\n/** DNS records created by cloudtunnel carry this comment (ownership marker). */\nexport const MANAGED_DNS_COMMENT = \"managed-by:cloudtunnel\";\n\nexport function cargoTarget(tunnelId: string): string {\n return `${tunnelId}.cfargotunnel.com`;\n}\n\nexport function isManagedDns(record: DnsRecord): boolean {\n return record.comment === MANAGED_DNS_COMMENT;\n}\n\n/** Create the proxied CNAME that routes a subdomain to the tunnel. Asserts the\n * record came back orange-clouded (grey-cloud would silently break routing). */\nexport async function createCname(\n token: string,\n zoneId: string,\n name: string,\n tunnelId: string,\n): Promise<DnsRecord> {\n const env = await cfRequest<DnsRecord>(token, \"POST\", `/zones/${zoneId}/dns_records`, {\n type: \"CNAME\",\n name,\n content: cargoTarget(tunnelId),\n proxied: true,\n ttl: 1,\n comment: MANAGED_DNS_COMMENT,\n });\n if (env.result.proxied !== true) {\n throw new CliError(`DNS record for ${name} was created grey-clouded (not proxied).`, {\n hint: \"cfargotunnel routing requires a proxied (orange-cloud) record\",\n });\n }\n return env.result;\n}\n\nexport async function findDnsRecords(\n token: string,\n zoneId: string,\n fqdn: string,\n): Promise<DnsRecord[]> {\n const env = await cfRequest<DnsRecord[]>(\n token,\n \"GET\",\n `/zones/${zoneId}/dns_records?name=${encodeURIComponent(fqdn)}`,\n );\n return env.result ?? [];\n}\n\nexport async function findCname(\n token: string,\n zoneId: string,\n fqdn: string,\n): Promise<DnsRecord | undefined> {\n const env = await cfRequest<DnsRecord[]>(\n token,\n \"GET\",\n `/zones/${zoneId}/dns_records?type=CNAME&name=${encodeURIComponent(fqdn)}`,\n );\n return env.result?.[0];\n}\n\n/** CNAMEs in the zone that point at a cfargotunnel target (any tool's). */\nexport async function listCargoCnames(token: string, zoneId: string): Promise<DnsRecord[]> {\n const all = await cfPaginate<DnsRecord>(token, `/zones/${zoneId}/dns_records?type=CNAME`);\n return all.filter((r) => r.content.endsWith(\".cfargotunnel.com\"));\n}\n\nexport async function deleteDnsRecord(token: string, zoneId: string, recordId: string): Promise<void> {\n try {\n await cfRequest<unknown>(token, \"DELETE\", `/zones/${zoneId}/dns_records/${recordId}`);\n } catch (err) {\n if (err instanceof CliError && err.status === 404) return;\n throw err;\n }\n}\n"],"mappings":";;;;;;;;AAKO,IAAM,sBAAsB;AAE5B,SAAS,YAAY,UAA0B;AACpD,SAAO,GAAG,QAAQ;AACpB;AAEO,SAAS,aAAa,QAA4B;AACvD,SAAO,OAAO,YAAY;AAC5B;AAIA,eAAsB,YACpB,OACA,QACA,MACA,UACoB;AACpB,QAAM,MAAM,MAAM,UAAqB,OAAO,QAAQ,UAAU,MAAM,gBAAgB;AAAA,IACpF,MAAM;AAAA,IACN;AAAA,IACA,SAAS,YAAY,QAAQ;AAAA,IAC7B,SAAS;AAAA,IACT,KAAK;AAAA,IACL,SAAS;AAAA,EACX,CAAC;AACD,MAAI,IAAI,OAAO,YAAY,MAAM;AAC/B,UAAM,IAAI,SAAS,kBAAkB,IAAI,4CAA4C;AAAA,MACnF,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO,IAAI;AACb;AAEA,eAAsB,eACpB,OACA,QACA,MACsB;AACtB,QAAM,MAAM,MAAM;AAAA,IAChB;AAAA,IACA;AAAA,IACA,UAAU,MAAM,qBAAqB,mBAAmB,IAAI,CAAC;AAAA,EAC/D;AACA,SAAO,IAAI,UAAU,CAAC;AACxB;AAEA,eAAsB,UACpB,OACA,QACA,MACgC;AAChC,QAAM,MAAM,MAAM;AAAA,IAChB;AAAA,IACA;AAAA,IACA,UAAU,MAAM,gCAAgC,mBAAmB,IAAI,CAAC;AAAA,EAC1E;AACA,SAAO,IAAI,SAAS,CAAC;AACvB;AAGA,eAAsB,gBAAgB,OAAe,QAAsC;AACzF,QAAM,MAAM,MAAM,WAAsB,OAAO,UAAU,MAAM,yBAAyB;AACxF,SAAO,IAAI,OAAO,CAAC,MAAM,EAAE,QAAQ,SAAS,mBAAmB,CAAC;AAClE;AAEA,eAAsB,gBAAgB,OAAe,QAAgB,UAAiC;AACpG,MAAI;AACF,UAAM,UAAmB,OAAO,UAAU,UAAU,MAAM,gBAAgB,QAAQ,EAAE;AAAA,EACtF,SAAS,KAAK;AACZ,QAAI,eAAe,YAAY,IAAI,WAAW,IAAK;AACnD,UAAM;AAAA,EACR;AACF;","names":[]}
|
|
@@ -5,9 +5,10 @@ import {
|
|
|
5
5
|
createCname,
|
|
6
6
|
deleteDnsRecord,
|
|
7
7
|
findCname,
|
|
8
|
+
findDnsRecords,
|
|
8
9
|
isManagedDns,
|
|
9
10
|
listCargoCnames
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-FILVDOPL.js";
|
|
11
12
|
import "./chunk-7XACBYL7.js";
|
|
12
13
|
export {
|
|
13
14
|
MANAGED_DNS_COMMENT,
|
|
@@ -15,7 +16,8 @@ export {
|
|
|
15
16
|
createCname,
|
|
16
17
|
deleteDnsRecord,
|
|
17
18
|
findCname,
|
|
19
|
+
findDnsRecords,
|
|
18
20
|
isManagedDns,
|
|
19
21
|
listCargoCnames
|
|
20
22
|
};
|
|
21
|
-
//# sourceMappingURL=dns-
|
|
23
|
+
//# sourceMappingURL=dns-BNDEFUCM.js.map
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
createCname,
|
|
8
8
|
deleteDnsRecord,
|
|
9
9
|
findCname,
|
|
10
|
+
findDnsRecords,
|
|
10
11
|
isManagedDns
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-FILVDOPL.js";
|
|
12
13
|
import {
|
|
13
14
|
CliError,
|
|
14
15
|
DEFAULT_API_BASE,
|
|
@@ -246,11 +247,13 @@ function assertSupported() {
|
|
|
246
247
|
}
|
|
247
248
|
function install(d) {
|
|
248
249
|
assertSupported();
|
|
249
|
-
const
|
|
250
|
+
const name = label(d.fqdn);
|
|
251
|
+
const tmp = join2(tmpdir(), name);
|
|
250
252
|
writeFileSync(tmp, buildUnit(d), { mode: 420 });
|
|
251
253
|
privileged(["install", "-m", "0644", tmp, unitPath(d.fqdn)]);
|
|
252
254
|
privileged(["systemctl", "daemon-reload"]);
|
|
253
|
-
privileged(["systemctl", "enable",
|
|
255
|
+
privileged(["systemctl", "enable", name]);
|
|
256
|
+
privileged(["systemctl", "restart", name]);
|
|
254
257
|
}
|
|
255
258
|
function uninstall(fqdn) {
|
|
256
259
|
try {
|
|
@@ -388,7 +391,7 @@ function buildTaskXml(d) {
|
|
|
388
391
|
" <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>",
|
|
389
392
|
" <RestartOnFailure><Interval>PT1M</Interval><Count>999</Count></RestartOnFailure>",
|
|
390
393
|
" <Enabled>true</Enabled>",
|
|
391
|
-
"
|
|
394
|
+
"</Settings>",
|
|
392
395
|
' <Actions Context="Author">',
|
|
393
396
|
` <Exec><Command>${xml2(d.nodePath)}</Command><Arguments>${xml2(args)}</Arguments></Exec>`,
|
|
394
397
|
" </Actions>",
|
|
@@ -408,9 +411,11 @@ function assertSupported3() {
|
|
|
408
411
|
}
|
|
409
412
|
function install3(d) {
|
|
410
413
|
const file = join4(tmpdir2(), `${d.slug}.task.xml`);
|
|
414
|
+
const name = label3(d.fqdn);
|
|
411
415
|
writeFileSync3(file, "\uFEFF" + buildTaskXml(d), { encoding: "utf16le" });
|
|
412
|
-
|
|
413
|
-
schtasks
|
|
416
|
+
schtasks(["/End", "/TN", name]);
|
|
417
|
+
execFileSync3("schtasks", ["/Create", "/TN", name, "/XML", file, "/F"], { stdio: "inherit" });
|
|
418
|
+
schtasks(["/Run", "/TN", name]);
|
|
414
419
|
}
|
|
415
420
|
function uninstall3(fqdn) {
|
|
416
421
|
schtasks(["/Delete", "/TN", label3(fqdn), "/F"]);
|
|
@@ -940,17 +945,115 @@ function extractTgz(bytes) {
|
|
|
940
945
|
});
|
|
941
946
|
}
|
|
942
947
|
|
|
948
|
+
// src/cloudflare/tunnels.ts
|
|
949
|
+
var MANAGED_TUNNEL_PREFIX = "ct-";
|
|
950
|
+
function isManagedTunnel(tunnel) {
|
|
951
|
+
return tunnel.name.startsWith(MANAGED_TUNNEL_PREFIX);
|
|
952
|
+
}
|
|
953
|
+
async function createTunnel(cf, name) {
|
|
954
|
+
const env = await cfRequest(cf.token, "POST", `/accounts/${cf.accountId}/cfd_tunnel`, {
|
|
955
|
+
name,
|
|
956
|
+
config_src: "cloudflare"
|
|
957
|
+
});
|
|
958
|
+
return env.result;
|
|
959
|
+
}
|
|
960
|
+
function listTunnels(cf) {
|
|
961
|
+
return cfPaginate(cf.token, `/accounts/${cf.accountId}/cfd_tunnel?is_deleted=false`);
|
|
962
|
+
}
|
|
963
|
+
async function getTunnel(cf, id) {
|
|
964
|
+
return (await cfRequest(cf.token, "GET", `/accounts/${cf.accountId}/cfd_tunnel/${id}`)).result;
|
|
965
|
+
}
|
|
966
|
+
async function deleteTunnel(cf, id) {
|
|
967
|
+
await cfRequest(cf.token, "DELETE", `/accounts/${cf.accountId}/cfd_tunnel/${id}`);
|
|
968
|
+
}
|
|
969
|
+
async function cleanupConnections(cf, id) {
|
|
970
|
+
await cfRequest(cf.token, "DELETE", `/accounts/${cf.accountId}/cfd_tunnel/${id}/connections`);
|
|
971
|
+
}
|
|
972
|
+
async function deleteTunnelWithConnections(cf, id) {
|
|
973
|
+
try {
|
|
974
|
+
await deleteTunnel(cf, id);
|
|
975
|
+
} catch (err) {
|
|
976
|
+
if (err instanceof CliError && /active connections/i.test(err.message)) {
|
|
977
|
+
await cleanupConnections(cf, id);
|
|
978
|
+
await deleteTunnel(cf, id);
|
|
979
|
+
} else {
|
|
980
|
+
throw err;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
async function getTunnelToken(cf, id) {
|
|
985
|
+
return (await cfRequest(cf.token, "GET", `/accounts/${cf.accountId}/cfd_tunnel/${id}/token`)).result;
|
|
986
|
+
}
|
|
987
|
+
async function putIngress(cf, id, ingress) {
|
|
988
|
+
await cfRequest(cf.token, "PUT", `/accounts/${cf.accountId}/cfd_tunnel/${id}/configurations`, {
|
|
989
|
+
config: { ingress }
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
async function getConnections(cf, id) {
|
|
993
|
+
const env = await cfRequest(
|
|
994
|
+
cf.token,
|
|
995
|
+
"GET",
|
|
996
|
+
`/accounts/${cf.accountId}/cfd_tunnel/${id}/connections`
|
|
997
|
+
);
|
|
998
|
+
return env.result ?? [];
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// src/connector/health.ts
|
|
1002
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1003
|
+
async function waitHealthy(cf, tunnelId, opts = {}) {
|
|
1004
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 3e4);
|
|
1005
|
+
while (Date.now() < deadline) {
|
|
1006
|
+
if (opts.signal?.aborted) return "dead";
|
|
1007
|
+
try {
|
|
1008
|
+
const connections = await getConnections(cf, tunnelId);
|
|
1009
|
+
if (connections.length > 0) return "healthy";
|
|
1010
|
+
} catch {
|
|
1011
|
+
}
|
|
1012
|
+
await sleep(2e3);
|
|
1013
|
+
}
|
|
1014
|
+
return opts.signal?.aborted ? "dead" : "provisioning";
|
|
1015
|
+
}
|
|
1016
|
+
function extractMetricsPort(log) {
|
|
1017
|
+
const matches = [...log.matchAll(/Starting metrics server on 127\.0\.0\.1:(\d+)\/metrics/g)];
|
|
1018
|
+
if (!matches.length) return void 0;
|
|
1019
|
+
const last = matches[matches.length - 1];
|
|
1020
|
+
const port = Number(last?.[1]);
|
|
1021
|
+
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
|
|
1022
|
+
}
|
|
1023
|
+
async function checkLocalReady(port, opts = {}) {
|
|
1024
|
+
const controller = new AbortController();
|
|
1025
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 2e3);
|
|
1026
|
+
try {
|
|
1027
|
+
const res = await fetch(`http://127.0.0.1:${port}/ready`, { signal: controller.signal });
|
|
1028
|
+
if (!res.ok) return { healthy: false, readyConnections: 0, status: res.status };
|
|
1029
|
+
const data = await res.json().catch(() => ({}));
|
|
1030
|
+
const readyConnections = Number(data.readyConnections) || 0;
|
|
1031
|
+
return { healthy: readyConnections > 0, readyConnections, status: res.status };
|
|
1032
|
+
} catch {
|
|
1033
|
+
return { healthy: false, readyConnections: 0 };
|
|
1034
|
+
} finally {
|
|
1035
|
+
clearTimeout(timer);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
async function probeEntryState(entry, gone) {
|
|
1039
|
+
if (gone || entry.state !== "running" || !entry.pid) return "down";
|
|
1040
|
+
if (!entry.metricsPort) return "up";
|
|
1041
|
+
const ready = await checkLocalReady(entry.metricsPort, { timeoutMs: 300 });
|
|
1042
|
+
return ready.healthy ? "up" : "reconnecting";
|
|
1043
|
+
}
|
|
1044
|
+
|
|
943
1045
|
// src/core/up-runner.ts
|
|
944
1046
|
import { join as join7 } from "path";
|
|
945
1047
|
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
|
|
946
1048
|
import * as clack2 from "@clack/prompts";
|
|
947
1049
|
|
|
948
1050
|
// src/connector/process.ts
|
|
949
|
-
import { execFileSync as
|
|
1051
|
+
import { execFileSync as execFileSync7, spawn as spawn2 } from "child_process";
|
|
950
1052
|
import { openSync } from "fs";
|
|
951
1053
|
|
|
952
1054
|
// src/connector/registry.ts
|
|
953
1055
|
import { existsSync as existsSync5, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
1056
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
954
1057
|
import { readFile } from "fs/promises";
|
|
955
1058
|
import os3 from "os";
|
|
956
1059
|
import lockfile from "proper-lockfile";
|
|
@@ -961,6 +1064,13 @@ function currentBootId() {
|
|
|
961
1064
|
try {
|
|
962
1065
|
return readFileSync3("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
963
1066
|
} catch {
|
|
1067
|
+
if (process.platform === "darwin") {
|
|
1068
|
+
try {
|
|
1069
|
+
const out = execFileSync6("sysctl", ["-n", "kern.boottime"], { encoding: "utf8" }).trim();
|
|
1070
|
+
return `darwin-${out}-${os3.hostname()}`;
|
|
1071
|
+
} catch {
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
964
1074
|
const bootMinute = Math.floor((Date.now() - os3.uptime() * 1e3) / 6e4);
|
|
965
1075
|
return `boot-${bootMinute}-${os3.hostname()}`;
|
|
966
1076
|
}
|
|
@@ -1047,6 +1157,14 @@ async function isOurConnector(entry) {
|
|
|
1047
1157
|
return false;
|
|
1048
1158
|
}
|
|
1049
1159
|
}
|
|
1160
|
+
if (process.platform === "darwin") {
|
|
1161
|
+
try {
|
|
1162
|
+
const comm = execFileSync6("ps", ["-p", String(entry.pid), "-o", "command="], { encoding: "utf8" });
|
|
1163
|
+
return comm.includes("cloudflared");
|
|
1164
|
+
} catch {
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1050
1168
|
return true;
|
|
1051
1169
|
}
|
|
1052
1170
|
async function reconcile() {
|
|
@@ -1067,7 +1185,7 @@ async function reconcile() {
|
|
|
1067
1185
|
}
|
|
1068
1186
|
|
|
1069
1187
|
// src/connector/process.ts
|
|
1070
|
-
var
|
|
1188
|
+
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1071
1189
|
function startConnector(opts) {
|
|
1072
1190
|
const args = ["tunnel", "--metrics", "127.0.0.1:0", "run"];
|
|
1073
1191
|
if (opts.protocol) args.push("--protocol", opts.protocol);
|
|
@@ -1088,7 +1206,7 @@ async function stopConnector(entry) {
|
|
|
1088
1206
|
const pid = entry.pid;
|
|
1089
1207
|
if (process.platform === "win32") {
|
|
1090
1208
|
try {
|
|
1091
|
-
|
|
1209
|
+
execFileSync7("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
|
|
1092
1210
|
} catch {
|
|
1093
1211
|
return false;
|
|
1094
1212
|
}
|
|
@@ -1099,7 +1217,7 @@ async function stopConnector(entry) {
|
|
|
1099
1217
|
} catch {
|
|
1100
1218
|
return false;
|
|
1101
1219
|
}
|
|
1102
|
-
await
|
|
1220
|
+
await sleep2(3e3);
|
|
1103
1221
|
if (await isOurConnector(entry)) {
|
|
1104
1222
|
try {
|
|
1105
1223
|
process.kill(pid, "SIGKILL");
|
|
@@ -1109,103 +1227,6 @@ async function stopConnector(entry) {
|
|
|
1109
1227
|
return true;
|
|
1110
1228
|
}
|
|
1111
1229
|
|
|
1112
|
-
// src/cloudflare/tunnels.ts
|
|
1113
|
-
var MANAGED_TUNNEL_PREFIX = "ct-";
|
|
1114
|
-
function isManagedTunnel(tunnel) {
|
|
1115
|
-
return tunnel.name.startsWith(MANAGED_TUNNEL_PREFIX);
|
|
1116
|
-
}
|
|
1117
|
-
async function createTunnel(cf, name) {
|
|
1118
|
-
const env = await cfRequest(cf.token, "POST", `/accounts/${cf.accountId}/cfd_tunnel`, {
|
|
1119
|
-
name,
|
|
1120
|
-
config_src: "cloudflare"
|
|
1121
|
-
});
|
|
1122
|
-
return env.result;
|
|
1123
|
-
}
|
|
1124
|
-
function listTunnels(cf) {
|
|
1125
|
-
return cfPaginate(cf.token, `/accounts/${cf.accountId}/cfd_tunnel?is_deleted=false`);
|
|
1126
|
-
}
|
|
1127
|
-
async function getTunnel(cf, id) {
|
|
1128
|
-
return (await cfRequest(cf.token, "GET", `/accounts/${cf.accountId}/cfd_tunnel/${id}`)).result;
|
|
1129
|
-
}
|
|
1130
|
-
async function deleteTunnel(cf, id) {
|
|
1131
|
-
await cfRequest(cf.token, "DELETE", `/accounts/${cf.accountId}/cfd_tunnel/${id}`);
|
|
1132
|
-
}
|
|
1133
|
-
async function cleanupConnections(cf, id) {
|
|
1134
|
-
await cfRequest(cf.token, "DELETE", `/accounts/${cf.accountId}/cfd_tunnel/${id}/connections`);
|
|
1135
|
-
}
|
|
1136
|
-
async function deleteTunnelWithConnections(cf, id) {
|
|
1137
|
-
try {
|
|
1138
|
-
await deleteTunnel(cf, id);
|
|
1139
|
-
} catch (err) {
|
|
1140
|
-
if (err instanceof CliError && /active connections/i.test(err.message)) {
|
|
1141
|
-
await cleanupConnections(cf, id);
|
|
1142
|
-
await deleteTunnel(cf, id);
|
|
1143
|
-
} else {
|
|
1144
|
-
throw err;
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
async function getTunnelToken(cf, id) {
|
|
1149
|
-
return (await cfRequest(cf.token, "GET", `/accounts/${cf.accountId}/cfd_tunnel/${id}/token`)).result;
|
|
1150
|
-
}
|
|
1151
|
-
async function putIngress(cf, id, ingress) {
|
|
1152
|
-
await cfRequest(cf.token, "PUT", `/accounts/${cf.accountId}/cfd_tunnel/${id}/configurations`, {
|
|
1153
|
-
config: { ingress }
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
async function getConnections(cf, id) {
|
|
1157
|
-
const env = await cfRequest(
|
|
1158
|
-
cf.token,
|
|
1159
|
-
"GET",
|
|
1160
|
-
`/accounts/${cf.accountId}/cfd_tunnel/${id}/connections`
|
|
1161
|
-
);
|
|
1162
|
-
return env.result ?? [];
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
// src/connector/health.ts
|
|
1166
|
-
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1167
|
-
async function waitHealthy(cf, tunnelId, opts = {}) {
|
|
1168
|
-
const deadline = Date.now() + (opts.timeoutMs ?? 3e4);
|
|
1169
|
-
while (Date.now() < deadline) {
|
|
1170
|
-
if (opts.signal?.aborted) return "dead";
|
|
1171
|
-
try {
|
|
1172
|
-
const connections = await getConnections(cf, tunnelId);
|
|
1173
|
-
if (connections.length > 0) return "healthy";
|
|
1174
|
-
} catch {
|
|
1175
|
-
}
|
|
1176
|
-
await sleep2(2e3);
|
|
1177
|
-
}
|
|
1178
|
-
return opts.signal?.aborted ? "dead" : "provisioning";
|
|
1179
|
-
}
|
|
1180
|
-
function extractMetricsPort(log) {
|
|
1181
|
-
const matches = [...log.matchAll(/Starting metrics server on 127\.0\.0\.1:(\d+)\/metrics/g)];
|
|
1182
|
-
if (!matches.length) return void 0;
|
|
1183
|
-
const last = matches[matches.length - 1];
|
|
1184
|
-
const port = Number(last?.[1]);
|
|
1185
|
-
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
|
|
1186
|
-
}
|
|
1187
|
-
async function checkLocalReady(port, opts = {}) {
|
|
1188
|
-
const controller = new AbortController();
|
|
1189
|
-
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 2e3);
|
|
1190
|
-
try {
|
|
1191
|
-
const res = await fetch(`http://127.0.0.1:${port}/ready`, { signal: controller.signal });
|
|
1192
|
-
if (!res.ok) return { healthy: false, readyConnections: 0, status: res.status };
|
|
1193
|
-
const data = await res.json().catch(() => ({}));
|
|
1194
|
-
const readyConnections = Number(data.readyConnections) || 0;
|
|
1195
|
-
return { healthy: readyConnections > 0, readyConnections, status: res.status };
|
|
1196
|
-
} catch {
|
|
1197
|
-
return { healthy: false, readyConnections: 0 };
|
|
1198
|
-
} finally {
|
|
1199
|
-
clearTimeout(timer);
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
async function probeEntryState(entry, gone) {
|
|
1203
|
-
if (gone || entry.state !== "running" || !entry.pid) return "down";
|
|
1204
|
-
if (!entry.metricsPort) return "up";
|
|
1205
|
-
const ready = await checkLocalReady(entry.metricsPort, { timeoutMs: 300 });
|
|
1206
|
-
return ready.healthy ? "up" : "reconnecting";
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
1230
|
// src/core/orchestrator-create.ts
|
|
1210
1231
|
import { randomInt as randomInt2 } from "crypto";
|
|
1211
1232
|
|
|
@@ -1278,9 +1299,9 @@ var tunnelIdFromCname = (content) => content.replace(/\.cfargotunnel\.com\.?$/,
|
|
|
1278
1299
|
async function createTunnelSubdomain(cf, opts) {
|
|
1279
1300
|
const host = resolveHostSpec(opts, opts.defaultZone);
|
|
1280
1301
|
const zone = await resolveZone(cf.token, host.zone);
|
|
1281
|
-
const
|
|
1282
|
-
if (
|
|
1283
|
-
const isTunnelRecord =
|
|
1302
|
+
const existingList = await findDnsRecords(cf.token, zone.id, host.hostname);
|
|
1303
|
+
if (existingList.length > 0) {
|
|
1304
|
+
const isTunnelRecord = existingList.every((r) => r.content.endsWith(".cfargotunnel.com"));
|
|
1284
1305
|
if (!isTunnelRecord && !opts.force) {
|
|
1285
1306
|
throw new CliError(`${host.hostname} is taken by a non-tunnel DNS record.`, {
|
|
1286
1307
|
hint: "pick another --subdomain/--hostname, or pass -f/--force to replace it"
|
|
@@ -1292,7 +1313,14 @@ async function createTunnelSubdomain(cf, opts) {
|
|
|
1292
1313
|
throw new CliError("Cancelled.", { exitCode: 130 });
|
|
1293
1314
|
}
|
|
1294
1315
|
}
|
|
1295
|
-
|
|
1316
|
+
const prev = getEntry(host.hostname);
|
|
1317
|
+
if (prev) await stopConnector(prev);
|
|
1318
|
+
for (const rec of existingList) {
|
|
1319
|
+
await releaseHostname(cf, zone.id, rec);
|
|
1320
|
+
}
|
|
1321
|
+
} else {
|
|
1322
|
+
const prev = getEntry(host.hostname);
|
|
1323
|
+
if (prev) await stopConnector(prev);
|
|
1296
1324
|
}
|
|
1297
1325
|
await upsertEntry(host.hostname, {
|
|
1298
1326
|
subdomain: host.subdomain,
|
|
@@ -1301,7 +1329,9 @@ async function createTunnelSubdomain(cf, opts) {
|
|
|
1301
1329
|
port: opts.port,
|
|
1302
1330
|
proto: opts.proto,
|
|
1303
1331
|
host: opts.host,
|
|
1304
|
-
state: "provisioning"
|
|
1332
|
+
state: "provisioning",
|
|
1333
|
+
pid: void 0,
|
|
1334
|
+
metricsPort: void 0
|
|
1305
1335
|
});
|
|
1306
1336
|
let tunnelId;
|
|
1307
1337
|
let dnsRecordId;
|
|
@@ -1466,7 +1496,7 @@ async function resolveRemoteTarget(cf, target) {
|
|
|
1466
1496
|
}
|
|
1467
1497
|
const tunnel = matches[0];
|
|
1468
1498
|
if (!tunnel) return null;
|
|
1469
|
-
const { listCargoCnames } = await import("./dns-
|
|
1499
|
+
const { listCargoCnames } = await import("./dns-BNDEFUCM.js");
|
|
1470
1500
|
const { listZones: listZones3 } = await import("./zones-XNCXYCB4.js");
|
|
1471
1501
|
for (const zone of await listZones3(cf.token)) {
|
|
1472
1502
|
const rec = (await listCargoCnames(cf.token, zone.id)).find((r) => tunnelIdFromCname2(r.content) === tunnel.id);
|
|
@@ -1505,7 +1535,7 @@ async function listAll(cf, opts = {}) {
|
|
|
1505
1535
|
};
|
|
1506
1536
|
}));
|
|
1507
1537
|
if (opts.all) {
|
|
1508
|
-
const { listCargoCnames } = await import("./dns-
|
|
1538
|
+
const { listCargoCnames } = await import("./dns-BNDEFUCM.js");
|
|
1509
1539
|
const { listZones: listZones3 } = await import("./zones-XNCXYCB4.js");
|
|
1510
1540
|
const tracked = new Set(entries.map(entryFqdn));
|
|
1511
1541
|
const unmanaged = [];
|
|
@@ -1838,7 +1868,7 @@ async function runUp(specArgs, opts) {
|
|
|
1838
1868
|
}
|
|
1839
1869
|
await startTunnels(cf, bin, items, { detach: opts.detach, protocol, keepAlive: opts.keepAlive });
|
|
1840
1870
|
}
|
|
1841
|
-
var SERVICE_UP_TIMEOUT_MS =
|
|
1871
|
+
var SERVICE_UP_TIMEOUT_MS = 3e4;
|
|
1842
1872
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1843
1873
|
async function registerServices(cf, items, domain, proto, protocol) {
|
|
1844
1874
|
assertServiceSupported();
|
|
@@ -1850,6 +1880,7 @@ async function registerServices(cf, items, domain, proto, protocol) {
|
|
|
1850
1880
|
for (const item of items) {
|
|
1851
1881
|
const subdomain = item.name;
|
|
1852
1882
|
const fqdn = subdomain === "@" ? domain : `${subdomain}.${domain}`;
|
|
1883
|
+
await patchEntry(fqdn, { state: "provisioning", pid: void 0, metricsPort: void 0 });
|
|
1853
1884
|
installServiceForSpec({ subdomain, port: item.port, host: item.host, zone: domain, proto, protocol });
|
|
1854
1885
|
fqdns.push(fqdn);
|
|
1855
1886
|
}
|
|
@@ -1874,11 +1905,21 @@ async function waitServicesUp(fqdns, timeoutMs) {
|
|
|
1874
1905
|
while (pending.size > 0) {
|
|
1875
1906
|
for (const fqdn of [...pending]) {
|
|
1876
1907
|
const entry = getEntry(fqdn);
|
|
1877
|
-
if (entry?.state === "running" && entry.pid
|
|
1908
|
+
if (entry?.state === "running" && entry.pid && await isOurConnector(entry)) {
|
|
1909
|
+
if (!entry.metricsPort || (await checkLocalReady(entry.metricsPort, { timeoutMs: 500 })).healthy) {
|
|
1910
|
+
pending.delete(fqdn);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1878
1913
|
}
|
|
1879
1914
|
if (pending.size === 0 || Date.now() >= deadline) break;
|
|
1880
1915
|
await delay(500);
|
|
1881
1916
|
}
|
|
1917
|
+
for (const fqdn of [...pending]) {
|
|
1918
|
+
const entry = getEntry(fqdn);
|
|
1919
|
+
if (entry?.state === "running" && entry.pid && await isOurConnector(entry)) {
|
|
1920
|
+
pending.delete(fqdn);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1882
1923
|
return [...pending];
|
|
1883
1924
|
}
|
|
1884
1925
|
function registerUp(program) {
|