dsh-zen-remote 2.2.9 → 2.3.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.
Files changed (149) hide show
  1. package/README.md +2 -2
  2. package/README.zh-CN.md +2 -2
  3. package/docs/interface.md +1 -1
  4. package/docs/remote-access.md +20 -0
  5. package/lan-gate.mjs +11 -1
  6. package/lib/adapters/dsh-better-sidebar/index.js +6 -2
  7. package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
  8. package/lib/adapters/dsh-better-sidebar/methods.js +3 -0
  9. package/lib/adapters/dsh-better-sidebar/methods.js.map +1 -1
  10. package/lib/admin-routes.js +33 -2
  11. package/lib/admin-routes.js.map +1 -1
  12. package/lib/client.js +3289 -6
  13. package/lib/config.js +70 -0
  14. package/lib/config.js.map +1 -1
  15. package/lib/index.js +232 -2
  16. package/lib/index.js.map +1 -1
  17. package/lib/intercept.js +43 -3
  18. package/lib/intercept.js.map +1 -1
  19. package/lib/lan-gate-server.cjs +29 -3
  20. package/lib/merge-streams.js +42 -12
  21. package/lib/merge-streams.js.map +1 -1
  22. package/lib/types/adapters/dsh-better-sidebar/index.d.ts +1 -1
  23. package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
  24. package/lib/types/adapters/dsh-better-sidebar/methods.d.ts.map +1 -1
  25. package/lib/types/admin-routes.d.ts +13 -0
  26. package/lib/types/admin-routes.d.ts.map +1 -1
  27. package/lib/types/client/index.d.ts.map +1 -1
  28. package/lib/types/client/locales.d.ts +204 -1
  29. package/lib/types/client/locales.d.ts.map +1 -1
  30. package/lib/types/client/wizard/GatewayWizard.d.ts +29 -0
  31. package/lib/types/client/wizard/GatewayWizard.d.ts.map +1 -0
  32. package/lib/types/client/wizard/WizardCard.d.ts +30 -0
  33. package/lib/types/client/wizard/WizardCard.d.ts.map +1 -0
  34. package/lib/types/client/wizard/cards/MigrateCard.d.ts +15 -0
  35. package/lib/types/client/wizard/cards/MigrateCard.d.ts.map +1 -0
  36. package/lib/types/client/wizard/cards/PortForwardCard.d.ts +15 -0
  37. package/lib/types/client/wizard/cards/PortForwardCard.d.ts.map +1 -0
  38. package/lib/types/client/wizard/cards/ProxyCard.d.ts +19 -0
  39. package/lib/types/client/wizard/cards/ProxyCard.d.ts.map +1 -0
  40. package/lib/types/client/wizard/cards/TokenCard.d.ts +20 -0
  41. package/lib/types/client/wizard/cards/TokenCard.d.ts.map +1 -0
  42. package/lib/types/client/wizard/register-wizard.d.ts +51 -0
  43. package/lib/types/client/wizard/register-wizard.d.ts.map +1 -0
  44. package/lib/types/client/wizard/wizard-css.d.ts +10 -0
  45. package/lib/types/client/wizard/wizard-css.d.ts.map +1 -0
  46. package/lib/types/client-data/qr.d.ts +20 -0
  47. package/lib/types/client-data/qr.d.ts.map +1 -0
  48. package/lib/types/client-data/settings-form.d.ts +7 -2
  49. package/lib/types/client-data/settings-form.d.ts.map +1 -1
  50. package/lib/types/client-data/wizard.d.ts +247 -0
  51. package/lib/types/client-data/wizard.d.ts.map +1 -0
  52. package/lib/types/config.d.ts +27 -0
  53. package/lib/types/config.d.ts.map +1 -1
  54. package/lib/types/index.d.ts.map +1 -1
  55. package/lib/types/intercept.d.ts.map +1 -1
  56. package/lib/types/merge-streams.d.ts.map +1 -1
  57. package/lib/types/wizard/binary-io.d.ts +111 -0
  58. package/lib/types/wizard/binary-io.d.ts.map +1 -0
  59. package/lib/types/wizard/binary.d.ts +118 -0
  60. package/lib/types/wizard/binary.d.ts.map +1 -0
  61. package/lib/types/wizard/caddy-process.d.ts +101 -0
  62. package/lib/types/wizard/caddy-process.d.ts.map +1 -0
  63. package/lib/types/wizard/caddy.d.ts +53 -0
  64. package/lib/types/wizard/caddy.d.ts.map +1 -0
  65. package/lib/types/wizard/cloudflare.d.ts +112 -0
  66. package/lib/types/wizard/cloudflare.d.ts.map +1 -0
  67. package/lib/types/wizard/cloudflared.d.ts +71 -0
  68. package/lib/types/wizard/cloudflared.d.ts.map +1 -0
  69. package/lib/types/wizard/probe.d.ts +66 -0
  70. package/lib/types/wizard/probe.d.ts.map +1 -0
  71. package/lib/types/wizard/proxy-snippets.d.ts +60 -0
  72. package/lib/types/wizard/proxy-snippets.d.ts.map +1 -0
  73. package/lib/types/wizard/routes.d.ts +152 -0
  74. package/lib/types/wizard/routes.d.ts.map +1 -0
  75. package/lib/types/wizard/source-detect.d.ts +53 -0
  76. package/lib/types/wizard/source-detect.d.ts.map +1 -0
  77. package/lib/types/wizard/status.d.ts +115 -0
  78. package/lib/types/wizard/status.d.ts.map +1 -0
  79. package/lib/types/wizard/token-store.d.ts +27 -0
  80. package/lib/types/wizard/token-store.d.ts.map +1 -0
  81. package/lib/types/wizard/tunnel-orchestrator.d.ts +90 -0
  82. package/lib/types/wizard/tunnel-orchestrator.d.ts.map +1 -0
  83. package/lib/types/wizard/verify.d.ts +97 -0
  84. package/lib/types/wizard/verify.d.ts.map +1 -0
  85. package/lib/wizard/binary-io.js +386 -0
  86. package/lib/wizard/binary-io.js.map +1 -0
  87. package/lib/wizard/binary.js +138 -0
  88. package/lib/wizard/binary.js.map +1 -0
  89. package/lib/wizard/caddy-process.js +162 -0
  90. package/lib/wizard/caddy-process.js.map +1 -0
  91. package/lib/wizard/caddy.js +80 -0
  92. package/lib/wizard/caddy.js.map +1 -0
  93. package/lib/wizard/cloudflare.js +334 -0
  94. package/lib/wizard/cloudflare.js.map +1 -0
  95. package/lib/wizard/cloudflared.js +158 -0
  96. package/lib/wizard/cloudflared.js.map +1 -0
  97. package/lib/wizard/probe.js +118 -0
  98. package/lib/wizard/probe.js.map +1 -0
  99. package/lib/wizard/proxy-snippets.js +112 -0
  100. package/lib/wizard/proxy-snippets.js.map +1 -0
  101. package/lib/wizard/routes.js +462 -0
  102. package/lib/wizard/routes.js.map +1 -0
  103. package/lib/wizard/source-detect.js +77 -0
  104. package/lib/wizard/source-detect.js.map +1 -0
  105. package/lib/wizard/status.js +94 -0
  106. package/lib/wizard/status.js.map +1 -0
  107. package/lib/wizard/token-store.js +71 -0
  108. package/lib/wizard/token-store.js.map +1 -0
  109. package/lib/wizard/tunnel-orchestrator.js +155 -0
  110. package/lib/wizard/tunnel-orchestrator.js.map +1 -0
  111. package/lib/wizard/verify.js +171 -0
  112. package/lib/wizard/verify.js.map +1 -0
  113. package/package.json +37 -37
  114. package/scripts/check-settings-form.mjs +5 -4
  115. package/scripts/check-wizard-ui.mjs +690 -0
  116. package/src/adapters/dsh-better-sidebar/index.ts +6 -2
  117. package/src/adapters/dsh-better-sidebar/methods.ts +3 -0
  118. package/src/admin-routes.ts +44 -2
  119. package/src/client/index.tsx +9 -0
  120. package/src/client/locales.ts +410 -2
  121. package/src/client/wizard/GatewayWizard.tsx +2220 -0
  122. package/src/client/wizard/WizardCard.tsx +81 -0
  123. package/src/client/wizard/cards/MigrateCard.tsx +42 -0
  124. package/src/client/wizard/cards/PortForwardCard.tsx +37 -0
  125. package/src/client/wizard/cards/ProxyCard.tsx +56 -0
  126. package/src/client/wizard/cards/TokenCard.tsx +81 -0
  127. package/src/client/wizard/register-wizard.ts +99 -0
  128. package/src/client/wizard/wizard-css.ts +948 -0
  129. package/src/client-data/qr.ts +284 -0
  130. package/src/client-data/settings-form.ts +12 -3
  131. package/src/client-data/wizard.ts +432 -0
  132. package/src/config.ts +87 -0
  133. package/src/index.ts +227 -2
  134. package/src/intercept.ts +43 -3
  135. package/src/merge-streams.ts +41 -12
  136. package/src/wizard/binary-io.ts +449 -0
  137. package/src/wizard/binary.ts +177 -0
  138. package/src/wizard/caddy-process.ts +228 -0
  139. package/src/wizard/caddy.ts +97 -0
  140. package/src/wizard/cloudflare.ts +413 -0
  141. package/src/wizard/cloudflared.ts +196 -0
  142. package/src/wizard/probe.ts +161 -0
  143. package/src/wizard/proxy-snippets.ts +128 -0
  144. package/src/wizard/routes.ts +537 -0
  145. package/src/wizard/source-detect.ts +87 -0
  146. package/src/wizard/status.ts +180 -0
  147. package/src/wizard/token-store.ts +73 -0
  148. package/src/wizard/tunnel-orchestrator.ts +197 -0
  149. package/src/wizard/verify.ts +236 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * hostPath probe (GW-8): the server machine walks its OWN public URL and
3
+ * classifies what answers, so the wizard can say something sharper than
4
+ * "it didn't work" — the pairing page showing up means TLS + proxying to
5
+ * the gateway both work; the admin surface answering 200 means the proxy
6
+ * is DROPPING the forwarded headers the gateway trusts; a redirect smells
7
+ * like Cloudflare Access or a proxy rewriting to a login page.
8
+ *
9
+ * Split exactly as the ticket asks: the two `classify*Response` functions
10
+ * are pure (the check scripts can drive them), `probeHostPath` owns the
11
+ * fetch I/O — `redirect: 'manual'` so a proxy's 3xx is SEEN, never
12
+ * followed — with the URL admission reusing client-pairing's
13
+ * `normalizeServerUrl` (https anywhere; plain http only for the explicit
14
+ * private-network allowances) and a 10 s ceiling per request.
15
+ */
16
+ /** What answered on `GET <publicUrl>/`. */
17
+ export type RootClassification = 'gateway-pairing-page' | 'unexpected-401' | 'unexpected-200' | 'redirect' | 'unexpected';
18
+ /** What answered on `GET <publicUrl>/lan-gate/admin`. */
19
+ export type AdminClassification = 'gateway-admin-403' | 'admin-exposed' | 'proxy-403' | 'unexpected';
20
+ /**
21
+ * `GET /`: the ONE right answer is the gateway's pairing page — 401,
22
+ * text/html, carrying the claim-endpoint marker the page's own script
23
+ * posts to. A 200 means the URL belongs to something else entirely.
24
+ */
25
+ export declare function classifyRootResponse(status: number, contentType: string, body: string): RootClassification;
26
+ /**
27
+ * `GET /lan-gate/admin`: right is the gateway's own 403 JSON — a public
28
+ * visitor must not get in. 200 + HTML is the DANGEROUS shape: something
29
+ * answered with a page, meaning the proxy forwarded without the
30
+ * forwarded headers and the gateway's local-only wall never saw a
31
+ * proxied request (the admin page leaked). 403 + HTML smells like
32
+ * Cloudflare Access interposing its own login.
33
+ */
34
+ export declare function classifyAdminResponse(status: number, contentType: string, body: string): AdminClassification;
35
+ export interface HostPathReached {
36
+ kind: 'reached';
37
+ root: RootClassification;
38
+ admin: AdminClassification;
39
+ /** Wall-clock cost of both requests, milliseconds. */
40
+ ms: number;
41
+ }
42
+ export interface HostPathUnreachable {
43
+ kind: 'unreachable';
44
+ /** `invalid-url` / `insecure-http` — the URL never stood a chance;
45
+ * `timeout` — the 10 s ceiling fired; `network` — DNS, refused, reset. */
46
+ reason: 'invalid-url' | 'insecure-http' | 'timeout' | 'network';
47
+ detail?: string;
48
+ ms: number;
49
+ }
50
+ export type HostPathOutcome = HostPathReached | HostPathUnreachable;
51
+ export interface ProbeOptions {
52
+ fetchImpl?: typeof fetch;
53
+ /** Per-request ceiling. Default 10 000 ms (the ticket's value). */
54
+ timeoutMs?: number;
55
+ /** Clock for the `ms` measurement (tests). */
56
+ now?: () => number;
57
+ }
58
+ /**
59
+ * Probe the machine's public URL from here: both the root path and the
60
+ * admin surface, never following a redirect. The two requests run
61
+ * concurrently; the answer carries both classifications and the elapsed
62
+ * time. Every failure mode is a VALUE (`kind: 'unreachable'`), never a
63
+ * throw — the wizard routes render whichever verdict arrived.
64
+ */
65
+ export declare function probeHostPath(publicUrl: unknown, options?: ProbeOptions): Promise<HostPathOutcome>;
66
+ //# sourceMappingURL=probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../../src/wizard/probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,2CAA2C;AAC3C,MAAM,MAAM,kBAAkB,GAC1B,sBAAsB,GACtB,gBAAgB,GAChB,gBAAgB,GAChB,UAAU,GACV,YAAY,CAAA;AAEhB,yDAAyD;AACzD,MAAM,MAAM,mBAAmB,GAC3B,mBAAmB,GACnB,eAAe,GACf,WAAW,GACX,YAAY,CAAA;AAEhB;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAQ1G;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAkB5G;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,kBAAkB,CAAA;IACxB,KAAK,EAAE,mBAAmB,CAAA;IAC1B,sDAAsD;IACtD,EAAE,EAAE,MAAM,CAAA;CACX;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,aAAa,CAAA;IACnB;8EAC0E;IAC1E,MAAM,EAAE,aAAa,GAAG,eAAe,GAAG,SAAS,GAAG,SAAS,CAAA;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;CACX;AAED,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG,mBAAmB,CAAA;AAEnE,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAqBD;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CA+B5G"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Reverse-proxy configuration snippets (GW-5, 路 B) — for the user who
3
+ * ALREADY runs their own proxy, the wizard hands out copy-paste material
4
+ * instead of managing a process. Everything here is pure text generation:
5
+ * every interpolated value passes a charset gate (the domain through
6
+ * config.ts's `hostOf`, the upstream host likewise, the port in [1,
7
+ * 65535]), so a hostile form value cannot smuggle a directive into
8
+ * anyone's nginx.
9
+ *
10
+ * The upstream port is the gateway's ACTUAL port — the caller reads it
11
+ * from the running gateway (it may have retried past the configured one),
12
+ * never from config.
13
+ */
14
+ export type SnippetResult = {
15
+ ok: true;
16
+ text: string;
17
+ } | {
18
+ ok: false;
19
+ error: 'bad-domain' | 'bad-upstream-host' | 'bad-upstream-port';
20
+ };
21
+ export interface SnippetInput {
22
+ /** Public domain the proxy terminates TLS for (bare hostname). */
23
+ domain: unknown;
24
+ /** Where the gateway listens — a hostname or IP literal (e.g. the
25
+ * loopback default). */
26
+ upstreamHost: unknown;
27
+ /** The gateway's ACTUAL port. */
28
+ upstreamPort: unknown;
29
+ }
30
+ /**
31
+ * nginx: a complete server block. The WebSocket upgrade map sits outside
32
+ * the server (http-level context) — the snippet says so in a comment, the
33
+ * user pastes it into the http/server scope their setup offers. Long
34
+ * idle tunnels need the 3600s read/send timeouts, uploads the 128m
35
+ * client body cap, and streaming the buffering switch — all three are
36
+ * the shapes DSH's relay traffic actually needs.
37
+ */
38
+ export declare function nginxSnippet(input: SnippetInput): SnippetResult;
39
+ /**
40
+ * Caddy (user-operated, not the wizard-managed one): the site block is
41
+ * one `reverse_proxy` line — Caddy routes the rest itself.
42
+ */
43
+ export declare function caddySnippet(input: SnippetInput): SnippetResult;
44
+ export type StepsResult = {
45
+ ok: true;
46
+ steps: string[];
47
+ } | {
48
+ ok: false;
49
+ error: 'bad-upstream-host' | 'bad-upstream-port';
50
+ };
51
+ /**
52
+ * Lucky (Web 服务反代) three steps: listen 443, proxy to the upstream,
53
+ * flip the catch-all switch. Text only — Lucky has no config file format
54
+ * to generate.
55
+ */
56
+ export declare function luckySteps(input: {
57
+ upstreamHost: unknown;
58
+ upstreamPort: unknown;
59
+ }): StepsResult;
60
+ //# sourceMappingURL=proxy-snippets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-snippets.d.ts","sourceRoot":"","sources":["../../../src/wizard/proxy-snippets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,YAAY,GAAG,mBAAmB,GAAG,mBAAmB,CAAA;CAAE,CAAA;AAElF,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,MAAM,EAAE,OAAO,CAAA;IACf;4BACwB;IACxB,YAAY,EAAE,OAAO,CAAA;IACrB,iCAAiC;IACjC,YAAY,EAAE,OAAO,CAAA;CACtB;AAiBD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,aAAa,CAiC/D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,aAAa,CAW/D;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,mBAAmB,GAAG,mBAAmB,CAAA;CAAE,CAAA;AAEnE;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,GAAG,WAAW,CAa/F"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Wizard route skeleton (GW-4, extended by GW-5): every
3
+ * `/_dsh/zen-remote/admin/wizard/*` route the gateway wizard serves, with
4
+ * the security walls applied to ALL of them, in this order:
5
+ *
6
+ * 1. `connection.admit` — webServer-registered routes skip DSH's `/api`
7
+ * authentication, so each request asks the connection service first;
8
+ * its rejection is relayed verbatim (401/403).
9
+ * 2. The gateway marker. A request wearing `x-zen-remote-via` came
10
+ * THROUGH the gateway from a remote device. Unlike the admin status
11
+ * route there is no read-only exception: the wizard is a server-owner
12
+ * surface, and a remote device gets a flat 403 `via-gateway` — GET
13
+ * included. The marker wall runs BEFORE same-origin because a
14
+ * forwarded request presents as same-origin (the gateway rewrites
15
+ * Origin/Host onto the upstream origin); the marker is the only thing
16
+ * identifying it.
17
+ * 3. `sameOriginPost` for every POST.
18
+ *
19
+ * Real routes: `GET /wizard/status` (the sanitized projection from
20
+ * status.ts — reads only, never mutates) and, since GW-5, `POST
21
+ * /wizard/trust-proxy` (merge one detected proxy IP into the persisted
22
+ * `trustedProxies` list). Every other POST under the prefix is a 501
23
+ * placeholder; later tasks fill those in. GET /status answers from a
24
+ * per-request config resolution and the injected runtime probe; a throwing
25
+ * runtime probe degrades to "nothing configured" instead of failing the
26
+ * read.
27
+ *
28
+ * The handler is built by a factory so the tests drive it over a real
29
+ * socket with mock admit/config (same seam as the admin routes).
30
+ */
31
+ import type { IncomingMessage, ServerResponse } from 'node:http';
32
+ import type { AdminAdmission, AdminConfigSnapshot } from '../admin-routes.js';
33
+ import type { ConfigSource } from '../config.js';
34
+ import type { CaddyStartResult } from './caddy-process.js';
35
+ import type { TunnelCheckResult, TunnelSetupResult } from './tunnel-orchestrator.js';
36
+ import { type WizardRuntimeInput } from './status.js';
37
+ import type { VerificationCheckResult, VerificationStartResult } from './verify.js';
38
+ /** Prefix all wizard routes live under (one webServer prefix registration;
39
+ * the host web server matches longest-prefix-first, so these win over the
40
+ * plain admin prefix registration). */
41
+ export declare const WIZARD_ROUTE_PREFIX = "/_dsh/zen-remote/admin/wizard";
42
+ /** GET: the sanitized wizard status projection. */
43
+ export declare const WIZARD_STATUS_ROUTE = "/_dsh/zen-remote/admin/wizard/status";
44
+ /** POST `{ip}`: merge one detected reverse-proxy IP into the persisted
45
+ * trustedProxies (GW-5). */
46
+ export declare const WIZARD_TRUST_PROXY_ROUTE = "/_dsh/zen-remote/admin/wizard/trust-proxy";
47
+ /** POST `{publicUrl}`: start the final verification — mint the web pairing
48
+ * code (GW-9). GET: poll its status. */
49
+ export declare const WIZARD_VERIFY_ROUTE = "/_dsh/zen-remote/admin/wizard/verify";
50
+ /** POST `{token}`: store the Cloudflare API token 0600 on this machine;
51
+ * DELETE: stop the consumers and remove it (GW-13a). */
52
+ export declare const WIZARD_CF_TOKEN_ROUTE = "/_dsh/zen-remote/admin/wizard/cf-token";
53
+ /** POST `{token}`: store the cloudflared connector token (GW-13a). */
54
+ export declare const WIZARD_TUNNEL_TOKEN_ROUTE = "/_dsh/zen-remote/admin/wizard/tunnel-token";
55
+ /** POST: start the managed Caddy from the saved row config; POST (stop)
56
+ * terminates it; GET answers its three-state (GW-13a). */
57
+ export declare const WIZARD_CADDY_START_ROUTE = "/_dsh/zen-remote/admin/wizard/caddy/start";
58
+ export declare const WIZARD_CADDY_STOP_ROUTE = "/_dsh/zen-remote/admin/wizard/caddy/stop";
59
+ export declare const WIZARD_CADDY_STATUS_ROUTE = "/_dsh/zen-remote/admin/wizard/caddy/status";
60
+ /** POST `{domain}`: the zone/account probe the migration card re-checks;
61
+ * POST `{domain, tunnelId?}`: the full tunnel setup; POST (stop) and GET
62
+ * (three-state) mirror the Caddy pair (GW-13b). */
63
+ export declare const WIZARD_TUNNEL_CHECK_ROUTE = "/_dsh/zen-remote/admin/wizard/tunnel/check-domain";
64
+ export declare const WIZARD_TUNNEL_SETUP_ROUTE = "/_dsh/zen-remote/admin/wizard/tunnel/setup";
65
+ export declare const WIZARD_TUNNEL_STOP_ROUTE = "/_dsh/zen-remote/admin/wizard/tunnel/stop";
66
+ export declare const WIZARD_TUNNEL_STATUS_ROUTE = "/_dsh/zen-remote/admin/wizard/tunnel/status";
67
+ /** The managed Caddy's three-state. `stopped` means a start happened and
68
+ * the process has since exited (degraded); `never-started` precedes that. */
69
+ export type CaddyProcessState = 'running' | 'stopped' | 'never-started';
70
+ export interface WizardHandlerOptions {
71
+ /** The connection service's admit, injected by apply(); tests fake it. */
72
+ admit: (req: IncomingMessage) => AdminAdmission;
73
+ /** Fresh resolveConfig() per call — the ingress fields are volatile row
74
+ * fields and must not go stale. */
75
+ getConfig: () => AdminConfigSnapshot;
76
+ /** The live wizard runtime facts (token presence as booleans, process
77
+ * liveness, the gateway's actual port, probe memories, the untrusted-
78
+ * proxy detection), read PER REQUEST. MAY be async (the GW-5 wiring
79
+ * rounds-trips the gateway's own status). MUST be read-only — GET
80
+ * /status calls it, and a GET never changes state. Absent means
81
+ * "nothing configured, nothing running"; a throw is degraded to the
82
+ * same. Later tasks inject the real probe. */
83
+ getRuntime?: () => WizardRuntimeInput | Promise<WizardRuntimeInput>;
84
+ /** The trust-proxy persistence (GW-5, GW-fix-2/L2), injected by apply():
85
+ * the CURRENTLY resolved trustedProxies with its layer, and the atomic
86
+ * persister for a merged list. `write` re-checks the persisted surface
87
+ * against `expected` (the value `resolved` answered moments earlier) —
88
+ * answering false when another writer moved it in between, which the
89
+ * route turns into a 409 instead of a silent clobber. Absent — a wiring
90
+ * that predates GW-5 — and the route answers 501 like the other
91
+ * placeholders. */
92
+ trustProxy?: {
93
+ resolved: () => {
94
+ value: string;
95
+ source: ConfigSource;
96
+ };
97
+ write: (expected: string, list: string) => boolean | Promise<boolean>;
98
+ };
99
+ /** The Cloudflare token store (GW-13a), injected by apply(): 0600 files
100
+ * under the ingress directory. `deleteCfToken` is called AFTER the
101
+ * callers have stopped the consuming processes. Absent — a wiring that
102
+ * predates GW-13 — and the routes answer 501. */
103
+ tokenStore?: {
104
+ writeCfToken: (token: string) => void;
105
+ deleteCfToken: () => void;
106
+ cfTokenConfigured: () => boolean;
107
+ writeTunnelToken: (token: string) => void;
108
+ };
109
+ /** The managed Caddy (GW-13a), injected by apply(). `start` receives the
110
+ * SAVED row values and the gateway's actual port; the Cloudflare token
111
+ * comes from the token store inside the wiring (never through routes). */
112
+ caddy?: {
113
+ start: (input: {
114
+ domain: string;
115
+ httpsPort: number;
116
+ gatewayPort: number;
117
+ }) => Promise<CaddyStartResult>;
118
+ stop: () => void;
119
+ status: () => CaddyProcessState;
120
+ };
121
+ /** The Cloudflare Tunnel orchestration (GW-13b), injected by apply().
122
+ * `setup` receives the SAVED domain and (when the user adopted an
123
+ * existing tunnel) its id; the Cloudflare token comes from the token
124
+ * store inside the wiring. Absent — unwired — and the routes 501. */
125
+ tunnel?: {
126
+ checkDomain: (domain: string) => Promise<TunnelCheckResult>;
127
+ setup: (input: {
128
+ domain: string;
129
+ tunnelId?: string;
130
+ }) => Promise<TunnelSetupResult>;
131
+ stop: () => void;
132
+ status: () => CaddyProcessState;
133
+ };
134
+ /** The final-verification orchestration (GW-9), injected by apply():
135
+ * POST mints the pairing code, GET polls the session. Absent — a wiring
136
+ * that predates GW-9 — and both answer 501 like the placeholders. */
137
+ verify?: {
138
+ start: (input: {
139
+ publicUrl: unknown;
140
+ probeOnly?: boolean;
141
+ }) => Promise<VerificationStartResult>;
142
+ check: () => Promise<VerificationCheckResult>;
143
+ };
144
+ }
145
+ export type WizardHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;
146
+ /**
147
+ * Build the wizard route handler for one plugin row. The returned handler
148
+ * owns the full response lifecycle of every request under
149
+ * {@link WIZARD_ROUTE_PREFIX} and never throws.
150
+ */
151
+ export declare function createWizardHandler(options: WizardHandlerOptions): WizardHandler;
152
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/wizard/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Reverse-proxy source detection (GW-5), pure logic — the browser-facing
3
+ * half of the 「检测到反代从 {ip} 连过来,自动信任?」flow.
4
+ *
5
+ * The gateway (lib/lan-gate-server.cjs) records the last request that
6
+ * carried proxy headers from a socket it does NOT trust — almost always
7
+ * the user's own reverse proxy whose IP never made it into
8
+ * `trustedProxies` — as `lastForwardedSource: { ip, at }` in its
9
+ * `/lan-gate/status` answer. {@link detectForwardedSource} lifts that
10
+ * field out (defensively: the gateway may be old and send nothing), and
11
+ * {@link mergeProxyList} / {@link normalizeTrustedIp} build the merged
12
+ * trust list the trust-proxy route persists.
13
+ *
14
+ * Pure: no imports, no I/O — the check scripts can drive it directly.
15
+ */
16
+ /**
17
+ * The recognized shape of the gateway's `lastForwardedSource`. Everything
18
+ * else — missing field (an older gateway), junk values — reads as "no
19
+ * detection".
20
+ */
21
+ export interface ForwardedSourceInput {
22
+ ip?: unknown;
23
+ at?: unknown;
24
+ }
25
+ export interface DetectedForwardedSource {
26
+ ip: string;
27
+ at: number;
28
+ }
29
+ /**
30
+ * Extract the untrusted-proxy detection from one `/lan-gate/status` body.
31
+ * Returns null when the gateway predates the field or the field is
32
+ * malformed — a missing detection is the common case, never an error.
33
+ */
34
+ export declare function detectForwardedSource(gatewayStatus: unknown): DetectedForwardedSource | null;
35
+ /**
36
+ * One trust-list entry: an IP literal, normalized the way the gateway
37
+ * normalizes socket peers (`::ffff:` IPv4-in-IPv6 unwrapped). Refuses
38
+ * anything that is not a plausible IPv4/IPv6 literal — the value goes
39
+ * into persisted config and later into an exact string comparison
40
+ * against socket peers, where a hostname or a CIDR would silently never
41
+ * match.
42
+ */
43
+ export declare function normalizeTrustedIp(raw: unknown): string | undefined;
44
+ /** Split the comma-separated trustedProxies value into entries. */
45
+ export declare function parseProxyList(value: unknown): string[];
46
+ /**
47
+ * Append one IP to the comma-separated list, preserving order, skipping
48
+ * duplicates (the trust-proxy route answers "already trusted" instead of
49
+ * rewriting). Normalization is the caller's job — the route normalizes
50
+ * before merging.
51
+ */
52
+ export declare function mergeProxyList(current: unknown, ip: string): string;
53
+ //# sourceMappingURL=source-detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-detect.d.ts","sourceRoot":"","sources":["../../../src/wizard/source-detect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,EAAE,OAAO,CAAA;IACZ,EAAE,CAAC,EAAE,OAAO,CAAA;CACb;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,OAAO,GAAG,uBAAuB,GAAG,IAAI,CAQ5F;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAcnE;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAGvD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAInE"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Wizard status projection (GW-1): declared ingress config + observed
3
+ * runtime facts → one sanitized JSON shape the browser may see.
4
+ *
5
+ * This module is the SECURITY BOUNDARY for everything the wizard surfaces
6
+ * on `GET /_dsh/zen-remote/admin/wizard/status`. The two properties it must
7
+ * hold, regardless of what the callers hand it:
8
+ *
9
+ * 1. No secret material. The Cloudflare API token and the cloudflared
10
+ * tunnel token never enter this module — the runtime side reports only
11
+ * `*Configured` booleans (the files live 0600 under
12
+ * `<DSH_HOME>/zen-remote-ingress/`, and their contents stay there).
13
+ * 2. No file paths. The runtime side knows where binaries, token files
14
+ * and the Caddyfile live; the projection carries none of it — every
15
+ * output field is built from a whitelist below, and input objects are
16
+ * never spread through.
17
+ *
18
+ * Pure logic on purpose: zero imports, so the check scripts and the tests
19
+ * can drive it through Node's strip-only type stripping, and the wizard
20
+ * routes can call it per request without re-resolving anything.
21
+ *
22
+ * Every input is coerced defensively. The declared side normally arrives
23
+ * from `resolveConfig(...).values` (already legal), but the projection is
24
+ * also the last line of defense for the wire: an off-enum mode answers
25
+ * `'off'`, a junk port answers the 443 default, a non-string domain answers
26
+ * `''` — same transparency discipline as the config resolver, applied at
27
+ * the rendering edge.
28
+ */
29
+ /** The ingress modes `resolveConfig` can legally produce. */
30
+ export declare const WIZARD_INGRESS_MODES: readonly ["off", "caddy", "external", "cloudflared"];
31
+ export type WizardIngressMode = (typeof WIZARD_INGRESS_MODES)[number];
32
+ /**
33
+ * The declared slice of the resolved config. Loose (`unknown`) on purpose:
34
+ * the projection is what guarantees the wire shape, not the caller's typing.
35
+ */
36
+ export interface WizardDeclaredInput {
37
+ ingressMode?: unknown;
38
+ ingressDomain?: unknown;
39
+ ingressHttpsPort?: unknown;
40
+ ingressPublicUrl?: unknown;
41
+ }
42
+ /**
43
+ * One probe / verification memory the wizard back-end keeps. Only these
44
+ * three fields are recognized; everything else on the record (URLs probed,
45
+ * error details, headers) is dropped at the boundary.
46
+ */
47
+ export interface WizardRecordInput {
48
+ result?: unknown;
49
+ deviceId?: unknown;
50
+ at?: unknown;
51
+ }
52
+ /**
53
+ * The gateway's own detection of an untrusted reverse proxy (GW-5): the
54
+ * socket peer of the last request that carried proxy headers from an IP
55
+ * outside `trustedProxies` — almost certainly the user's own proxy, and
56
+ * the one IP the trust-proxy flow offers to add. Whitelist-projected like
57
+ * everything else.
58
+ */
59
+ export interface WizardForwardedSourceInput {
60
+ ip?: unknown;
61
+ at?: unknown;
62
+ }
63
+ /**
64
+ * Runtime facts the wizard back-end observes live. Everything is optional —
65
+ * a composition that has not started any ingress process simply omits the
66
+ * fields. Token presence is reported as booleans only; port as the gateway
67
+ * child's ACTUAL listening port (it may differ from the configured one).
68
+ */
69
+ export interface WizardRuntimeInput {
70
+ cfTokenConfigured?: unknown;
71
+ tunnelTokenConfigured?: unknown;
72
+ caddyRunning?: unknown;
73
+ cloudflaredRunning?: unknown;
74
+ gatewayActualPort?: unknown;
75
+ lanAddress?: unknown;
76
+ lastHostProbe?: WizardRecordInput | null;
77
+ lastExternalVerification?: WizardRecordInput | null;
78
+ lastForwardedSource?: WizardForwardedSourceInput | null;
79
+ }
80
+ /** One projected probe / verification memory, or null when there is none. */
81
+ export interface WizardStatusRecord {
82
+ result: string;
83
+ deviceId: string | null;
84
+ at: number | null;
85
+ }
86
+ /** One projected untrusted-proxy detection, or null when none is pending. */
87
+ export interface WizardStatusForwardedSource {
88
+ ip: string;
89
+ at: number;
90
+ }
91
+ /** The full sanitized status the wizard status route answers with. */
92
+ export interface WizardStatus {
93
+ ok: true;
94
+ mode: WizardIngressMode;
95
+ domain: string;
96
+ httpsPort: number;
97
+ publicUrl: string;
98
+ cfTokenConfigured: boolean;
99
+ tunnelTokenConfigured: boolean;
100
+ caddyRunning: boolean;
101
+ cloudflaredRunning: boolean;
102
+ gatewayActualPort: number | null;
103
+ lanAddress?: string;
104
+ lastHostProbe: WizardStatusRecord | null;
105
+ lastExternalVerification: WizardStatusRecord | null;
106
+ lastForwardedSource: WizardStatusForwardedSource | null;
107
+ }
108
+ /**
109
+ * Build the sanitized wizard status. Both arguments are optional so a
110
+ * composition can answer before anything is configured; the all-empty
111
+ * answer is the honest "wizard not started" state (`mode: 'off'`, no ports,
112
+ * no tokens configured, no memories).
113
+ */
114
+ export declare function projectWizardStatus(declared?: WizardDeclaredInput, runtime?: WizardRuntimeInput): WizardStatus;
115
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/wizard/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,6DAA6D;AAC7D,eAAO,MAAM,oBAAoB,sDAAuD,CAAA;AAExF,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAA;AAErE;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,EAAE,CAAC,EAAE,OAAO,CAAA;CACb;AAED;;;;;;GAMG;AACH,MAAM,WAAW,0BAA0B;IACzC,EAAE,CAAC,EAAE,OAAO,CAAA;IACZ,EAAE,CAAC,EAAE,OAAO,CAAA;CACb;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACxC,wBAAwB,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACnD,mBAAmB,CAAC,EAAE,0BAA0B,GAAG,IAAI,CAAA;CACxD;AAED,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;CACX;AAED,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,IAAI,CAAA;IACR,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,qBAAqB,EAAE,OAAO,CAAA;IAC9B,YAAY,EAAE,OAAO,CAAA;IACrB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACxC,wBAAwB,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACnD,mBAAmB,EAAE,2BAA2B,GAAG,IAAI,CAAA;CACxD;AAyCD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,mBAAwB,EAAE,OAAO,GAAE,kBAAuB,GAAG,YAAY,CAiBtH"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Wizard token storage (GW-13a): the secrets that must never enter the
3
+ * plugin row or any wire response live as 0600 files under
4
+ * `<DSH_HOME>/zen-remote-ingress/`, written through a temp-file rename so
5
+ * a crash mid-write can never leave a half token at the real path. The
6
+ * tunnel-token half lives in cloudflared.ts (it is the cloudflared
7
+ * process's own input); this module owns the shared Cloudflare API token.
8
+ *
9
+ * Reads answer presence or hand the raw string ONLY to in-process callers
10
+ * (the Caddy manager's env) — nothing here is ever logged or serialized
11
+ * into a response.
12
+ */
13
+ /** `<DSH_HOME>/zen-remote-ingress/cf-api-token`. */
14
+ export declare function cfTokenPath(dshHome: string): string;
15
+ /** Atomic 0600 write (temp sibling + rename), with a direct-write fallback
16
+ * on Windows if an existing file or antivirus scan refuses `renameSync` with
17
+ * EPERM/EACCES. */
18
+ export declare function writeCfTokenFile(dshHome: string, token: string): string;
19
+ /** The stored token, or undefined when absent/empty. Callers feed it to the
20
+ * Caddy manager's environment; it must never reach a response or a log. */
21
+ export declare function readCfTokenFile(dshHome: string): string | undefined;
22
+ /** Whether a non-empty token file exists (the projection's boolean only). */
23
+ export declare function cfTokenConfigured(dshHome: string): boolean;
24
+ /** Delete the stored token (the DELETE route; callers stop the processes
25
+ * that consume it first). */
26
+ export declare function deleteCfTokenFile(dshHome: string): void;
27
+ //# sourceMappingURL=token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../../src/wizard/token-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;mBAEmB;AACnB,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAiBvE;AAED;2EAC2E;AAC3E,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOnE;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAM1D;AAED;6BAC6B;AAC7B,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEvD"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Tunnel orchestration (GW-13b, 路 C's autopilot): one call drives the
3
+ * whole Cloudflare-side setup the design promises — zone/account lookup
4
+ * (through the ancestor-chain check), tunnel create-or-adopt, ingress
5
+ * configuration against the gateway's ACTUAL port, the proxied CNAME,
6
+ * and the cloudflared child.
7
+ *
8
+ * Rules the review and the design pin down:
9
+ *
10
+ * - **已有同名隧道**:the first setup pass answers `tunnel-conflict`
11
+ * with the same-name list; the UI lets the user pick one and re-runs
12
+ * with `tunnelId`, which adopts the existing tunnel through the token
13
+ * endpoint instead of creating anything.
14
+ * - **模式切换**:starting the tunnel stops the managed Caddy first
15
+ * (`stopCaddy`) — never two ingress processes at once.
16
+ * - The connector token travels from the Cloudflare answer into the
17
+ * 0600 token file and the child's `--token-file`; it never reaches a
18
+ * response or a log.
19
+ * - Every failure is a VALUE with a stable `reason` the UI maps to
20
+ * locale copy; CloudflareError codes map losslessly (`cf-*` → the
21
+ * orchestration's own reasons), unknown ones degrade to `api`.
22
+ */
23
+ import { type CloudflareApi, type CloudflareTunnelSummary } from './cloudflare.js';
24
+ /** The tunnel name the wizard mints and looks for. */
25
+ export declare const TUNNEL_NAME = "dsh-zen-remote";
26
+ export interface TunnelOrchestratorOptions {
27
+ dshHome: string;
28
+ /** The stored Cloudflare token (token-store); absent → `no-token`. */
29
+ readToken: () => string | undefined;
30
+ /** The gateway's ACTUAL port (the ingress service target); may probe
31
+ * asynchronously (the wiring re-reads /lan-gate/status per setup). */
32
+ gatewayPort: () => number | Promise<number>;
33
+ /** The client factory; injectable so the tests drive the orchestration
34
+ * against a scripted CloudflareApi without any HTTP. Defaults to
35
+ * createCloudflareApi over the injected fetch. */
36
+ apiFactory?: (token: string) => CloudflareApi;
37
+ cloudflared: {
38
+ start: (input: {
39
+ tunnelToken: string;
40
+ }) => Promise<{
41
+ ok: true;
42
+ } | {
43
+ ok: false;
44
+ error: string;
45
+ }>;
46
+ /** 模式切换:starting the tunnel stops the managed Caddy first. */
47
+ stopCaddy: () => void;
48
+ };
49
+ fetchImpl?: typeof fetch;
50
+ }
51
+ export interface TunnelSetupInput {
52
+ /** The public domain (bare hostname; may be a subdomain of the zone). */
53
+ domain: string;
54
+ /** Adopt this existing tunnel instead of creating one (the conflict path). */
55
+ tunnelId?: string;
56
+ }
57
+ export type TunnelSetupResult = {
58
+ ok: true;
59
+ tunnelId: string;
60
+ publicUrl: string;
61
+ tokenFile: string;
62
+ } | {
63
+ ok: false;
64
+ reason: 'no-token' | 'bad-domain' | 'zone-not-found' | 'tunnel-conflict' | 'dns-conflict' | 'auth' | 'network' | 'api' | 'spawn-failed';
65
+ detail?: string;
66
+ /** The same-name tunnels, for `tunnel-conflict`. */
67
+ existing?: CloudflareTunnelSummary[];
68
+ /** The manual-install page, for `spawn-failed`. */
69
+ installUrl?: string;
70
+ };
71
+ /**
72
+ * Drive the whole tunnel setup. Throws nothing — every outcome is a value.
73
+ */
74
+ export declare function setupTunnel(options: TunnelOrchestratorOptions, input: TunnelSetupInput): Promise<TunnelSetupResult>;
75
+ /**
76
+ * The check-domain probe the migration card's 重新检查 drives: does any
77
+ * ancestor of the domain name a zone on the token's accounts?
78
+ */
79
+ export type TunnelCheckResult = {
80
+ ok: true;
81
+ onCloudflare: boolean;
82
+ zoneId: string | null;
83
+ accountId: string | null;
84
+ } | {
85
+ ok: false;
86
+ reason: 'no-token' | 'bad-domain' | 'auth' | 'network' | 'api';
87
+ detail?: string;
88
+ };
89
+ export declare function checkDomainOnCloudflare(options: Pick<TunnelOrchestratorOptions, 'readToken' | 'fetchImpl' | 'apiFactory'>, domain: string): Promise<TunnelCheckResult>;
90
+ //# sourceMappingURL=tunnel-orchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-orchestrator.d.ts","sourceRoot":"","sources":["../../../src/wizard/tunnel-orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EAAuB,KAAK,aAAa,EAAE,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAGvG,sDAAsD;AACtD,eAAO,MAAM,WAAW,mBAAmB,CAAA;AAE3C,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,SAAS,EAAE,MAAM,MAAM,GAAG,SAAS,CAAA;IACnC;0EACsE;IACtE,WAAW,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3C;;sDAEkD;IAClD,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,aAAa,CAAA;IAC7C,WAAW,EAAE;QACX,KAAK,EAAE,CAAC,KAAK,EAAE;YAAE,WAAW,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC;YAAE,EAAE,EAAE,IAAI,CAAA;SAAE,GAAG;YAAE,EAAE,EAAE,KAAK,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAC/F,8DAA8D;QAC9D,SAAS,EAAE,MAAM,IAAI,CAAA;KACtB,CAAA;IACD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACpE;IACA,EAAE,EAAE,KAAK,CAAA;IACT,MAAM,EAAE,UAAU,GAAG,YAAY,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,cAAc,CAAA;IACvI,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oDAAoD;IACpD,QAAQ,CAAC,EAAE,uBAAuB,EAAE,CAAA;IACpC,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAcH;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,yBAAyB,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAwFzH;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACpF;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,UAAU,GAAG,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAElG,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,IAAI,CAAC,yBAAyB,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAe5K"}