unplugin-https-reverse-proxy 4.1.0-beta.2 → 4.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-7CA3EIIX.js → chunk-H7LIW3S4.js} +345 -665
- package/dist/chunk-WOMR3ZLM.js +17 -0
- package/dist/esbuild.cjs +350 -665
- package/dist/esbuild.js +2 -1
- package/dist/index.cjs +350 -665
- package/dist/index.js +2 -1
- package/dist/nuxt.cjs +322 -659
- package/dist/nuxt.js +2 -1
- 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 +110 -13
- package/dist/privileged-helper.js +111 -14
- package/dist/rollup.cjs +350 -665
- package/dist/rollup.js +2 -1
- package/dist/rspack.cjs +350 -665
- package/dist/rspack.js +2 -1
- package/dist/vite.cjs +350 -665
- package/dist/vite.js +2 -1
- package/dist/webpack.cjs +350 -665
- package/dist/webpack.js +2 -1
- package/package.json +1 -1
- package/src/caddy/config.ts +176 -57
- package/src/caddy/index.ts +89 -131
- package/src/caddy/privileged.ts +3 -0
- package/src/index.ts +48 -7
- package/src/privilege-state.ts +23 -0
- package/src/privileged/bootstrap.ts +83 -2
- package/src/privileged/entry.ts +34 -6
- package/src/privileged/launch.ts +46 -4
- package/src/privileged/server.ts +95 -14
- package/src/privileged/session.ts +66 -14
package/dist/webpack.js
CHANGED
package/package.json
CHANGED
package/src/caddy/config.ts
CHANGED
|
@@ -1,72 +1,191 @@
|
|
|
1
1
|
import type { ProxyType } from './caddyfile'
|
|
2
|
-
import { existsSync, readFileSync,
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
import * as lockfile from 'proper-lockfile'
|
|
5
5
|
import { caddyConfigPath } from './constants'
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
/** One project's route, plus the pid that owns it. */
|
|
8
|
+
type ProxyEntry = typeof ProxyType.infer & { ownerPid?: number }
|
|
9
|
+
|
|
10
|
+
type ProxyMap = Record<string, ProxyEntry>
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A file lock held around every read-modify-write of the shared config.
|
|
14
|
+
*
|
|
15
|
+
* Several dev servers run as independent processes, so a read-then-write from
|
|
16
|
+
* two of them can interleave and drop an entry. Synchronous calls are only
|
|
17
|
+
* atomic within one event loop, which is why the lock has to be a real file
|
|
18
|
+
* lock rather than an in-process queue.
|
|
19
|
+
*/
|
|
20
|
+
interface ConfigLock {
|
|
21
|
+
acquire: () => Promise<() => void>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CaddyConfigApi {
|
|
25
|
+
configJsonRef: () => ProxyMap | undefined
|
|
26
|
+
/** Adds or replaces this project's routes, keeping everyone else's. */
|
|
27
|
+
writeCaddyConfig: (proxies: Array<typeof ProxyType.infer>, key: string) => Promise<void>
|
|
28
|
+
/** Drops one project's routes; the file goes away when nothing is left. */
|
|
29
|
+
removeProxy: (key: string) => Promise<void>
|
|
30
|
+
/** Reads the routes currently on disk, for regenerating the Caddyfile. */
|
|
31
|
+
readProxies: () => ProxyMap
|
|
10
32
|
cleanUp: () => Promise<void>
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
function setConfigContent(): void {
|
|
24
|
-
if (!existsSync(caddyConfigPath)) {
|
|
25
|
-
writeFileSync(caddyConfigPath, '{}')
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
configContentRef(readFileSync(caddyConfigPath, 'utf-8'))
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
configContentRef('')
|
|
32
|
-
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Owner pids that have exited can never clean up after themselves. */
|
|
36
|
+
function isAlive(pid: number | undefined): boolean {
|
|
37
|
+
if (pid === undefined)
|
|
38
|
+
return true
|
|
39
|
+
try {
|
|
40
|
+
process.kill(pid, 0)
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false
|
|
33
45
|
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Reads the shared config, dropping entries whose owner process is gone.
|
|
50
|
+
*
|
|
51
|
+
* A SIGKILLed dev server cannot remove its own route, so without this sweep its
|
|
52
|
+
* domain would keep resolving to a proxy with no upstream behind it.
|
|
53
|
+
*/
|
|
54
|
+
function readConfig(): ProxyMap {
|
|
55
|
+
if (!existsSync(caddyConfigPath))
|
|
56
|
+
return {}
|
|
34
57
|
|
|
35
|
-
|
|
36
|
-
|
|
58
|
+
let parsed: ProxyMap
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(readFileSync(caddyConfigPath, 'utf-8')) as ProxyMap
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// A half-written file is not worth preserving: it has no usable routes.
|
|
64
|
+
return {}
|
|
37
65
|
}
|
|
38
66
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
function writeCaddyConfig(_proxies: Array<typeof ProxyType.infer>): void {
|
|
44
|
-
const proxies = configJsonRef() || {}
|
|
45
|
-
|
|
46
|
-
_proxies.forEach((p) => {
|
|
47
|
-
let base = p.base || '/'
|
|
48
|
-
if (!base.startsWith('/'))
|
|
49
|
-
base = `/${base}`
|
|
50
|
-
if (base !== '/' && !base.endsWith('/'))
|
|
51
|
-
base = `${base}/`
|
|
52
|
-
const index = `${p.target}${p.portSuffix}${base}`
|
|
53
|
-
proxies[index] = { ...p, base }
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
const content = JSON.stringify(proxies, null, 2)
|
|
57
|
-
configContentRef(content)
|
|
58
|
-
return writeFileSync(caddyConfigPath, content)
|
|
67
|
+
const alive: ProxyMap = {}
|
|
68
|
+
for (const [key, entry] of Object.entries(parsed)) {
|
|
69
|
+
if (isAlive(entry?.ownerPid))
|
|
70
|
+
alive[key] = entry
|
|
59
71
|
}
|
|
72
|
+
return alive
|
|
73
|
+
}
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Writes through a temporary file so a concurrent reader never sees a partial
|
|
77
|
+
* document. `rename` is atomic within a filesystem, which keeps the shared
|
|
78
|
+
* config readable at all times.
|
|
79
|
+
*/
|
|
80
|
+
function writeConfig(proxies: ProxyMap): void {
|
|
81
|
+
const content = JSON.stringify(proxies, null, 2)
|
|
82
|
+
const temp = `${caddyConfigPath}.${process.pid}.tmp`
|
|
83
|
+
writeFileSync(temp, content)
|
|
84
|
+
renameSync(temp, caddyConfigPath)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Applies `mutate` under the file lock, re-reading the latest state first. */
|
|
88
|
+
async function mutateConfig(
|
|
89
|
+
lock: ConfigLock,
|
|
90
|
+
mutate: (proxies: ProxyMap) => void,
|
|
91
|
+
): Promise<ProxyMap> {
|
|
92
|
+
const release = await lock.acquire()
|
|
93
|
+
try {
|
|
94
|
+
const proxies = readConfig()
|
|
95
|
+
mutate(proxies)
|
|
96
|
+
writeConfig(proxies)
|
|
97
|
+
return proxies
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
release()
|
|
65
101
|
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface CaddyConfigOptions {
|
|
105
|
+
/** Injected in tests so they do not need the real lock. */
|
|
106
|
+
lock?: ConfigLock
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function useCaddyConfig(options: CaddyConfigOptions = {}): CaddyConfigApi {
|
|
110
|
+
const lock = options.lock ?? createFileLock()
|
|
66
111
|
|
|
67
112
|
return {
|
|
68
|
-
configJsonRef,
|
|
69
|
-
|
|
70
|
-
|
|
113
|
+
configJsonRef: () => (existsSync(caddyConfigPath) ? readConfig() : undefined),
|
|
114
|
+
|
|
115
|
+
async writeCaddyConfig(_proxies, key) {
|
|
116
|
+
await mutateConfig(lock, (proxies) => {
|
|
117
|
+
for (const p of _proxies)
|
|
118
|
+
proxies[routeKey(p, key)] = { ...p, base: normalizeBase(p.base), ownerPid: process.pid }
|
|
119
|
+
})
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
async removeProxy(key) {
|
|
123
|
+
const proxies = await mutateConfig(lock, (current) => {
|
|
124
|
+
for (const route of Object.keys(current)) {
|
|
125
|
+
if (route.endsWith(`#${key}`))
|
|
126
|
+
delete current[route]
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
if (Object.keys(proxies).length === 0)
|
|
131
|
+
removeConfigFile()
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
readProxies: () => readConfig(),
|
|
135
|
+
|
|
136
|
+
async cleanUp() {
|
|
137
|
+
removeConfigFile()
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function removeConfigFile(): void {
|
|
143
|
+
try {
|
|
144
|
+
if (existsSync(caddyConfigPath))
|
|
145
|
+
unlinkSync(caddyConfigPath)
|
|
146
|
+
}
|
|
147
|
+
catch {}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function normalizeBase(base: string | undefined): string {
|
|
151
|
+
let out = base || '/'
|
|
152
|
+
if (!out.startsWith('/'))
|
|
153
|
+
out = `/${out}`
|
|
154
|
+
if (out !== '/' && !out.endsWith('/'))
|
|
155
|
+
out = `${out}/`
|
|
156
|
+
return out
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Route key: the domain it answers on, plus the owning project.
|
|
161
|
+
*
|
|
162
|
+
* Two projects can legitimately serve the same domain on different bases, so
|
|
163
|
+
* the key has to carry the base as well as the owner.
|
|
164
|
+
*/
|
|
165
|
+
export function routeKey(proxy: typeof ProxyType.infer, ownerKey: string): string {
|
|
166
|
+
return `${proxy.target}${proxy.portSuffix}${normalizeBase(proxy.base)}#${ownerKey}`
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Builds the on-disk lock.
|
|
171
|
+
*
|
|
172
|
+
* `proper-lockfile` retries because the winner may hold the lock for a moment;
|
|
173
|
+
* `realpath: false` keeps it working when the temp directory is a symlink.
|
|
174
|
+
*/
|
|
175
|
+
function createFileLock(): ConfigLock {
|
|
176
|
+
return {
|
|
177
|
+
async acquire() {
|
|
178
|
+
if (!existsSync(caddyConfigPath))
|
|
179
|
+
writeFileSync(caddyConfigPath, '{}')
|
|
180
|
+
|
|
181
|
+
const release = await lockfile.lock(caddyConfigPath, {
|
|
182
|
+
realpath: false,
|
|
183
|
+
stale: 10_000,
|
|
184
|
+
retries: { retries: 20, minTimeout: 10, maxTimeout: 100 },
|
|
185
|
+
})
|
|
186
|
+
return () => {
|
|
187
|
+
void release().catch(() => {})
|
|
188
|
+
}
|
|
189
|
+
},
|
|
71
190
|
}
|
|
72
191
|
}
|
package/src/caddy/index.ts
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import type { Buffer } from 'node:buffer'
|
|
2
2
|
import type { OpenedPrivilegedSession } from './privileged'
|
|
3
3
|
import { execFile } from 'node:child_process'
|
|
4
|
-
import { chmodSync, createWriteStream, existsSync, mkdirSync, unlinkSync
|
|
4
|
+
import { chmodSync, createWriteStream, existsSync, mkdirSync, unlinkSync } from 'node:fs'
|
|
5
5
|
import { readFile, unlink, writeFile } from 'node:fs/promises'
|
|
6
6
|
import { platform } from 'node:os'
|
|
7
7
|
import { join } from 'node:path'
|
|
8
8
|
import process from 'node:process'
|
|
9
|
-
import { effect } from 'alien-signals'
|
|
10
9
|
import { got } from 'got-cjs'
|
|
11
10
|
import { HttpProxyAgent } from 'http-proxy-agent'
|
|
12
11
|
import { HttpsProxyAgent } from 'https-proxy-agent'
|
|
13
|
-
import * as lockfile from 'proper-lockfile'
|
|
14
12
|
import { consola, once } from '../utils'
|
|
15
13
|
import { genCaddyfile, validateTemplateContext } from './caddyfile'
|
|
16
14
|
import { useCaddyConfig } from './config'
|
|
17
|
-
import { caddyFilePath,
|
|
15
|
+
import { caddyFilePath, caddyPath, supportList, TEMP_DIR } from './constants'
|
|
18
16
|
import { isCaddyReloadCancellation } from './log'
|
|
19
17
|
import { HelperCaddyProcess, openPrivilegedSession } from './privileged'
|
|
20
18
|
import { formatTlsHealthFailure, waitForLocalTls } from './tls-health'
|
|
@@ -132,17 +130,21 @@ function splitHostPort(addr: string): { host: string, port: string } {
|
|
|
132
130
|
|
|
133
131
|
/** Injection point for tests: opens the privileged helper without sudo. */
|
|
134
132
|
export interface CaddyDependencies {
|
|
135
|
-
openSession: (runtimeDir: string) => Promise<OpenedPrivilegedSession>
|
|
133
|
+
openSession: (runtimeDir: string, reason?: { target: string, https: boolean }) => Promise<OpenedPrivilegedSession>
|
|
134
|
+
/** Shared route config; tests inject an in-memory one. */
|
|
135
|
+
config: () => ReturnType<typeof useCaddyConfig>
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
const defaultDependencies: CaddyDependencies = {
|
|
139
|
-
openSession: runtimeDir => openPrivilegedSession(runtimeDir, {
|
|
139
|
+
openSession: (runtimeDir, reason) => openPrivilegedSession(runtimeDir, {
|
|
140
140
|
log: message => consola.debug(message),
|
|
141
141
|
// Both paths are fixed here, before any socket traffic: the privileged
|
|
142
142
|
// helper must never execute something named in a request.
|
|
143
143
|
caddyBin: caddyPath,
|
|
144
144
|
caddyConfigPath: caddyFilePath,
|
|
145
|
+
reason,
|
|
145
146
|
}),
|
|
147
|
+
config: () => getCaddyConfig(),
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
export class CaddyInstant {
|
|
@@ -150,8 +152,6 @@ export class CaddyInstant {
|
|
|
150
152
|
private stoped = false
|
|
151
153
|
private readonly deps: CaddyDependencies
|
|
152
154
|
|
|
153
|
-
/** has locked when start */
|
|
154
|
-
private locked = false
|
|
155
155
|
private caddyfile: string | undefined
|
|
156
156
|
|
|
157
157
|
private _caddyChild: HelperCaddyProcess | undefined
|
|
@@ -160,21 +160,11 @@ export class CaddyInstant {
|
|
|
160
160
|
private _source: string | undefined
|
|
161
161
|
private _target: string | undefined
|
|
162
162
|
private _stopEffects: Array<(() => void)> = []
|
|
163
|
+
/** Identifies this instance's routes in the shared config. */
|
|
164
|
+
private readonly proxyKey = `${process.pid}-${Math.random().toString(36).slice(2, 8)}`
|
|
163
165
|
|
|
164
166
|
constructor(deps: Partial<CaddyDependencies> = {}) {
|
|
165
167
|
this.deps = { ...defaultDependencies, ...deps }
|
|
166
|
-
this.locked = this.checkLock()
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
checkLock(): boolean {
|
|
170
|
-
if (!existsSync(caddyLockFilePath))
|
|
171
|
-
writeFileSync(caddyLockFilePath, '')
|
|
172
|
-
try {
|
|
173
|
-
return lockfile.checkSync(caddyLockFilePath)
|
|
174
|
-
}
|
|
175
|
-
catch {
|
|
176
|
-
return false
|
|
177
|
-
}
|
|
178
168
|
}
|
|
179
169
|
|
|
180
170
|
/**
|
|
@@ -182,14 +172,14 @@ export class CaddyInstant {
|
|
|
182
172
|
* reused across instances so a second dev server does not ask for a
|
|
183
173
|
* password again.
|
|
184
174
|
*/
|
|
185
|
-
private async session(): Promise<OpenedPrivilegedSession> {
|
|
175
|
+
private async session(reason?: { target: string, https: boolean }): Promise<OpenedPrivilegedSession> {
|
|
186
176
|
// A crashed helper leaves the cached client permanently disconnected; drop
|
|
187
177
|
// it so the next call starts a fresh helper instead of failing forever.
|
|
188
178
|
if (this._session && !this._session.client.connected)
|
|
189
179
|
this._session = undefined
|
|
190
180
|
|
|
191
181
|
if (!this._session)
|
|
192
|
-
this._session = await this.deps.openSession(TEMP_DIR)
|
|
182
|
+
this._session = await this.deps.openSession(TEMP_DIR, reason)
|
|
193
183
|
return this._session
|
|
194
184
|
}
|
|
195
185
|
|
|
@@ -259,9 +249,15 @@ export class CaddyInstant {
|
|
|
259
249
|
throw new Error(formatTlsHealthFailure(targetHost, health))
|
|
260
250
|
}
|
|
261
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Regenerates the Caddyfile from the config on disk.
|
|
254
|
+
*
|
|
255
|
+
* Reading the file rather than a cached view matters here: another project
|
|
256
|
+
* may have added its route since this instance last looked.
|
|
257
|
+
*/
|
|
262
258
|
private async updateCaddyfile(): Promise<void> {
|
|
263
|
-
const {
|
|
264
|
-
const proxies = Object.values(
|
|
259
|
+
const { readProxies } = this.deps.config()
|
|
260
|
+
const proxies = Object.values(readProxies())
|
|
265
261
|
const ctx = validateTemplateContext({
|
|
266
262
|
caddy_root: this.caddyRoot,
|
|
267
263
|
proxies,
|
|
@@ -313,10 +309,10 @@ export class CaddyInstant {
|
|
|
313
309
|
if (!this.inited)
|
|
314
310
|
await this.init()
|
|
315
311
|
|
|
316
|
-
const session = await this.session()
|
|
317
|
-
|
|
318
312
|
const sourceHost = splitHostPort(source).host
|
|
319
313
|
const targetHost = splitHostPort(target).host
|
|
314
|
+
|
|
315
|
+
const session = await this.session({ target: targetHost, https })
|
|
320
316
|
await session.client.call('hosts.add', { ip: sourceHost, host: targetHost })
|
|
321
317
|
consola.success('update host success\n')
|
|
322
318
|
|
|
@@ -341,32 +337,17 @@ export class CaddyInstant {
|
|
|
341
337
|
},
|
|
342
338
|
]
|
|
343
339
|
|
|
344
|
-
const { writeCaddyConfig } =
|
|
345
|
-
writeCaddyConfig(proxies)
|
|
340
|
+
const { writeCaddyConfig } = this.deps.config()
|
|
341
|
+
await writeCaddyConfig(proxies, this.proxyKey)
|
|
346
342
|
|
|
347
343
|
await this.updateCaddyfile()
|
|
348
344
|
|
|
349
|
-
|
|
350
|
-
this
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
return
|
|
356
|
-
}
|
|
357
|
-
void this.updateCaddyfile()
|
|
358
|
-
}),
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
if (!existsSync(caddyLockFilePath))
|
|
362
|
-
writeFileSync(caddyLockFilePath, '')
|
|
363
|
-
|
|
364
|
-
if (!this.locked) {
|
|
365
|
-
// Ports 80/443 may still be held by a previous, uncleanly stopped run.
|
|
366
|
-
await session.client.call('port.free', { port: 443 })
|
|
367
|
-
if (process.platform === 'win32' || !https)
|
|
368
|
-
await session.client.call('port.free', { port: 80 })
|
|
369
|
-
}
|
|
345
|
+
// Ports 80/443 may still be held by a previous, uncleanly stopped run.
|
|
346
|
+
// The helper skips its own Caddy, so this never kills a proxy another
|
|
347
|
+
// project is already using.
|
|
348
|
+
await session.client.call('port.free', { port: 443 })
|
|
349
|
+
if (process.platform === 'win32' || !https)
|
|
350
|
+
await session.client.call('port.free', { port: 80 })
|
|
370
351
|
|
|
371
352
|
return new Promise<void>((resolve, reject) => {
|
|
372
353
|
let startupCheckStarted = false
|
|
@@ -387,8 +368,7 @@ export class CaddyInstant {
|
|
|
387
368
|
resolve()
|
|
388
369
|
}
|
|
389
370
|
catch (error) {
|
|
390
|
-
|
|
391
|
-
await this.baseCleanup(restore)
|
|
371
|
+
await this.baseCleanup(restore)
|
|
392
372
|
reject(error)
|
|
393
373
|
}
|
|
394
374
|
}
|
|
@@ -433,84 +413,65 @@ export class CaddyInstant {
|
|
|
433
413
|
}
|
|
434
414
|
}
|
|
435
415
|
|
|
436
|
-
|
|
437
|
-
lockfile.lockSync(caddyLockFilePath)
|
|
438
|
-
setupCleanup()
|
|
439
|
-
|
|
440
|
-
void (async () => {
|
|
441
|
-
try {
|
|
442
|
-
const child = new HelperCaddyProcess(session.client)
|
|
443
|
-
this._caddyChild = child
|
|
444
|
-
|
|
445
|
-
await session.client.call('caddy.start')
|
|
446
|
-
|
|
447
|
-
child.on('exit', (code: number | null) => {
|
|
448
|
-
if (code !== null && code !== 0)
|
|
449
|
-
reject(new Error(`Caddy exited with code ${code}`))
|
|
450
|
-
})
|
|
416
|
+
setupCleanup()
|
|
451
417
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
418
|
+
void (async () => {
|
|
419
|
+
try {
|
|
420
|
+
const child = new HelperCaddyProcess(session.client)
|
|
421
|
+
this._caddyChild = child
|
|
422
|
+
|
|
423
|
+
await session.client.call('caddy.start')
|
|
424
|
+
|
|
425
|
+
child.on('exit', (code: number | null) => {
|
|
426
|
+
if (code !== null && code !== 0)
|
|
427
|
+
reject(new Error(`Caddy exited with code ${code}`))
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
child.stderr.on('data', async (data: Buffer) => {
|
|
431
|
+
const lines = data.toString().split('\n').map((line: string) => line.trim())
|
|
432
|
+
for (const line of lines) {
|
|
433
|
+
// caddy log
|
|
434
|
+
// eslint-disable-next-line no-console
|
|
435
|
+
showCaddyLog && line && console.info(line)
|
|
436
|
+
if (isCaddyReloadCancellation(line)) {
|
|
437
|
+
consola.info('Caddy restarted before TLS issuance completed; retrying.\n')
|
|
438
|
+
continue
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
if (line.includes('Error:') || (line && JSON.parse(line).level === 'error')) {
|
|
442
|
+
if (line.includes('parsing caddyfile tokens')) {
|
|
443
|
+
consola.error('Caddyfile syntax error, please contact the author:\n', `${this.caddyfile}\n\n${line}\n`)
|
|
444
|
+
await this.baseCleanup()
|
|
445
|
+
process.exit()
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
consola.error(`${line}\n`)
|
|
449
|
+
return reject(line)
|
|
473
450
|
}
|
|
474
451
|
}
|
|
475
|
-
catch {}
|
|
476
|
-
|
|
477
|
-
if (line.includes('config file changed'))
|
|
478
|
-
consola.success('Detect added a new reverse proxy config, restarted.\n')
|
|
479
|
-
|
|
480
|
-
if (line.includes('caddy proxying') || line.includes('serving initial'))
|
|
481
|
-
void resolveWhenReady()
|
|
482
452
|
}
|
|
483
|
-
|
|
453
|
+
catch {}
|
|
484
454
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
void resolveWhenReady()
|
|
488
|
-
})
|
|
489
|
-
}
|
|
490
|
-
catch (error) {
|
|
491
|
-
// The hosts entry was written before Caddy started; a failed start
|
|
492
|
-
// must not leave it behind while the dev server keeps running.
|
|
493
|
-
if (!this.locked)
|
|
494
|
-
await this.baseCleanup(restore)
|
|
495
|
-
reject(error)
|
|
496
|
-
}
|
|
497
|
-
})()
|
|
498
|
-
}
|
|
499
|
-
else {
|
|
500
|
-
consola.info('Caddy is running in another process.')
|
|
455
|
+
if (line.includes('config file changed'))
|
|
456
|
+
consola.success('Detect added a new reverse proxy config, restarted.\n')
|
|
501
457
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
458
|
+
if (line.includes('caddy proxying') || line.includes('serving initial'))
|
|
459
|
+
void resolveWhenReady()
|
|
460
|
+
}
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
child.stdout.on('data', (data: Buffer) => {
|
|
464
|
+
consola.info(data.toString())
|
|
465
|
+
void resolveWhenReady()
|
|
466
|
+
})
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
// The hosts entry was written before Caddy started; a failed start
|
|
470
|
+
// must not leave it behind while the dev server keeps running.
|
|
471
|
+
await this.baseCleanup(restore)
|
|
472
|
+
reject(error)
|
|
473
|
+
}
|
|
474
|
+
})()
|
|
514
475
|
})
|
|
515
476
|
}
|
|
516
477
|
|
|
@@ -540,15 +501,12 @@ export class CaddyInstant {
|
|
|
540
501
|
}
|
|
541
502
|
}
|
|
542
503
|
|
|
543
|
-
|
|
544
|
-
|
|
504
|
+
// Caddy and the shared Caddyfile belong to every project using this
|
|
505
|
+
// helper. Only this project's route is removed here; the helper stops
|
|
506
|
+
// Caddy when its last client disconnects.
|
|
507
|
+
if (!this.stoped) {
|
|
545
508
|
try {
|
|
546
|
-
await
|
|
547
|
-
await unlink(caddyFilePath).catch(() => {})
|
|
548
|
-
await unlink(caddyLockFilePath).catch(() => {})
|
|
549
|
-
if (caddyConfig) {
|
|
550
|
-
await caddyConfig.cleanUp()
|
|
551
|
-
}
|
|
509
|
+
await this.deps.config().removeProxy(this.proxyKey)
|
|
552
510
|
}
|
|
553
511
|
catch (e) {
|
|
554
512
|
consola.error(e)
|
package/src/caddy/privileged.ts
CHANGED
|
@@ -82,6 +82,8 @@ export interface PrivilegedSessionOptions {
|
|
|
82
82
|
caddyBin: string
|
|
83
83
|
/** Caddyfile the helper may load. */
|
|
84
84
|
caddyConfigPath: string
|
|
85
|
+
/** Shown before the password prompt so the request is not a surprise. */
|
|
86
|
+
reason?: { target: string, https: boolean }
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
/** Where Caddy keeps its PKI, per platform. */
|
|
@@ -113,6 +115,7 @@ export function openPrivilegedSession(
|
|
|
113
115
|
// execute a path supplied over the socket.
|
|
114
116
|
caddyBin: options.caddyBin,
|
|
115
117
|
caddyConfigPath: options.caddyConfigPath,
|
|
118
|
+
reason: options.reason,
|
|
116
119
|
log: options.log,
|
|
117
120
|
})
|
|
118
121
|
}
|