dsh-zen-remote 2.2.9 → 2.3.2
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 +2 -2
- package/README.zh-CN.md +2 -2
- package/docs/interface.md +1 -1
- package/docs/remote-access.md +20 -0
- package/lan-gate.mjs +11 -1
- package/lib/adapters/dsh-better-sidebar/index.js +6 -2
- package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
- package/lib/adapters/dsh-better-sidebar/methods.js +3 -0
- package/lib/adapters/dsh-better-sidebar/methods.js.map +1 -1
- package/lib/admin-routes.js +33 -2
- package/lib/admin-routes.js.map +1 -1
- package/lib/client.js +3293 -6
- package/lib/config.js +70 -0
- package/lib/config.js.map +1 -1
- package/lib/index.js +232 -2
- package/lib/index.js.map +1 -1
- package/lib/intercept.js +43 -3
- package/lib/intercept.js.map +1 -1
- package/lib/lan-gate-server.cjs +58 -3
- package/lib/merge-streams.js +42 -12
- package/lib/merge-streams.js.map +1 -1
- package/lib/types/adapters/dsh-better-sidebar/index.d.ts +1 -1
- package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
- package/lib/types/adapters/dsh-better-sidebar/methods.d.ts.map +1 -1
- package/lib/types/admin-routes.d.ts +13 -0
- package/lib/types/admin-routes.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +204 -1
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/wizard/GatewayWizard.d.ts +29 -0
- package/lib/types/client/wizard/GatewayWizard.d.ts.map +1 -0
- package/lib/types/client/wizard/WizardCard.d.ts +30 -0
- package/lib/types/client/wizard/WizardCard.d.ts.map +1 -0
- package/lib/types/client/wizard/cards/MigrateCard.d.ts +15 -0
- package/lib/types/client/wizard/cards/MigrateCard.d.ts.map +1 -0
- package/lib/types/client/wizard/cards/PortForwardCard.d.ts +15 -0
- package/lib/types/client/wizard/cards/PortForwardCard.d.ts.map +1 -0
- package/lib/types/client/wizard/cards/ProxyCard.d.ts +19 -0
- package/lib/types/client/wizard/cards/ProxyCard.d.ts.map +1 -0
- package/lib/types/client/wizard/cards/TokenCard.d.ts +20 -0
- package/lib/types/client/wizard/cards/TokenCard.d.ts.map +1 -0
- package/lib/types/client/wizard/register-wizard.d.ts +51 -0
- package/lib/types/client/wizard/register-wizard.d.ts.map +1 -0
- package/lib/types/client/wizard/wizard-css.d.ts +10 -0
- package/lib/types/client/wizard/wizard-css.d.ts.map +1 -0
- package/lib/types/client-data/qr.d.ts +20 -0
- package/lib/types/client-data/qr.d.ts.map +1 -0
- package/lib/types/client-data/settings-form.d.ts +7 -2
- package/lib/types/client-data/settings-form.d.ts.map +1 -1
- package/lib/types/client-data/wizard.d.ts +247 -0
- package/lib/types/client-data/wizard.d.ts.map +1 -0
- package/lib/types/config.d.ts +27 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/intercept.d.ts.map +1 -1
- package/lib/types/merge-streams.d.ts.map +1 -1
- package/lib/types/wizard/binary-io.d.ts +111 -0
- package/lib/types/wizard/binary-io.d.ts.map +1 -0
- package/lib/types/wizard/binary.d.ts +118 -0
- package/lib/types/wizard/binary.d.ts.map +1 -0
- package/lib/types/wizard/caddy-process.d.ts +101 -0
- package/lib/types/wizard/caddy-process.d.ts.map +1 -0
- package/lib/types/wizard/caddy.d.ts +53 -0
- package/lib/types/wizard/caddy.d.ts.map +1 -0
- package/lib/types/wizard/cloudflare.d.ts +112 -0
- package/lib/types/wizard/cloudflare.d.ts.map +1 -0
- package/lib/types/wizard/cloudflared.d.ts +71 -0
- package/lib/types/wizard/cloudflared.d.ts.map +1 -0
- package/lib/types/wizard/probe.d.ts +66 -0
- package/lib/types/wizard/probe.d.ts.map +1 -0
- package/lib/types/wizard/proxy-snippets.d.ts +60 -0
- package/lib/types/wizard/proxy-snippets.d.ts.map +1 -0
- package/lib/types/wizard/routes.d.ts +152 -0
- package/lib/types/wizard/routes.d.ts.map +1 -0
- package/lib/types/wizard/source-detect.d.ts +53 -0
- package/lib/types/wizard/source-detect.d.ts.map +1 -0
- package/lib/types/wizard/status.d.ts +115 -0
- package/lib/types/wizard/status.d.ts.map +1 -0
- package/lib/types/wizard/token-store.d.ts +27 -0
- package/lib/types/wizard/token-store.d.ts.map +1 -0
- package/lib/types/wizard/tunnel-orchestrator.d.ts +90 -0
- package/lib/types/wizard/tunnel-orchestrator.d.ts.map +1 -0
- package/lib/types/wizard/verify.d.ts +97 -0
- package/lib/types/wizard/verify.d.ts.map +1 -0
- package/lib/wizard/binary-io.js +386 -0
- package/lib/wizard/binary-io.js.map +1 -0
- package/lib/wizard/binary.js +138 -0
- package/lib/wizard/binary.js.map +1 -0
- package/lib/wizard/caddy-process.js +162 -0
- package/lib/wizard/caddy-process.js.map +1 -0
- package/lib/wizard/caddy.js +80 -0
- package/lib/wizard/caddy.js.map +1 -0
- package/lib/wizard/cloudflare.js +334 -0
- package/lib/wizard/cloudflare.js.map +1 -0
- package/lib/wizard/cloudflared.js +158 -0
- package/lib/wizard/cloudflared.js.map +1 -0
- package/lib/wizard/probe.js +118 -0
- package/lib/wizard/probe.js.map +1 -0
- package/lib/wizard/proxy-snippets.js +112 -0
- package/lib/wizard/proxy-snippets.js.map +1 -0
- package/lib/wizard/routes.js +462 -0
- package/lib/wizard/routes.js.map +1 -0
- package/lib/wizard/source-detect.js +77 -0
- package/lib/wizard/source-detect.js.map +1 -0
- package/lib/wizard/status.js +94 -0
- package/lib/wizard/status.js.map +1 -0
- package/lib/wizard/token-store.js +71 -0
- package/lib/wizard/token-store.js.map +1 -0
- package/lib/wizard/tunnel-orchestrator.js +155 -0
- package/lib/wizard/tunnel-orchestrator.js.map +1 -0
- package/lib/wizard/verify.js +171 -0
- package/lib/wizard/verify.js.map +1 -0
- package/package.json +37 -37
- package/scripts/check-settings-form.mjs +5 -4
- package/scripts/check-wizard-ui.mjs +690 -0
- package/src/adapters/dsh-better-sidebar/index.ts +6 -2
- package/src/adapters/dsh-better-sidebar/methods.ts +3 -0
- package/src/admin-routes.ts +44 -2
- package/src/client/index.tsx +9 -0
- package/src/client/locales.ts +410 -2
- package/src/client/wizard/GatewayWizard.tsx +2224 -0
- package/src/client/wizard/WizardCard.tsx +81 -0
- package/src/client/wizard/cards/MigrateCard.tsx +42 -0
- package/src/client/wizard/cards/PortForwardCard.tsx +37 -0
- package/src/client/wizard/cards/ProxyCard.tsx +56 -0
- package/src/client/wizard/cards/TokenCard.tsx +81 -0
- package/src/client/wizard/register-wizard.ts +99 -0
- package/src/client/wizard/wizard-css.ts +948 -0
- package/src/client-data/qr.ts +284 -0
- package/src/client-data/settings-form.ts +12 -3
- package/src/client-data/wizard.ts +432 -0
- package/src/config.ts +87 -0
- package/src/index.ts +227 -2
- package/src/intercept.ts +43 -3
- package/src/merge-streams.ts +41 -12
- package/src/wizard/binary-io.ts +449 -0
- package/src/wizard/binary.ts +177 -0
- package/src/wizard/caddy-process.ts +228 -0
- package/src/wizard/caddy.ts +97 -0
- package/src/wizard/cloudflare.ts +413 -0
- package/src/wizard/cloudflared.ts +196 -0
- package/src/wizard/probe.ts +161 -0
- package/src/wizard/proxy-snippets.ts +128 -0
- package/src/wizard/routes.ts +537 -0
- package/src/wizard/source-detect.ts +87 -0
- package/src/wizard/status.ts +180 -0
- package/src/wizard/token-store.ts +73 -0
- package/src/wizard/tunnel-orchestrator.ts +197 -0
- package/src/wizard/verify.ts +236 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ingress binary arithmetic (GW-2): platform mapping, download-URL
|
|
3
|
+
* construction and install-path computation for the two binaries the
|
|
4
|
+
* gateway wizard manages — Caddy (custom build with the Cloudflare DNS
|
|
5
|
+
* module, for DNS-01 certificates) and cloudflared (the tunnel client).
|
|
6
|
+
*
|
|
7
|
+
* Pure by contract: URL strings, path strings and platform facts only —
|
|
8
|
+
* every touch of the filesystem or the network lives in binary-io.ts, and
|
|
9
|
+
* the routes layer reads both. `node:path`/`node:os` here are arithmetic,
|
|
10
|
+
* not I/O: the same discipline as virtual-id.ts's zero-dependency rule,
|
|
11
|
+
* relaxed one notch because path building is this module's whole job.
|
|
12
|
+
*
|
|
13
|
+
* Platform coverage mirrors what both upstream distribution points actually
|
|
14
|
+
* ship: darwin/linux/windows × amd64/arm64. macOS arm64's cloudflared asset
|
|
15
|
+
* is a `.tgz` (the only archive shape in the set) — binary-io unpacks it.
|
|
16
|
+
*/
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
/** This process's platform, mapped into the download points' naming. */
|
|
20
|
+
export function currentPlatform() {
|
|
21
|
+
if (process.platform === 'win32')
|
|
22
|
+
return { os: 'windows', arch: 'amd64' };
|
|
23
|
+
const arch = process.arch === 'arm64' ? 'arm64' : 'amd64';
|
|
24
|
+
if (process.platform === 'darwin')
|
|
25
|
+
return { os: 'darwin', arch };
|
|
26
|
+
return { os: 'linux', arch };
|
|
27
|
+
}
|
|
28
|
+
/** Caddy's custom-build API, pinned to the Cloudflare DNS module the
|
|
29
|
+
* DNS-01 challenge needs (plain Caddy cannot write the TXT record). */
|
|
30
|
+
export function caddyDownloadUrl(platform) {
|
|
31
|
+
return `https://caddyserver.com/api/download?os=${platform.os}&arch=${platform.arch}&p=github.com/caddy-dns/cloudflare`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The Caddy asset the build API answers with, PLUS its archive shape —
|
|
35
|
+
* `caddyserver.com/api/download` returns a bare Mach-O/ELF binary on unix
|
|
36
|
+
* (`Content-Disposition: attachment; filename="caddy_<os>_<arch>_custom"`,
|
|
37
|
+
* `Content-Type: application/octet-stream`) and a bare `.exe` on Windows.
|
|
38
|
+
*/
|
|
39
|
+
export function caddyDownload(platform) {
|
|
40
|
+
return { url: caddyDownloadUrl(platform), archive: platform.os === 'windows' ? 'exe' : 'none' };
|
|
41
|
+
}
|
|
42
|
+
/** cloudflared's GitHub Releases asset for one platform, plus the archive
|
|
43
|
+
* shape the I/O half must unwrap (darwin assets are `.tgz`, windows ships
|
|
44
|
+
* a bare `.exe`, linux a bare binary). */
|
|
45
|
+
export function cloudflaredDownload(platform) {
|
|
46
|
+
const base = 'https://github.com/cloudflare/cloudflared/releases/latest/download/';
|
|
47
|
+
if (platform.os === 'darwin')
|
|
48
|
+
return { url: `${base}cloudflared-darwin-${platform.arch}.tgz`, archive: 'tgz' };
|
|
49
|
+
if (platform.os === 'windows')
|
|
50
|
+
return { url: `${base}cloudflared-windows-amd64.exe`, archive: 'exe' };
|
|
51
|
+
return { url: `${base}cloudflared-linux-${platform.arch}`, archive: 'none' };
|
|
52
|
+
}
|
|
53
|
+
/** Both download URLs for one platform WITH their archive shapes, defaults
|
|
54
|
+
* to the running process's. The archive metadata — not a URL suffix — is
|
|
55
|
+
* what tells the I/O half how to unpack. */
|
|
56
|
+
export function binaryUrls(platform = currentPlatform()) {
|
|
57
|
+
const caddy = caddyDownload(platform);
|
|
58
|
+
const cloudflared = cloudflaredDownload(platform);
|
|
59
|
+
return { caddy: caddy.url, caddyArchive: caddy.archive, cloudflared: cloudflared.url, cloudflaredArchive: cloudflared.archive };
|
|
60
|
+
}
|
|
61
|
+
/** Official manual-install pages, surfaced by the UI when a download fails. */
|
|
62
|
+
export const MANUAL_INSTALL_LINKS = {
|
|
63
|
+
caddy: 'https://caddyserver.com/download',
|
|
64
|
+
cloudflared: 'https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/',
|
|
65
|
+
};
|
|
66
|
+
/** Everything ingress lives under this one directory of DSH_HOME. */
|
|
67
|
+
export const INGRESS_DIR_NAME = 'zen-remote-ingress';
|
|
68
|
+
/** `<DSH_HOME>` as the rest of the plugin resolves it (config.ts's convention). */
|
|
69
|
+
export function dshHomeDefault(env = process.env) {
|
|
70
|
+
return env.DSH_HOME ?? join(homedir(), '.dsh');
|
|
71
|
+
}
|
|
72
|
+
/** `<DSH_HOME>/zen-remote-ingress` — Caddyfile, data dir and bin/ sit here. */
|
|
73
|
+
export function ingressDir(dshHome) {
|
|
74
|
+
return join(dshHome, INGRESS_DIR_NAME);
|
|
75
|
+
}
|
|
76
|
+
/** `<DSH_HOME>/zen-remote-ingress/bin` — both binaries install here. */
|
|
77
|
+
export function binDir(dshHome) {
|
|
78
|
+
return join(ingressDir(dshHome), 'bin');
|
|
79
|
+
}
|
|
80
|
+
/** `<DSH_HOME>/zen-remote-ingress/Caddyfile` — generated, validated, then
|
|
81
|
+
* handed to `caddy run --config`. */
|
|
82
|
+
export function caddyfilePath(dshHome) {
|
|
83
|
+
return join(ingressDir(dshHome), 'Caddyfile');
|
|
84
|
+
}
|
|
85
|
+
/** `<DSH_HOME>/zen-remote-ingress/caddy-data` — certificates and the ACME
|
|
86
|
+
* account live here, kept out of the default Caddy home. */
|
|
87
|
+
export function caddyDataDir(dshHome) {
|
|
88
|
+
return join(ingressDir(dshHome), 'caddy-data');
|
|
89
|
+
}
|
|
90
|
+
/** The installed file name. Windows cannot spawn an extension-less image,
|
|
91
|
+
* so the `.exe` suffix is part of the install name there. */
|
|
92
|
+
export function binaryFileName(tool, platform = currentPlatform()) {
|
|
93
|
+
if (platform.os === 'windows')
|
|
94
|
+
return tool === 'caddy' ? 'caddy.exe' : 'cloudflared.exe';
|
|
95
|
+
return tool;
|
|
96
|
+
}
|
|
97
|
+
/** The absolute install path for one binary (where downloadBinary puts it). */
|
|
98
|
+
export function binaryInstallPath(dshHome, tool, platform = currentPlatform()) {
|
|
99
|
+
return join(binDir(dshHome), binaryFileName(tool, platform));
|
|
100
|
+
}
|
|
101
|
+
/** Prebuilt Caddy + `github.com/caddy-dns/cloudflare` release assets hosted
|
|
102
|
+
* on this repository's `caddy-cloudflare` GitHub Release (built by
|
|
103
|
+
* `.github/workflows/caddy-assets.yml`). Unlike `caddyserver.com/api/download`
|
|
104
|
+
* — which compiles on the fly and throttles downloads to ~13 KB/s — these
|
|
105
|
+
* static GitHub Release assets download in seconds and work with mainland
|
|
106
|
+
* China GitHub acceleration mirrors (`gh-proxy.com` / `ghfast.top`). */
|
|
107
|
+
export function caddyPrebuiltDownload(platform) {
|
|
108
|
+
const base = 'https://github.com/KyoMio/dsh-zen-remote/releases/download/caddy-cloudflare/';
|
|
109
|
+
if (platform.os === 'windows')
|
|
110
|
+
return { url: `${base}caddy-windows-amd64.exe`, archive: 'exe' };
|
|
111
|
+
return { url: `${base}caddy-${platform.os}-${platform.arch}.tgz`, archive: 'tgz' };
|
|
112
|
+
}
|
|
113
|
+
/** Mainland China GitHub release acceleration mirrors, tried first when the
|
|
114
|
+
* host environment is detected as mainland China (or tried as fallbacks if
|
|
115
|
+
* direct GitHub times out). */
|
|
116
|
+
export const GITHUB_MIRROR_PREFIXES = [
|
|
117
|
+
'https://gh-proxy.com/',
|
|
118
|
+
'https://ghfast.top/',
|
|
119
|
+
];
|
|
120
|
+
/** Ordered candidate downloads for one binary and platform:
|
|
121
|
+
* - When `preferCnMirror` is true, GitHub Release URLs through mainland
|
|
122
|
+
* mirrors come first, followed by direct GitHub Release URL, and finally
|
|
123
|
+
* (for Caddy) the upstream `caddyserver.com/api/download` fallback.
|
|
124
|
+
* - When `preferCnMirror` is false, direct GitHub Release comes first, then
|
|
125
|
+
* mainland mirrors, then the upstream build API fallback. */
|
|
126
|
+
export function binaryDownloadCandidates(tool, platform = currentPlatform(), preferCnMirror = false) {
|
|
127
|
+
const primary = tool === 'caddy' ? caddyPrebuiltDownload(platform) : cloudflaredDownload(platform);
|
|
128
|
+
const mirrored = GITHUB_MIRROR_PREFIXES.map((prefix) => ({
|
|
129
|
+
url: `${prefix}${primary.url}`,
|
|
130
|
+
archive: primary.archive,
|
|
131
|
+
}));
|
|
132
|
+
const ordered = preferCnMirror ? [...mirrored, primary] : [primary, ...mirrored];
|
|
133
|
+
if (tool === 'caddy') {
|
|
134
|
+
ordered.push(caddyDownload(platform));
|
|
135
|
+
}
|
|
136
|
+
return ordered;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=binary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binary.js","sourceRoot":"","sources":["../../src/wizard/binary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed-Caddy process lifecycle (GW-3). The Caddyfile's content is
|
|
3
|
+
* caddy.ts's job; this module owns everything with a heartbeat:
|
|
4
|
+
*
|
|
5
|
+
* start: ensureBinary (GW-2, cached after the first download) →
|
|
6
|
+
* generateCaddyfile (typed refusals) → write the Caddyfile as a
|
|
7
|
+
* `.part` sibling → `caddy validate --config <part> --adapter
|
|
8
|
+
* caddyfile` → only on a clean validate rename it to the real
|
|
9
|
+
* path → `caddy run --config <Caddyfile>`. The ticket's "validate
|
|
10
|
+
* before write" is honored as validate-before-PUBLISH: the final
|
|
11
|
+
* Caddyfile on disk only ever holds bytes Caddy accepted, and a
|
|
12
|
+
* failed validate leaves neither a start nor a published file.
|
|
13
|
+
*
|
|
14
|
+
* The Cloudflare API token travels in the child's ENVIRONMENT
|
|
15
|
+
* (`CF_API_TOKEN`, what the generated `{env.CF_API_TOKEN}` placeholder
|
|
16
|
+
* reads) — never in argv, which other processes on the machine can see.
|
|
17
|
+
*
|
|
18
|
+
* Idempotency: start with a live process refuses (`already-running`) —
|
|
19
|
+
* switching configuration stops first (design: 切换路时先停旧进程再启新的).
|
|
20
|
+
* stop is idempotent; an exited child clears its own slot so a later
|
|
21
|
+
* start works. The host wiring hands stop to ctx.effect, so the row's
|
|
22
|
+
* disposal kills Caddy too.
|
|
23
|
+
*
|
|
24
|
+
* The subprocess service is structural (the tests feed a recorder), and
|
|
25
|
+
* `fetch` is injectable for the ensureBinary step.
|
|
26
|
+
*/
|
|
27
|
+
import { mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
28
|
+
import { ensureBinary, BinaryDownloadError } from './binary-io.js';
|
|
29
|
+
import { caddyDataDir, caddyfilePath, ingressDir } from './binary.js';
|
|
30
|
+
import { generateCaddyfile } from './caddy.js';
|
|
31
|
+
export function createCaddyManager(options) {
|
|
32
|
+
const { subprocess, dshHome } = options;
|
|
33
|
+
const maxLogBytes = options.maxLogBytes ?? 64 * 1024;
|
|
34
|
+
let handle = null;
|
|
35
|
+
let caddyfileArg;
|
|
36
|
+
let startedOnce = false;
|
|
37
|
+
// GW-fix-2/L3: a WHITELIST, not the host's full environment — the host
|
|
38
|
+
// process carries the relay secret and DSH's own credentials in env, and
|
|
39
|
+
// a child that only needs to reach the internet and the local disk has
|
|
40
|
+
// no business inheriting them. Include Windows system/profile variables so
|
|
41
|
+
// Go's TLS root-cert store and winsock initialize cleanly on Windows.
|
|
42
|
+
const childEnv = (cfToken) => {
|
|
43
|
+
const env = {};
|
|
44
|
+
for (const key of [
|
|
45
|
+
'PATH', 'HOME', 'TMPDIR', 'LANG', 'TZ',
|
|
46
|
+
'SystemRoot', 'SYSTEMROOT', 'windir', 'WINDIR',
|
|
47
|
+
'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'PROGRAMDATA', 'TEMP', 'TMP',
|
|
48
|
+
]) {
|
|
49
|
+
if (process.env[key] !== undefined)
|
|
50
|
+
env[key] = process.env[key];
|
|
51
|
+
}
|
|
52
|
+
// The one secret this child needs, by env only (the Caddyfile reads
|
|
53
|
+
// {env.CF_API_TOKEN}) — never argv.
|
|
54
|
+
env.CF_API_TOKEN = cfToken;
|
|
55
|
+
return env;
|
|
56
|
+
};
|
|
57
|
+
const spawnChild = (argv, env) => subprocess.spawn({
|
|
58
|
+
argv,
|
|
59
|
+
cwd: dshHome,
|
|
60
|
+
env,
|
|
61
|
+
stdio: {
|
|
62
|
+
stdin: 'ignore',
|
|
63
|
+
stdout: { maxBytes: maxLogBytes },
|
|
64
|
+
stderr: { maxBytes: maxLogBytes },
|
|
65
|
+
},
|
|
66
|
+
graceMs: 3000,
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
async start(input) {
|
|
70
|
+
if (handle !== null)
|
|
71
|
+
return { ok: false, error: 'already-running' };
|
|
72
|
+
const cfToken = typeof input.cfToken === 'string' && input.cfToken.trim() !== '' ? input.cfToken : undefined;
|
|
73
|
+
if (cfToken === undefined)
|
|
74
|
+
return { ok: false, error: 'missing-token' };
|
|
75
|
+
const generated = generateCaddyfile({ ...input, storageDir: caddyDataDir(dshHome) });
|
|
76
|
+
if (!generated.ok)
|
|
77
|
+
return { ok: false, error: 'bad-config', detail: generated.error };
|
|
78
|
+
let binaryPath;
|
|
79
|
+
try {
|
|
80
|
+
binaryPath = (await ensureBinary(dshHome, 'caddy', {
|
|
81
|
+
fetchImpl: options.fetchImpl,
|
|
82
|
+
platform: options.platform,
|
|
83
|
+
onProgress: options.onProgress,
|
|
84
|
+
})).path;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof BinaryDownloadError) {
|
|
88
|
+
return { ok: false, error: 'download-failed', detail: error.message, installUrl: error.installUrl };
|
|
89
|
+
}
|
|
90
|
+
return { ok: false, error: 'download-failed', detail: String(error?.message ?? error) };
|
|
91
|
+
}
|
|
92
|
+
// Write → validate → publish: the `.part` file carries the exact
|
|
93
|
+
// bytes under validation, so what lands at the real path is exactly
|
|
94
|
+
// what Caddy accepted.
|
|
95
|
+
const finalPath = caddyfilePath(dshHome);
|
|
96
|
+
const partPath = `${finalPath}.part`;
|
|
97
|
+
try {
|
|
98
|
+
mkdirSync(ingressDir(dshHome), { recursive: true });
|
|
99
|
+
writeFileSync(partPath, generated.caddyfile, { mode: 0o600 });
|
|
100
|
+
const validate = spawnChild([binaryPath, 'validate', '--config', partPath, '--adapter', 'caddyfile'], childEnv(cfToken));
|
|
101
|
+
const outcome = await validate.done;
|
|
102
|
+
if ((outcome.exitCode ?? 1) !== 0) {
|
|
103
|
+
rmSync(partPath, { force: true });
|
|
104
|
+
return { ok: false, error: 'validate-failed' };
|
|
105
|
+
}
|
|
106
|
+
renameSync(partPath, finalPath);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
rmSync(partPath, { force: true });
|
|
110
|
+
return { ok: false, error: 'spawn-failed', detail: String(error?.message ?? error) };
|
|
111
|
+
}
|
|
112
|
+
const env = childEnv(cfToken);
|
|
113
|
+
try {
|
|
114
|
+
const child = spawnChild([binaryPath, 'run', '--config', finalPath], env);
|
|
115
|
+
handle = child;
|
|
116
|
+
caddyfileArg = finalPath;
|
|
117
|
+
startedOnce = true;
|
|
118
|
+
// An exited child clears its own slot: isRunning() goes honest and
|
|
119
|
+
// a later start() works without a manual stop.
|
|
120
|
+
void child.done.then(() => {
|
|
121
|
+
if (handle === child) {
|
|
122
|
+
handle = null;
|
|
123
|
+
caddyfileArg = undefined;
|
|
124
|
+
}
|
|
125
|
+
}).catch(() => {
|
|
126
|
+
if (handle === child) {
|
|
127
|
+
handle = null;
|
|
128
|
+
caddyfileArg = undefined;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return { ok: true, binaryPath, caddyfile: finalPath, alreadyRunning: false };
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
handle = null;
|
|
135
|
+
caddyfileArg = undefined;
|
|
136
|
+
return { ok: false, error: 'spawn-failed', detail: String(error?.message ?? error) };
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
stop() {
|
|
140
|
+
if (handle === null)
|
|
141
|
+
return;
|
|
142
|
+
const child = handle;
|
|
143
|
+
handle = null;
|
|
144
|
+
caddyfileArg = undefined;
|
|
145
|
+
try {
|
|
146
|
+
void child.terminate();
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// A dead child's terminate is a no-op or a throw — either way done.
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
isRunning: () => handle !== null,
|
|
153
|
+
runningCaddyfile: () => caddyfileArg,
|
|
154
|
+
/** Whether a start has EVER succeeded in this row's lifetime — the
|
|
155
|
+
* three-state status needs it to tell 已退出 (degraded) from 未启动. */
|
|
156
|
+
hasStarted: () => startedOnce,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/** Re-exported so the wiring (and later the routes) need one import for
|
|
160
|
+
* the whole ingress-directory layout. */
|
|
161
|
+
export { caddyDataDir, caddyfilePath, ingressDir };
|
|
162
|
+
//# sourceMappingURL=caddy-process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caddy-process.js","sourceRoot":"","sources":["../../src/wizard/caddy-process.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caddyfile generation (GW-3), pure logic — every byte the managed Caddy
|
|
3
|
+
* will run comes out of {@link generateCaddyfile}, so the validation and
|
|
4
|
+
* the injection guards live HERE and the process half (caddy-process.ts)
|
|
5
|
+
* only writes, validates and spawns what this answered.
|
|
6
|
+
*
|
|
7
|
+
* Shape (design.md 路 A), with one deliberate correction to the design
|
|
8
|
+
* sketch: the global DNS-01 option in a real Caddyfile is `acme_dns
|
|
9
|
+
* <provider> <token-placeholder>` — the sketch's bare `dns cloudflare`
|
|
10
|
+
* line is site-block `tls` syntax and is not valid as a global option.
|
|
11
|
+
* The DNS-01 challenge itself is the point: the certificate is ordered
|
|
12
|
+
* through Cloudflare's API (the token rides the `CF_API_TOKEN`
|
|
13
|
+
* environment variable, never the file), so neither 80 nor 443 needs to
|
|
14
|
+
* be reachable from outside for issuance; only the HTTPS service port
|
|
15
|
+
* must be.
|
|
16
|
+
*
|
|
17
|
+
* Injection posture: every interpolated value passes a charset gate —
|
|
18
|
+
* the domain through config.ts's `hostOf` (the exact validator the row
|
|
19
|
+
* layer applies), the storage path free of control characters, quotes and
|
|
20
|
+
* braces (Caddyfile's block delimiters), the env-var name as a legal
|
|
21
|
+
* POSIX name. Nothing else from the outside world reaches the output.
|
|
22
|
+
*/
|
|
23
|
+
import { hostOf } from '../config.js';
|
|
24
|
+
/** The default environment variable the Cloudflare token rides in on. */
|
|
25
|
+
export const CF_TOKEN_ENV_DEFAULT = 'CF_API_TOKEN';
|
|
26
|
+
/** Integers in [1, 65535] only. */
|
|
27
|
+
function portOf(value) {
|
|
28
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 65535 ? value : undefined;
|
|
29
|
+
}
|
|
30
|
+
/** Storage paths go into the Caddyfile quoted; anything that could break
|
|
31
|
+
* out of the quoting or forge a block delimiter is refused — control
|
|
32
|
+
* characters (newlines foremost), quotes, braces. */
|
|
33
|
+
function storageDirOf(value) {
|
|
34
|
+
if (typeof value !== 'string' || value === '')
|
|
35
|
+
return undefined;
|
|
36
|
+
// eslint-disable-next-line no-control-regex
|
|
37
|
+
if (/[\u0000-\u001f\u007f"{}]/.test(value))
|
|
38
|
+
return undefined;
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
/** Env names: POSIX uppercase convention, strictly. */
|
|
42
|
+
const ENV_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
|
|
43
|
+
/**
|
|
44
|
+
* Build the complete Caddyfile for the managed Caddy. Refuses (typed)
|
|
45
|
+
* instead of throwing so the caller can map each refusal to a form error.
|
|
46
|
+
*/
|
|
47
|
+
export function generateCaddyfile(input) {
|
|
48
|
+
const domain = hostOf(input.domain);
|
|
49
|
+
if (domain === undefined)
|
|
50
|
+
return { ok: false, error: 'bad-domain' };
|
|
51
|
+
const httpsPort = portOf(input.httpsPort);
|
|
52
|
+
if (httpsPort === undefined)
|
|
53
|
+
return { ok: false, error: 'bad-https-port' };
|
|
54
|
+
const gatewayPort = portOf(input.gatewayPort);
|
|
55
|
+
if (gatewayPort === undefined)
|
|
56
|
+
return { ok: false, error: 'bad-gateway-port' };
|
|
57
|
+
const storageDir = storageDirOf(input.storageDir);
|
|
58
|
+
if (storageDir === undefined)
|
|
59
|
+
return { ok: false, error: 'bad-storage' };
|
|
60
|
+
const tokenEnv = typeof input.cfTokenEnvName === 'string' && ENV_NAME_PATTERN.test(input.cfTokenEnvName)
|
|
61
|
+
? input.cfTokenEnvName
|
|
62
|
+
: CF_TOKEN_ENV_DEFAULT;
|
|
63
|
+
if (input.cfTokenEnvName !== undefined && input.cfTokenEnvName !== '' && tokenEnv !== input.cfTokenEnvName) {
|
|
64
|
+
return { ok: false, error: 'bad-token-env' };
|
|
65
|
+
}
|
|
66
|
+
const caddyfile = [
|
|
67
|
+
'{',
|
|
68
|
+
`\tacme_dns cloudflare {env.${tokenEnv}}`,
|
|
69
|
+
`\tstorage file_system "${storageDir}"`,
|
|
70
|
+
'\tadmin off',
|
|
71
|
+
'}',
|
|
72
|
+
'',
|
|
73
|
+
`https://${domain}:${httpsPort} {`,
|
|
74
|
+
`\treverse_proxy 127.0.0.1:${gatewayPort}`,
|
|
75
|
+
'}',
|
|
76
|
+
'',
|
|
77
|
+
].join('\n');
|
|
78
|
+
return { ok: true, caddyfile, domain, httpsPort, gatewayPort };
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=caddy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caddy.js","sourceRoot":"","sources":["../../src/wizard/caddy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAErC,yEAAyE;AACzE,MAAM,CAAC,MAAM,oBAAoB,GAAG,cAAc,CAAA;AAoBlD,mCAAmC;AACnC,SAAS,MAAM,CAAC,KAAc;IAC5B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACjH,CAAC;AAED;;qDAEqD;AACrD,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,SAAS,CAAA;IAC/D,4CAA4C;IAC5C,IAAI,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAC5D,OAAO,KAAK,CAAA;AACd,CAAC;AAED,uDAAuD;AACvD,MAAM,gBAAgB,GAAG,oBAAoB,CAAA;AAE7C;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAqB;IACrD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACnC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,CAAA;IACnE,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IACzC,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAA;IAC1E,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;IAC7C,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAA;IAC9E,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;IACjD,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,CAAA;IACxE,MAAM,QAAQ,GAAG,OAAO,KAAK,CAAC,cAAc,KAAK,QAAQ,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC;QACtG,CAAC,CAAC,KAAK,CAAC,cAAc;QACtB,CAAC,CAAC,oBAAoB,CAAA;IACxB,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS,IAAI,KAAK,CAAC,cAAc,KAAK,EAAE,IAAI,QAAQ,KAAK,KAAK,CAAC,cAAc,EAAE,CAAC;QAC3G,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAA;IAC9C,CAAC;IACD,MAAM,SAAS,GAAG;QAChB,GAAG;QACH,8BAA8B,QAAQ,GAAG;QACzC,0BAA0B,UAAU,GAAG;QACvC,aAAa;QACb,GAAG;QACH,EAAE;QACF,WAAW,MAAM,IAAI,SAAS,IAAI;QAClC,6BAA6B,WAAW,EAAE;QAC1C,GAAG;QACH,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACZ,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,CAAA;AAChE,CAAC"}
|