unplugin-https-reverse-proxy 4.0.4 → 4.1.0-beta.10
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 +30 -6
- package/dist/{chunk-2P7SUCQ3.js → chunk-EY3HXBJ4.js} +0 -26
- package/dist/chunk-RKHJN6KY.js +53 -0
- package/dist/{chunk-5R33PUJP.js → chunk-U7KAIPZC.js} +981 -796
- package/dist/chunk-WOMR3ZLM.js +17 -0
- package/dist/esbuild.cjs +1077 -864
- package/dist/esbuild.js +4 -2
- package/dist/index.cjs +1077 -864
- package/dist/index.js +4 -2
- package/dist/nuxt.cjs +1049 -850
- package/dist/nuxt.js +6 -9
- package/dist/privilege-state.cjs +44 -0
- package/dist/privilege-state.d.cts +7 -0
- package/dist/privilege-state.d.ts +7 -0
- package/dist/privilege-state.js +10 -0
- package/dist/privileged-helper.cjs +824 -0
- package/dist/privileged-helper.d.cts +2 -0
- package/dist/privileged-helper.d.ts +2 -0
- package/dist/privileged-helper.js +797 -0
- package/dist/rollup.cjs +1077 -864
- package/dist/rollup.js +4 -2
- package/dist/rspack.cjs +1077 -864
- package/dist/rspack.js +4 -2
- package/dist/utils.cjs +0 -28
- package/dist/utils.d.cts +1 -3
- package/dist/utils.d.ts +1 -3
- package/dist/utils.js +1 -5
- package/dist/vite.cjs +1077 -864
- package/dist/vite.js +4 -2
- package/dist/webpack.cjs +1077 -864
- package/dist/webpack.js +4 -2
- package/package.json +1 -4
- package/src/caddy/caddyfile.ts +3 -0
- package/src/caddy/config.ts +195 -53
- package/src/caddy/constants.ts +30 -1
- package/src/caddy/index.ts +239 -206
- package/src/caddy/privileged.ts +124 -0
- package/src/caddy/utils.ts +0 -43
- package/src/index.ts +49 -22
- package/src/nuxt.ts +1 -6
- package/src/privilege-state.ts +23 -0
- package/src/privileged/bootstrap.ts +289 -0
- package/src/privileged/client.ts +288 -0
- package/src/privileged/entry.ts +73 -0
- package/src/privileged/hosts-file.ts +142 -0
- package/src/privileged/launch.ts +332 -0
- package/src/privileged/path-guard.ts +74 -0
- package/src/privileged/ports.ts +45 -0
- package/src/privileged/protocol.ts +111 -0
- package/src/privileged/server.ts +578 -0
- package/src/privileged/session.ts +169 -0
- package/src/privileged/validation.ts +38 -0
- package/src/privileged/windows-elevation.ts +80 -0
- package/src/privileged-helper.ts +123 -0
- package/src/utils.ts +0 -28
- package/src/host/index.ts +0 -52
package/src/caddy/index.ts
CHANGED
|
@@ -1,33 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import type { Buffer } from 'node:buffer'
|
|
2
|
+
import type { OpenedPrivilegedSession } from './privileged'
|
|
3
|
+
import { execFile } from 'node:child_process'
|
|
4
|
+
import { chmodSync, createWriteStream, existsSync, mkdirSync, unlinkSync } from 'node:fs'
|
|
5
|
+
import { readFile, rename, unlink, writeFile } from 'node:fs/promises'
|
|
4
6
|
import { platform } from 'node:os'
|
|
5
7
|
import { join } from 'node:path'
|
|
6
8
|
import process from 'node:process'
|
|
7
|
-
import { effect } from 'alien-signals'
|
|
8
9
|
import { got } from 'got-cjs'
|
|
9
10
|
import { HttpProxyAgent } from 'http-proxy-agent'
|
|
10
11
|
import { HttpsProxyAgent } from 'https-proxy-agent'
|
|
11
|
-
import
|
|
12
|
-
import { addHost, removeHost } from '../host'
|
|
13
|
-
import { chmodRecursive, consola, once } from '../utils'
|
|
12
|
+
import { consola, once } from '../utils'
|
|
14
13
|
import { genCaddyfile, validateTemplateContext } from './caddyfile'
|
|
15
14
|
import { useCaddyConfig } from './config'
|
|
16
|
-
import {
|
|
15
|
+
import { caddyAdminSocketPath, caddyFilePath, caddyPath, supportList, TEMP_DIR } from './constants'
|
|
17
16
|
import { isCaddyReloadCancellation } from './log'
|
|
17
|
+
import { HelperCaddyProcess, openPrivilegedSession } from './privileged'
|
|
18
18
|
import { formatTlsHealthFailure, waitForLocalTls } from './tls-health'
|
|
19
|
-
import {
|
|
19
|
+
import { logProgress, logProgressOver } from './utils'
|
|
20
20
|
|
|
21
21
|
export async function download(): Promise<string> {
|
|
22
22
|
if (await testCaddy())
|
|
23
23
|
return caddyPath
|
|
24
24
|
|
|
25
25
|
return new Promise<string>((resolve, reject) => {
|
|
26
|
-
// https://caddyserver.com/api/download?os=darwin&arch=amd64
|
|
27
|
-
|
|
28
26
|
if (!existsSync(TEMP_DIR)) {
|
|
29
27
|
mkdirSync(TEMP_DIR, { recursive: true })
|
|
30
|
-
chmodSync(TEMP_DIR,
|
|
28
|
+
chmodSync(TEMP_DIR, 0o700)
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
const file = createWriteStream(caddyPath)
|
|
@@ -55,8 +53,7 @@ export async function download(): Promise<string> {
|
|
|
55
53
|
const httpsAgent = httpsProxy ? new HttpsProxyAgent(httpsProxy) : undefined
|
|
56
54
|
|
|
57
55
|
const chmodCaddyOnce = once(() => {
|
|
58
|
-
|
|
59
|
-
chmodSync(caddyPath, 0o777)
|
|
56
|
+
chmodSync(caddyPath, 0o755)
|
|
60
57
|
})
|
|
61
58
|
|
|
62
59
|
got.stream(dowmloadLink, {
|
|
@@ -71,30 +68,32 @@ export async function download(): Promise<string> {
|
|
|
71
68
|
logProgressOver()
|
|
72
69
|
if (process.platform === 'win32')
|
|
73
70
|
return resolve(caddyPath)
|
|
74
|
-
|
|
75
|
-
chmodSync(caddyPath, 0o777)
|
|
71
|
+
chmodSync(caddyPath, 0o755)
|
|
76
72
|
resolve(caddyPath)
|
|
77
73
|
}).on('error', (err) => {
|
|
78
|
-
// consola.error(err)
|
|
79
74
|
reject(err)
|
|
80
75
|
})
|
|
81
76
|
})
|
|
82
77
|
}
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Checks for a usable binary by running it with no arguments: Caddy prints its
|
|
81
|
+
* usage and exits, so this neither binds a port nor needs privileges.
|
|
82
|
+
*/
|
|
83
|
+
export function testCaddy(): Promise<boolean> {
|
|
84
|
+
return new Promise((resolve) => {
|
|
86
85
|
if (!existsSync(caddyPath))
|
|
87
86
|
return resolve(false)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
chmodSync(caddyPath, 0o755)
|
|
90
|
+
}
|
|
91
|
+
catch {}
|
|
92
|
+
|
|
93
|
+
execFile(caddyPath, [], { timeout: 10_000 }, (error, stdout) => {
|
|
94
|
+
if (error && !stdout)
|
|
95
|
+
return resolve(false)
|
|
96
|
+
resolve(stdout.length > 0)
|
|
98
97
|
})
|
|
99
98
|
})
|
|
100
99
|
}
|
|
@@ -107,11 +106,44 @@ interface RunOptions {
|
|
|
107
106
|
healthCheck?: boolean
|
|
108
107
|
}
|
|
109
108
|
|
|
109
|
+
/** Where Caddy keeps the local CA; the template serves it for device installs. */
|
|
110
|
+
function caddyRootPath(): string {
|
|
111
|
+
return join(
|
|
112
|
+
process.platform === 'win32'
|
|
113
|
+
? `${process.env.AppData!}/Caddy`
|
|
114
|
+
: `${process.env.HOME!}/Library/Application Support/Caddy`,
|
|
115
|
+
'./pki/authorities/local',
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Renders the Caddyfile from the routes currently on disk and writes it.
|
|
121
|
+
*
|
|
122
|
+
* Called from inside the config lock, so two projects writing at once cannot
|
|
123
|
+
* render from their own stale view of the routes.
|
|
124
|
+
*/
|
|
125
|
+
async function renderCaddyfile(proxies: Record<string, unknown>): Promise<void> {
|
|
126
|
+
const ctx = validateTemplateContext({
|
|
127
|
+
caddy_root: caddyRootPath(),
|
|
128
|
+
admin_socket: caddyAdminSocketPath,
|
|
129
|
+
proxies: Object.values(proxies) as never,
|
|
130
|
+
})
|
|
131
|
+
const content = await genCaddyfile(ctx)
|
|
132
|
+
|
|
133
|
+
// Written through a temporary file: Caddy watches this path, and a reload
|
|
134
|
+
// triggered by a half-written file fails to parse. The starter's stderr
|
|
135
|
+
// handler exits the dev server on a parse error, so a partial read would take
|
|
136
|
+
// down an unrelated project.
|
|
137
|
+
const temp = `${caddyFilePath}.${process.pid}.tmp`
|
|
138
|
+
await writeFile(temp, content)
|
|
139
|
+
await rename(temp, caddyFilePath)
|
|
140
|
+
}
|
|
141
|
+
|
|
110
142
|
let caddyConfig: ReturnType<typeof useCaddyConfig> | undefined
|
|
111
143
|
|
|
112
144
|
function getCaddyConfig(): ReturnType<typeof useCaddyConfig> {
|
|
113
145
|
if (!caddyConfig) {
|
|
114
|
-
caddyConfig = useCaddyConfig()
|
|
146
|
+
caddyConfig = useCaddyConfig({ renderCaddyfile: renderCaddyfile as never })
|
|
115
147
|
}
|
|
116
148
|
return caddyConfig
|
|
117
149
|
}
|
|
@@ -129,32 +161,60 @@ function splitHostPort(addr: string): { host: string, port: string } {
|
|
|
129
161
|
return { host: addr.slice(0, lastColon), port: addr.slice(lastColon + 1) }
|
|
130
162
|
}
|
|
131
163
|
|
|
164
|
+
/** Injection point for tests: opens the privileged helper without sudo. */
|
|
165
|
+
export interface CaddyDependencies {
|
|
166
|
+
openSession: (runtimeDir: string, reason?: { target: string, https: boolean }) => Promise<OpenedPrivilegedSession>
|
|
167
|
+
/** Shared route config; tests inject an in-memory one. */
|
|
168
|
+
config: () => ReturnType<typeof useCaddyConfig>
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const defaultDependencies: CaddyDependencies = {
|
|
172
|
+
openSession: (runtimeDir, reason) => openPrivilegedSession(runtimeDir, {
|
|
173
|
+
log: message => consola.debug(message),
|
|
174
|
+
// Both paths are fixed here, before any socket traffic: the privileged
|
|
175
|
+
// helper must never execute something named in a request.
|
|
176
|
+
caddyBin: caddyPath,
|
|
177
|
+
caddyConfigPath: caddyFilePath,
|
|
178
|
+
caddyAdminSocket: caddyAdminSocketPath,
|
|
179
|
+
reason,
|
|
180
|
+
}),
|
|
181
|
+
config: () => getCaddyConfig(),
|
|
182
|
+
}
|
|
183
|
+
|
|
132
184
|
export class CaddyInstant {
|
|
133
185
|
private inited = false
|
|
134
186
|
private stoped = false
|
|
187
|
+
private readonly deps: CaddyDependencies
|
|
135
188
|
|
|
136
|
-
/** has locked when start */
|
|
137
|
-
private locked = false
|
|
138
189
|
private caddyfile: string | undefined
|
|
139
190
|
|
|
140
|
-
private _caddyChild:
|
|
191
|
+
private _caddyChild: HelperCaddyProcess | undefined
|
|
192
|
+
private _session: OpenedPrivilegedSession | undefined
|
|
141
193
|
|
|
142
194
|
private _source: string | undefined
|
|
143
195
|
private _target: string | undefined
|
|
144
|
-
private _stopEffects: Array<
|
|
196
|
+
private _stopEffects: Array<(() => void)> = []
|
|
197
|
+
/** Identifies this instance's routes in the shared config. */
|
|
198
|
+
private readonly proxyKey = `${process.pid}-${Math.random().toString(36).slice(2, 8)}`
|
|
145
199
|
|
|
146
|
-
constructor() {
|
|
147
|
-
|
|
148
|
-
this.inited = true
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
this.locked = this.checkLock()
|
|
200
|
+
constructor(deps: Partial<CaddyDependencies> = {}) {
|
|
201
|
+
this.deps = { ...defaultDependencies, ...deps }
|
|
152
202
|
}
|
|
153
203
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Connects to the privileged helper, starting it once if needed. It is
|
|
206
|
+
* reused across instances so a second dev server does not ask for a
|
|
207
|
+
* password again.
|
|
208
|
+
*/
|
|
209
|
+
private async session(reason?: { target: string, https: boolean }): Promise<OpenedPrivilegedSession> {
|
|
210
|
+
// A crashed helper leaves the cached client permanently disconnected; drop
|
|
211
|
+
// it so the next call starts a fresh helper instead of failing forever.
|
|
212
|
+
if (this._session && !this._session.client.connected)
|
|
213
|
+
this._session = undefined
|
|
214
|
+
|
|
215
|
+
if (!this._session)
|
|
216
|
+
this._session = await this.deps.openSession(TEMP_DIR, reason)
|
|
217
|
+
return this._session
|
|
158
218
|
}
|
|
159
219
|
|
|
160
220
|
async getCaddyfile(): Promise<string> {
|
|
@@ -168,12 +228,7 @@ export class CaddyInstant {
|
|
|
168
228
|
}
|
|
169
229
|
|
|
170
230
|
get caddyRoot(): string {
|
|
171
|
-
return
|
|
172
|
-
process.platform === 'win32'
|
|
173
|
-
? `${process.env.AppData!}/Caddy`
|
|
174
|
-
: `${process.env.HOME!}/Library/Application Support/Caddy`,
|
|
175
|
-
'./pki/authorities/local',
|
|
176
|
-
)
|
|
231
|
+
return caddyRootPath()
|
|
177
232
|
}
|
|
178
233
|
|
|
179
234
|
async writeCaddyfile(): Promise<void> {
|
|
@@ -189,43 +244,40 @@ export class CaddyInstant {
|
|
|
189
244
|
this.inited = true
|
|
190
245
|
}
|
|
191
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Hands the Caddy storage directory back to the developer. Local CA data
|
|
249
|
+
* created by an earlier root-owned run must stay readable for `tls internal`
|
|
250
|
+
* to keep serving the same certificate the browser already trusts.
|
|
251
|
+
*/
|
|
252
|
+
/**
|
|
253
|
+
* Installs Caddy's local root CA so the browser stops warning about
|
|
254
|
+
* `tls internal` certificates. Generating the CA and trusting it are separate
|
|
255
|
+
* steps: a new CA appears whenever the previous one expires or the storage is
|
|
256
|
+
* replaced, so this runs on every start instead of once.
|
|
257
|
+
*
|
|
258
|
+
* The helper owns both the binary path and the command, so nothing here can
|
|
259
|
+
* be turned into an arbitrary root execution.
|
|
260
|
+
*/
|
|
261
|
+
private async trustLocalCa(session: OpenedPrivilegedSession): Promise<void> {
|
|
262
|
+
try {
|
|
263
|
+
const result = await session.client.call('ca.trust')
|
|
264
|
+
if (result.trusted)
|
|
265
|
+
consola.debug(`Caddy local CA is trusted\n${result.output}`)
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
consola.warn(
|
|
269
|
+
`could not install the Caddy root certificate automatically: ${(error as Error).message}\n`
|
|
270
|
+
+ `Run \`${caddyPath} trust\` manually if the browser warns about the certificate.`,
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
192
275
|
private async ensureTlsReady(targetHost: string): Promise<void> {
|
|
193
276
|
const health = await waitForLocalTls(targetHost)
|
|
194
277
|
if (!health.healthy)
|
|
195
278
|
throw new Error(formatTlsHealthFailure(targetHost, health))
|
|
196
279
|
}
|
|
197
280
|
|
|
198
|
-
private async updateCaddyfile(): Promise<void> {
|
|
199
|
-
const { configJsonRef } = getCaddyConfig()
|
|
200
|
-
const proxies = Object.values(configJsonRef() || {})
|
|
201
|
-
const ctx = validateTemplateContext({
|
|
202
|
-
caddy_root: this.caddyRoot,
|
|
203
|
-
proxies,
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
this.caddyfile = await genCaddyfile(ctx)
|
|
207
|
-
await this.writeCaddyfile()
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
private async stopCaddy(): Promise<void> {
|
|
211
|
-
try {
|
|
212
|
-
await got.post('http://127.0.0.1:2019/stop', {
|
|
213
|
-
headers: {
|
|
214
|
-
Origin: 'http://localhost:2019',
|
|
215
|
-
},
|
|
216
|
-
retry: {
|
|
217
|
-
limit: 0,
|
|
218
|
-
},
|
|
219
|
-
timeout: {
|
|
220
|
-
request: 2000,
|
|
221
|
-
},
|
|
222
|
-
})
|
|
223
|
-
}
|
|
224
|
-
catch {}
|
|
225
|
-
|
|
226
|
-
this._caddyChild?.kill()
|
|
227
|
-
}
|
|
228
|
-
|
|
229
281
|
/**
|
|
230
282
|
* @param source like `127.0.0.1:8080`
|
|
231
283
|
* @param target like `test.abc.com`. And if you provide a port ,it will be ignore.
|
|
@@ -251,9 +303,11 @@ export class CaddyInstant {
|
|
|
251
303
|
|
|
252
304
|
const sourceHost = splitHostPort(source).host
|
|
253
305
|
const targetHost = splitHostPort(target).host
|
|
254
|
-
|
|
255
|
-
|
|
306
|
+
|
|
307
|
+
const session = await this.session({ target: targetHost, https })
|
|
308
|
+
await session.client.call('hosts.add', { ip: sourceHost, host: targetHost })
|
|
256
309
|
consola.success('update host success\n')
|
|
310
|
+
|
|
257
311
|
const normalizeBase = (b: string): string => {
|
|
258
312
|
let out = b || '/'
|
|
259
313
|
if (!out.startsWith('/'))
|
|
@@ -275,32 +329,18 @@ export class CaddyInstant {
|
|
|
275
329
|
},
|
|
276
330
|
]
|
|
277
331
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
332
|
+
// This also renders the Caddyfile, inside the same lock: rendering here
|
|
333
|
+
// afterwards would read the routes outside the lock and could overwrite a
|
|
334
|
+
// newer Caddyfile written by another project in between.
|
|
335
|
+
const { writeCaddyConfig } = this.deps.config()
|
|
336
|
+
await writeCaddyConfig(proxies, this.proxyKey)
|
|
282
337
|
|
|
283
|
-
|
|
284
|
-
this
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return
|
|
290
|
-
}
|
|
291
|
-
void this.updateCaddyfile()
|
|
292
|
-
}),
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
if (!existsSync(caddyLockFilePath))
|
|
296
|
-
writeFileSync(caddyLockFilePath, '')
|
|
297
|
-
|
|
298
|
-
if (!this.locked) {
|
|
299
|
-
if (await tryPort(443))
|
|
300
|
-
await kill(443)
|
|
301
|
-
if ((process.platform === 'win32' || !https) && await tryPort(80))
|
|
302
|
-
await kill(80)
|
|
303
|
-
}
|
|
338
|
+
// Ports 80/443 may still be held by a previous, uncleanly stopped run.
|
|
339
|
+
// The helper skips its own Caddy, so this never kills a proxy another
|
|
340
|
+
// project is already using.
|
|
341
|
+
await session.client.call('port.free', { port: 443 })
|
|
342
|
+
if (process.platform === 'win32' || !https)
|
|
343
|
+
await session.client.call('port.free', { port: 80 })
|
|
304
344
|
|
|
305
345
|
return new Promise<void>((resolve, reject) => {
|
|
306
346
|
let startupCheckStarted = false
|
|
@@ -311,13 +351,17 @@ export class CaddyInstant {
|
|
|
311
351
|
startupCheckStarted = true
|
|
312
352
|
|
|
313
353
|
try {
|
|
314
|
-
if (https)
|
|
354
|
+
if (https) {
|
|
355
|
+
// `caddy trust` reads the CA through the admin API, so it can only
|
|
356
|
+
// run once Caddy is serving. The helper does it as root because the
|
|
357
|
+
// system trust store is not writable by the developer account.
|
|
358
|
+
await this.trustLocalCa(session)
|
|
315
359
|
await this.ensureTlsReady(targetHost)
|
|
360
|
+
}
|
|
316
361
|
resolve()
|
|
317
362
|
}
|
|
318
363
|
catch (error) {
|
|
319
|
-
|
|
320
|
-
await this.baseCleanup(restore)
|
|
364
|
+
await this.baseCleanup(restore)
|
|
321
365
|
reject(error)
|
|
322
366
|
}
|
|
323
367
|
}
|
|
@@ -344,25 +388,6 @@ export class CaddyInstant {
|
|
|
344
388
|
|
|
345
389
|
// @ts-expect-error override
|
|
346
390
|
process.exit = async (code?: number) => {
|
|
347
|
-
const port = Number(splitHostPort(source).port)
|
|
348
|
-
if (!Number.isNaN(port) && await tryPort(port))
|
|
349
|
-
await kill(port)
|
|
350
|
-
|
|
351
|
-
// fix `Error: EPERM: operation not permitted`
|
|
352
|
-
const pwd = process.cwd()
|
|
353
|
-
const viteCacheDir = `${pwd}/node_modules/.vite`
|
|
354
|
-
if (existsSync(viteCacheDir))
|
|
355
|
-
await chmodRecursive(viteCacheDir, 0o777).catch(() => { })
|
|
356
|
-
const viteTempCacheDir = `${pwd}/node_modules/.vite-temp`
|
|
357
|
-
if (existsSync(viteTempCacheDir))
|
|
358
|
-
await chmodRecursive(viteTempCacheDir, 0o777).catch(() => { })
|
|
359
|
-
const nuxtCacheDir = `${pwd}/.nuxt`
|
|
360
|
-
if (existsSync(nuxtCacheDir))
|
|
361
|
-
await chmodRecursive(nuxtCacheDir, 0o777).catch(() => { })
|
|
362
|
-
const webpackCacheDir = `${pwd}/node_modules/.cache`
|
|
363
|
-
if (existsSync(webpackCacheDir))
|
|
364
|
-
await chmodRecursive(webpackCacheDir, 0o777).catch(() => { })
|
|
365
|
-
|
|
366
391
|
if (this.stoped)
|
|
367
392
|
return originalExit(code)
|
|
368
393
|
|
|
@@ -381,101 +406,109 @@ export class CaddyInstant {
|
|
|
381
406
|
}
|
|
382
407
|
}
|
|
383
408
|
|
|
384
|
-
|
|
385
|
-
this._caddyChild = process.platform !== 'win32'
|
|
386
|
-
? spawn('sudo', ['-E', caddyPath, 'run', '--config', caddyFilePath, '--watch'])
|
|
387
|
-
: spawn(caddyPath, ['run', '--config', caddyFilePath, '--watch'])
|
|
409
|
+
setupCleanup()
|
|
388
410
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
411
|
+
void (async () => {
|
|
412
|
+
try {
|
|
413
|
+
const child = new HelperCaddyProcess(session.client)
|
|
414
|
+
this._caddyChild = child
|
|
415
|
+
|
|
416
|
+
const started = await session.client.call('caddy.start')
|
|
417
|
+
|
|
418
|
+
child.on('exit', (code: number | null) => {
|
|
419
|
+
if (code !== null && code !== 0)
|
|
420
|
+
reject(new Error(`Caddy exited with code ${code}`))
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
// Caddy was already serving: its "serving initial" line was emitted
|
|
424
|
+
// when the first project started it, so waiting for it again would
|
|
425
|
+
// hang this instance forever.
|
|
426
|
+
if (started?.started === false) {
|
|
427
|
+
consola.info('Caddy is already running; added this route to it.')
|
|
428
|
+
void resolveWhenReady()
|
|
429
|
+
return
|
|
430
|
+
}
|
|
394
431
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
432
|
+
child.stderr.on('data', async (data: Buffer) => {
|
|
433
|
+
const lines = data.toString().split('\n').map((line: string) => line.trim())
|
|
434
|
+
for (const line of lines) {
|
|
435
|
+
// caddy log
|
|
436
|
+
// eslint-disable-next-line no-console
|
|
437
|
+
showCaddyLog && line && console.info(line)
|
|
438
|
+
if (isCaddyReloadCancellation(line)) {
|
|
439
|
+
consola.info('Caddy restarted before TLS issuance completed; retrying.\n')
|
|
440
|
+
continue
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
if (line.includes('Error:') || (line && JSON.parse(line).level === 'error')) {
|
|
444
|
+
if (line.includes('parsing caddyfile tokens')) {
|
|
445
|
+
consola.error('Caddyfile syntax error, please contact the author:\n', `${this.caddyfile}\n\n${line}\n`)
|
|
446
|
+
await this.baseCleanup()
|
|
447
|
+
process.exit()
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
consola.error(`${line}\n`)
|
|
451
|
+
return reject(line)
|
|
452
|
+
}
|
|
416
453
|
}
|
|
417
454
|
}
|
|
418
|
-
|
|
419
|
-
catch {}
|
|
420
|
-
|
|
421
|
-
if (line.includes('config file changed'))
|
|
422
|
-
consola.success('Detect added a new reverse proxy config, restarted.\n')
|
|
455
|
+
catch {}
|
|
423
456
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
})
|
|
457
|
+
if (line.includes('config file changed'))
|
|
458
|
+
consola.success('Detect added a new reverse proxy config, restarted.\n')
|
|
428
459
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
setupCleanup()
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
consola.info('Caddy is running in another process.')
|
|
460
|
+
if (line.includes('caddy proxying') || line.includes('serving initial'))
|
|
461
|
+
void resolveWhenReady()
|
|
462
|
+
}
|
|
463
|
+
})
|
|
438
464
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
}
|
|
465
|
+
child.stdout.on('data', (data: Buffer) => {
|
|
466
|
+
consola.info(data.toString())
|
|
467
|
+
void resolveWhenReady()
|
|
468
|
+
})
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
// The hosts entry was written before Caddy started; a failed start
|
|
472
|
+
// must not leave it behind while the dev server keeps running.
|
|
473
|
+
await this.baseCleanup(restore)
|
|
474
|
+
reject(error)
|
|
475
|
+
}
|
|
476
|
+
})()
|
|
451
477
|
})
|
|
452
478
|
}
|
|
453
479
|
|
|
454
480
|
async baseCleanup(restore: boolean = true): Promise<void> {
|
|
455
481
|
this._stopEffects.forEach(fn => fn())
|
|
482
|
+
this._stopEffects.length = 0
|
|
456
483
|
|
|
457
484
|
try {
|
|
458
|
-
if (restore && this._source && this._target) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
485
|
+
if (restore && this._source && this._target && this._session) {
|
|
486
|
+
const sourceHost = splitHostPort(this._source).host
|
|
487
|
+
const targetHost = splitHostPort(this._target).host
|
|
488
|
+
await this._session.client.call('hosts.remove', { ip: sourceHost, host: targetHost })
|
|
489
|
+
if (sourceHost === 'localhost')
|
|
490
|
+
await this._session.client.call('hosts.remove', { ip: '127.0.0.1', host: targetHost })
|
|
491
|
+
consola.success('Restore host success.\n')
|
|
464
492
|
}
|
|
465
493
|
}
|
|
466
494
|
catch (e) {
|
|
467
|
-
|
|
468
|
-
|
|
495
|
+
// The helper restores its own entries when the connection drops, so a
|
|
496
|
+
// closed session here is not a failed cleanup.
|
|
497
|
+
if (this._session?.client.connected === false) {
|
|
498
|
+
consola.debug('hosts entries are restored by the privileged helper on disconnect')
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
consola.error(e)
|
|
502
|
+
consola.fail('Restore host failed.\n')
|
|
503
|
+
}
|
|
469
504
|
}
|
|
470
|
-
|
|
471
|
-
|
|
505
|
+
|
|
506
|
+
// Caddy and the shared Caddyfile belong to every project using this
|
|
507
|
+
// helper. Only this project's route is removed here; the helper stops
|
|
508
|
+
// Caddy when its last client disconnects.
|
|
509
|
+
if (!this.stoped) {
|
|
472
510
|
try {
|
|
473
|
-
await
|
|
474
|
-
await unlink(caddyFilePath).catch(() => { })
|
|
475
|
-
await unlink(caddyLockFilePath).catch(() => { })
|
|
476
|
-
if (caddyConfig) {
|
|
477
|
-
await caddyConfig.cleanUp()
|
|
478
|
-
}
|
|
511
|
+
await this.deps.config().removeProxy(this.proxyKey)
|
|
479
512
|
}
|
|
480
513
|
catch (e) {
|
|
481
514
|
consola.error(e)
|