vite-plugin-caddy-multiple-tls 1.6.0 → 1.6.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.
- package/README.md +34 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +334 -108
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -136,6 +136,40 @@ const config = defineConfig({
|
|
|
136
136
|
export default config;
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
+
If your Caddy Admin API enforces a specific allowed origin that differs from `caddyApiUrl`, set `caddyAdminOrigin`.
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
// vite.config.js
|
|
143
|
+
import { defineConfig } from 'vite';
|
|
144
|
+
import caddyTls from 'vite-plugin-caddy-multiple-tls';
|
|
145
|
+
|
|
146
|
+
const config = defineConfig({
|
|
147
|
+
plugins: [
|
|
148
|
+
caddyTls({
|
|
149
|
+
caddyApiUrl: 'http://127.0.0.1:2019',
|
|
150
|
+
caddyAdminOrigin: 'http://localhost:2019',
|
|
151
|
+
})
|
|
152
|
+
]
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export default config;
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Troubleshooting
|
|
159
|
+
|
|
160
|
+
### `client is not allowed to access from origin ''`
|
|
161
|
+
|
|
162
|
+
This error comes from Caddy Admin API origin enforcement, not from Caddy being down.
|
|
163
|
+
|
|
164
|
+
- Check `caddyApiUrl` points to the correct Admin API endpoint.
|
|
165
|
+
- If Admin API expects a different origin than the API URL host, set `caddyAdminOrigin`.
|
|
166
|
+
- Verify behavior directly:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
curl -i http://127.0.0.1:2019/config/
|
|
170
|
+
curl -i -H 'Origin: http://127.0.0.1:2019' http://127.0.0.1:2019/config/
|
|
171
|
+
```
|
|
172
|
+
|
|
139
173
|
> [!IMPORTANT]
|
|
140
174
|
> **Hosts file limitation:** If you use a custom domain, you must **manually** add each generated subdomain to your `/etc/hosts` file (e.g., `127.0.0.1 repo.branch.local.example.test`). System hosts files **do not support wildcards** (e.g., `*.local.example.test`), so you lose the benefit of automatic domain resolution that `localhost` provides.
|
|
141
175
|
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ interface ViteCaddyTlsPluginOptions {
|
|
|
18
18
|
serverName?: string;
|
|
19
19
|
/** Override the Caddy Admin API base URL (default: http://localhost:2019) */
|
|
20
20
|
caddyApiUrl?: string;
|
|
21
|
+
/** Override the Origin header used for Caddy Admin API requests (defaults to caddyApiUrl origin) */
|
|
22
|
+
caddyAdminOrigin?: string;
|
|
21
23
|
/** Use Caddy's internal CA for the provided domains (defaults to true when baseDomain or domain is set) */
|
|
22
24
|
internalTls?: boolean;
|
|
23
25
|
/**
|
|
@@ -39,6 +41,6 @@ type LoopbackDomain = 'localtest.me' | 'lvh.me' | 'nip.io';
|
|
|
39
41
|
* ```
|
|
40
42
|
* @returns {Plugin} - a Vite plugin
|
|
41
43
|
*/
|
|
42
|
-
declare function viteCaddyTlsPlugin({ domain, baseDomain, loopbackDomain, repo, branch, instanceLabel, cors, serverName, caddyApiUrl, internalTls, upstreamHostHeader, }?: ViteCaddyTlsPluginOptions): PluginOption;
|
|
44
|
+
declare function viteCaddyTlsPlugin({ domain, baseDomain, loopbackDomain, repo, branch, instanceLabel, cors, serverName, caddyApiUrl, caddyAdminOrigin, internalTls, upstreamHostHeader, }?: ViteCaddyTlsPluginOptions): PluginOption;
|
|
43
45
|
|
|
44
46
|
export { type ViteCaddyTlsPluginOptions, viteCaddyTlsPlugin as default };
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,18 @@ import os from "os";
|
|
|
11
11
|
import path from "path";
|
|
12
12
|
var DEFAULT_SERVER_NAME = "srv0";
|
|
13
13
|
var DEFAULT_CADDY_API_URL = "http://localhost:2019";
|
|
14
|
+
var CADDY_ADMIN_ORIGIN_POLICY_ERROR_MESSAGE = "Caddy Admin API rejected request due to origin policy. Check caddyApiUrl and admin origin settings.";
|
|
15
|
+
var CONNECTIVITY_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
16
|
+
"ECONNREFUSED",
|
|
17
|
+
"ECONNRESET",
|
|
18
|
+
"EHOSTUNREACH",
|
|
19
|
+
"ENETUNREACH",
|
|
20
|
+
"ENOTFOUND",
|
|
21
|
+
"ETIMEDOUT",
|
|
22
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
23
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
24
|
+
"UND_ERR_SOCKET"
|
|
25
|
+
]);
|
|
14
26
|
function isRecord(value) {
|
|
15
27
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
16
28
|
}
|
|
@@ -28,6 +40,90 @@ function isTlsPolicyOverlapError(text) {
|
|
|
28
40
|
function getApiUrl(apiUrl) {
|
|
29
41
|
return apiUrl ?? DEFAULT_CADDY_API_URL;
|
|
30
42
|
}
|
|
43
|
+
function toError(error) {
|
|
44
|
+
if (error instanceof Error) return error;
|
|
45
|
+
return new Error(String(error));
|
|
46
|
+
}
|
|
47
|
+
function isOriginPolicyError(status, text) {
|
|
48
|
+
if (status !== 403) return false;
|
|
49
|
+
const normalizedText = text.toLowerCase();
|
|
50
|
+
return normalizedText.includes("origin") && normalizedText.includes("not allowed");
|
|
51
|
+
}
|
|
52
|
+
function buildCaddyRequestError(message, status, text) {
|
|
53
|
+
if (isOriginPolicyError(status, text)) {
|
|
54
|
+
return new Error(CADDY_ADMIN_ORIGIN_POLICY_ERROR_MESSAGE);
|
|
55
|
+
}
|
|
56
|
+
const normalizedText = text.trim();
|
|
57
|
+
if (!normalizedText) {
|
|
58
|
+
return new Error(`${message}: HTTP ${status}`);
|
|
59
|
+
}
|
|
60
|
+
return new Error(`${message}: ${normalizedText}`);
|
|
61
|
+
}
|
|
62
|
+
function getErrorCode(error) {
|
|
63
|
+
if (!error || typeof error !== "object") return void 0;
|
|
64
|
+
if ("code" in error && typeof error.code === "string") {
|
|
65
|
+
return error.code;
|
|
66
|
+
}
|
|
67
|
+
if ("cause" in error) {
|
|
68
|
+
const cause = error.cause;
|
|
69
|
+
if (cause && typeof cause === "object") {
|
|
70
|
+
if ("code" in cause && typeof cause.code === "string") {
|
|
71
|
+
return cause.code;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
function isConnectivityError(error) {
|
|
78
|
+
const code = getErrorCode(error);
|
|
79
|
+
return Boolean(code) && CONNECTIVITY_ERROR_CODES.has(code);
|
|
80
|
+
}
|
|
81
|
+
function getAdminOrigin(apiUrl, adminOrigin) {
|
|
82
|
+
const originSource = adminOrigin ?? getApiUrl(apiUrl);
|
|
83
|
+
try {
|
|
84
|
+
return new URL(originSource).origin;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return new URL(getApiUrl(apiUrl)).origin;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function caddyFetch(input, init, apiUrl, adminOrigin) {
|
|
90
|
+
const headers = new Headers(init?.headers);
|
|
91
|
+
headers.set("Origin", getAdminOrigin(apiUrl, adminOrigin));
|
|
92
|
+
return fetch(input, {
|
|
93
|
+
...init,
|
|
94
|
+
headers
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async function checkCaddyAdminStatus(apiUrl, adminOrigin) {
|
|
98
|
+
try {
|
|
99
|
+
const res = await caddyFetch(`${getApiUrl(apiUrl)}/config/`, void 0, apiUrl, adminOrigin);
|
|
100
|
+
if (res.ok) {
|
|
101
|
+
return { status: "running" };
|
|
102
|
+
}
|
|
103
|
+
const text = await res.text();
|
|
104
|
+
return {
|
|
105
|
+
status: "api-error",
|
|
106
|
+
error: buildCaddyRequestError("Failed to read Caddy config", res.status, text)
|
|
107
|
+
};
|
|
108
|
+
} catch (e) {
|
|
109
|
+
const error = toError(e);
|
|
110
|
+
if (isConnectivityError(error)) {
|
|
111
|
+
return {
|
|
112
|
+
status: "connectivity-error",
|
|
113
|
+
error
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
status: "api-error",
|
|
118
|
+
error
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function assertCaddyResponse(res, message) {
|
|
123
|
+
if (res.ok) return;
|
|
124
|
+
const text = await res.text();
|
|
125
|
+
throw buildCaddyRequestError(message, res.status, text);
|
|
126
|
+
}
|
|
31
127
|
function getLockPath(apiUrl) {
|
|
32
128
|
const key = createHash("sha1").update(getApiUrl(apiUrl)).digest("hex").slice(0, 12);
|
|
33
129
|
return path.join(os.tmpdir(), `vite-plugin-caddy-multiple-tls-${key}.lock`);
|
|
@@ -70,30 +166,36 @@ function validateCaddyIsInstalled() {
|
|
|
70
166
|
return false;
|
|
71
167
|
}
|
|
72
168
|
}
|
|
73
|
-
async function
|
|
74
|
-
try {
|
|
75
|
-
const res = await fetch(`${getApiUrl(apiUrl)}/config/`);
|
|
76
|
-
return res.ok;
|
|
77
|
-
} catch (e) {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async function startCaddy(apiUrl) {
|
|
169
|
+
async function startCaddy(apiUrl, adminOrigin) {
|
|
82
170
|
try {
|
|
83
171
|
execSync("caddy start", { stdio: "ignore" });
|
|
84
172
|
} catch (e) {
|
|
85
173
|
}
|
|
86
174
|
for (let i = 0; i < 10; i++) {
|
|
87
|
-
|
|
175
|
+
const status = await checkCaddyAdminStatus(apiUrl, adminOrigin);
|
|
176
|
+
if (status.status === "running") return true;
|
|
177
|
+
if (status.status === "api-error") {
|
|
178
|
+
throw status.error;
|
|
179
|
+
}
|
|
88
180
|
await sleep(500);
|
|
89
181
|
}
|
|
90
182
|
return false;
|
|
91
183
|
}
|
|
92
|
-
async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl) {
|
|
184
|
+
async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
|
|
93
185
|
const resolvedApiUrl = getApiUrl(apiUrl);
|
|
94
186
|
const serverUrl = `${resolvedApiUrl}/config/apps/http/servers/${serverName}`;
|
|
95
|
-
const res = await
|
|
187
|
+
const res = await caddyFetch(serverUrl, void 0, apiUrl, adminOrigin);
|
|
96
188
|
if (res.ok) return;
|
|
189
|
+
if (res.status === 403) {
|
|
190
|
+
const text = await res.text();
|
|
191
|
+
if (isOriginPolicyError(res.status, text)) {
|
|
192
|
+
throw buildCaddyRequestError(
|
|
193
|
+
"Failed to initialize Caddy base configuration",
|
|
194
|
+
res.status,
|
|
195
|
+
text
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
97
199
|
const baseConfig = {
|
|
98
200
|
listen: [":443"],
|
|
99
201
|
routes: []
|
|
@@ -103,11 +205,13 @@ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl) {
|
|
|
103
205
|
[serverName]: baseConfig
|
|
104
206
|
}
|
|
105
207
|
};
|
|
106
|
-
const configRes = await
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
208
|
+
const configRes = await caddyFetch(
|
|
209
|
+
`${resolvedApiUrl}/config/`,
|
|
210
|
+
void 0,
|
|
211
|
+
apiUrl,
|
|
212
|
+
adminOrigin
|
|
213
|
+
);
|
|
214
|
+
await assertCaddyResponse(configRes, "Failed to read Caddy config");
|
|
111
215
|
const configText = await configRes.text();
|
|
112
216
|
const config = parseConfig(configText);
|
|
113
217
|
if (config === void 0) {
|
|
@@ -115,18 +219,27 @@ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl) {
|
|
|
115
219
|
}
|
|
116
220
|
const isEmptyConfig = configText.trim() === "" || config === null || isRecord(config) && Object.keys(config).length === 0;
|
|
117
221
|
if (isEmptyConfig) {
|
|
118
|
-
const loadRes = await
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
222
|
+
const loadRes = await caddyFetch(
|
|
223
|
+
`${resolvedApiUrl}/load`,
|
|
224
|
+
{
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: { "Content-Type": "application/json" },
|
|
227
|
+
body: JSON.stringify({
|
|
228
|
+
apps: {
|
|
229
|
+
http: httpAppConfig
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
},
|
|
233
|
+
apiUrl,
|
|
234
|
+
adminOrigin
|
|
235
|
+
);
|
|
127
236
|
if (!loadRes.ok) {
|
|
128
237
|
const text = await loadRes.text();
|
|
129
|
-
throw
|
|
238
|
+
throw buildCaddyRequestError(
|
|
239
|
+
"Failed to initialize Caddy base configuration",
|
|
240
|
+
loadRes.status,
|
|
241
|
+
text
|
|
242
|
+
);
|
|
130
243
|
}
|
|
131
244
|
return;
|
|
132
245
|
}
|
|
@@ -137,82 +250,136 @@ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl) {
|
|
|
137
250
|
let hasHttp = isRecord(http);
|
|
138
251
|
let hasServers = isRecord(servers);
|
|
139
252
|
if (!hasApps) {
|
|
140
|
-
const createAppsRes = await
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
253
|
+
const createAppsRes = await caddyFetch(
|
|
254
|
+
`${resolvedApiUrl}/config/apps`,
|
|
255
|
+
{
|
|
256
|
+
method: "PUT",
|
|
257
|
+
headers: { "Content-Type": "application/json" },
|
|
258
|
+
body: JSON.stringify({})
|
|
259
|
+
},
|
|
260
|
+
apiUrl,
|
|
261
|
+
adminOrigin
|
|
262
|
+
);
|
|
145
263
|
if (!createAppsRes.ok && createAppsRes.status !== 409) {
|
|
146
264
|
const text = await createAppsRes.text();
|
|
147
|
-
throw
|
|
265
|
+
throw buildCaddyRequestError(
|
|
266
|
+
"Failed to initialize Caddy base configuration",
|
|
267
|
+
createAppsRes.status,
|
|
268
|
+
text
|
|
269
|
+
);
|
|
148
270
|
}
|
|
149
271
|
hasApps = true;
|
|
150
272
|
}
|
|
151
273
|
if (!hasHttp) {
|
|
152
|
-
const createHttpRes = await
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
274
|
+
const createHttpRes = await caddyFetch(
|
|
275
|
+
`${resolvedApiUrl}/config/apps/http`,
|
|
276
|
+
{
|
|
277
|
+
method: "PUT",
|
|
278
|
+
headers: { "Content-Type": "application/json" },
|
|
279
|
+
body: JSON.stringify({ servers: {} })
|
|
280
|
+
},
|
|
281
|
+
apiUrl,
|
|
282
|
+
adminOrigin
|
|
283
|
+
);
|
|
157
284
|
if (!createHttpRes.ok && createHttpRes.status !== 409) {
|
|
158
285
|
const text = await createHttpRes.text();
|
|
159
|
-
throw
|
|
286
|
+
throw buildCaddyRequestError(
|
|
287
|
+
"Failed to initialize Caddy base configuration",
|
|
288
|
+
createHttpRes.status,
|
|
289
|
+
text
|
|
290
|
+
);
|
|
160
291
|
}
|
|
161
292
|
hasHttp = true;
|
|
162
293
|
hasServers = true;
|
|
163
294
|
}
|
|
164
295
|
if (!hasServers) {
|
|
165
|
-
const createServersRes = await
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
296
|
+
const createServersRes = await caddyFetch(
|
|
297
|
+
`${resolvedApiUrl}/config/apps/http/servers`,
|
|
298
|
+
{
|
|
299
|
+
method: "PUT",
|
|
300
|
+
headers: { "Content-Type": "application/json" },
|
|
301
|
+
body: JSON.stringify({})
|
|
302
|
+
},
|
|
303
|
+
apiUrl,
|
|
304
|
+
adminOrigin
|
|
305
|
+
);
|
|
170
306
|
if (!createServersRes.ok && createServersRes.status !== 409) {
|
|
171
307
|
const text = await createServersRes.text();
|
|
172
|
-
throw
|
|
308
|
+
throw buildCaddyRequestError(
|
|
309
|
+
"Failed to initialize Caddy base configuration",
|
|
310
|
+
createServersRes.status,
|
|
311
|
+
text
|
|
312
|
+
);
|
|
173
313
|
}
|
|
174
314
|
}
|
|
175
|
-
const createServerRes = await
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
315
|
+
const createServerRes = await caddyFetch(
|
|
316
|
+
serverUrl,
|
|
317
|
+
{
|
|
318
|
+
method: "PUT",
|
|
319
|
+
headers: { "Content-Type": "application/json" },
|
|
320
|
+
body: JSON.stringify(baseConfig)
|
|
321
|
+
},
|
|
322
|
+
apiUrl,
|
|
323
|
+
adminOrigin
|
|
324
|
+
);
|
|
180
325
|
if (!createServerRes.ok && createServerRes.status !== 409) {
|
|
181
326
|
const text = await createServerRes.text();
|
|
182
|
-
throw
|
|
327
|
+
throw buildCaddyRequestError(
|
|
328
|
+
"Failed to initialize Caddy base configuration",
|
|
329
|
+
createServerRes.status,
|
|
330
|
+
text
|
|
331
|
+
);
|
|
183
332
|
}
|
|
184
333
|
}
|
|
185
|
-
async function ensureTlsAutomation(apiUrl) {
|
|
334
|
+
async function ensureTlsAutomation(apiUrl, adminOrigin) {
|
|
186
335
|
const resolvedApiUrl = getApiUrl(apiUrl);
|
|
187
336
|
const policiesUrl = `${resolvedApiUrl}/config/apps/tls/automation/policies`;
|
|
188
|
-
const policiesRes = await
|
|
337
|
+
const policiesRes = await caddyFetch(policiesUrl, void 0, apiUrl, adminOrigin);
|
|
189
338
|
if (policiesRes.ok) return;
|
|
190
339
|
const policiesText = await policiesRes.text();
|
|
191
340
|
if (policiesRes.status !== 404 && !policiesText.includes("invalid traversal path")) {
|
|
192
|
-
throw
|
|
193
|
-
|
|
341
|
+
throw buildCaddyRequestError(
|
|
342
|
+
"Failed to initialize Caddy TLS automation",
|
|
343
|
+
policiesRes.status,
|
|
344
|
+
policiesText
|
|
194
345
|
);
|
|
195
346
|
}
|
|
196
|
-
const automationRes = await
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
347
|
+
const automationRes = await caddyFetch(
|
|
348
|
+
`${resolvedApiUrl}/config/apps/tls/automation`,
|
|
349
|
+
{
|
|
350
|
+
method: "PUT",
|
|
351
|
+
headers: { "Content-Type": "application/json" },
|
|
352
|
+
body: JSON.stringify({ policies: [] })
|
|
353
|
+
},
|
|
354
|
+
apiUrl,
|
|
355
|
+
adminOrigin
|
|
356
|
+
);
|
|
201
357
|
if (automationRes.ok || automationRes.status === 409) return;
|
|
202
358
|
const automationText = await automationRes.text();
|
|
203
359
|
if (!automationText.includes("invalid traversal path")) {
|
|
204
|
-
throw
|
|
205
|
-
|
|
360
|
+
throw buildCaddyRequestError(
|
|
361
|
+
"Failed to initialize Caddy TLS automation",
|
|
362
|
+
automationRes.status,
|
|
363
|
+
automationText
|
|
206
364
|
);
|
|
207
365
|
}
|
|
208
|
-
const tlsRes = await
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
366
|
+
const tlsRes = await caddyFetch(
|
|
367
|
+
`${resolvedApiUrl}/config/apps/tls`,
|
|
368
|
+
{
|
|
369
|
+
method: "PUT",
|
|
370
|
+
headers: { "Content-Type": "application/json" },
|
|
371
|
+
body: JSON.stringify({ automation: { policies: [] } })
|
|
372
|
+
},
|
|
373
|
+
apiUrl,
|
|
374
|
+
adminOrigin
|
|
375
|
+
);
|
|
213
376
|
if (!tlsRes.ok && tlsRes.status !== 409) {
|
|
214
377
|
const text = await tlsRes.text();
|
|
215
|
-
throw
|
|
378
|
+
throw buildCaddyRequestError(
|
|
379
|
+
"Failed to initialize Caddy TLS automation",
|
|
380
|
+
tlsRes.status,
|
|
381
|
+
text
|
|
382
|
+
);
|
|
216
383
|
}
|
|
217
384
|
}
|
|
218
385
|
function formatDialAddress(host, port) {
|
|
@@ -241,10 +408,13 @@ function intersectsDomains(targetDomains, routeDomains) {
|
|
|
241
408
|
const targetSet = new Set(targetDomains);
|
|
242
409
|
return routeDomains.some((domain) => targetSet.has(domain));
|
|
243
410
|
}
|
|
244
|
-
async function cleanupStaleRoutesForDomains(domains, currentRouteId, serverName = DEFAULT_SERVER_NAME, apiUrl) {
|
|
411
|
+
async function cleanupStaleRoutesForDomains(domains, currentRouteId, serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
|
|
245
412
|
if (domains.length === 0) return;
|
|
246
|
-
const res = await
|
|
247
|
-
`${getApiUrl(apiUrl)}/config/apps/http/servers/${serverName}/routes
|
|
413
|
+
const res = await caddyFetch(
|
|
414
|
+
`${getApiUrl(apiUrl)}/config/apps/http/servers/${serverName}/routes`,
|
|
415
|
+
void 0,
|
|
416
|
+
apiUrl,
|
|
417
|
+
adminOrigin
|
|
248
418
|
);
|
|
249
419
|
if (!res.ok) return;
|
|
250
420
|
const text = await res.text();
|
|
@@ -258,10 +428,10 @@ async function cleanupStaleRoutesForDomains(domains, currentRouteId, serverName
|
|
|
258
428
|
if (id === currentRouteId) continue;
|
|
259
429
|
const routeDomains = extractMatchedHosts(route);
|
|
260
430
|
if (!intersectsDomains(domains, routeDomains)) continue;
|
|
261
|
-
await removeRoute(id, apiUrl);
|
|
431
|
+
await removeRoute(id, apiUrl, adminOrigin);
|
|
262
432
|
}
|
|
263
433
|
}
|
|
264
|
-
async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAME, upstreamHost = "127.0.0.1", upstreamHostHeader, apiUrl) {
|
|
434
|
+
async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAME, upstreamHost = "127.0.0.1", upstreamHostHeader, apiUrl, adminOrigin) {
|
|
265
435
|
const handlers = [];
|
|
266
436
|
if (cors) {
|
|
267
437
|
handlers.push({
|
|
@@ -311,22 +481,24 @@ async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAM
|
|
|
311
481
|
],
|
|
312
482
|
terminal: true
|
|
313
483
|
};
|
|
314
|
-
const res = await
|
|
484
|
+
const res = await caddyFetch(
|
|
315
485
|
`${getApiUrl(apiUrl)}/config/apps/http/servers/${serverName}/routes`,
|
|
316
486
|
{
|
|
317
487
|
method: "POST",
|
|
318
488
|
// Append to routes list
|
|
319
489
|
headers: { "Content-Type": "application/json" },
|
|
320
490
|
body: JSON.stringify(route)
|
|
321
|
-
}
|
|
491
|
+
},
|
|
492
|
+
apiUrl,
|
|
493
|
+
adminOrigin
|
|
322
494
|
);
|
|
323
495
|
if (!res.ok) {
|
|
324
496
|
const text = await res.text();
|
|
325
|
-
throw
|
|
497
|
+
throw buildCaddyRequestError("Failed to add route", res.status, text);
|
|
326
498
|
}
|
|
327
499
|
}
|
|
328
|
-
async function addTlsPolicy(id, domains, apiUrl) {
|
|
329
|
-
await ensureTlsAutomation(apiUrl);
|
|
500
|
+
async function addTlsPolicy(id, domains, apiUrl, adminOrigin) {
|
|
501
|
+
await ensureTlsAutomation(apiUrl, adminOrigin);
|
|
330
502
|
const policy = {
|
|
331
503
|
"@id": id,
|
|
332
504
|
subjects: domains,
|
|
@@ -336,49 +508,76 @@ async function addTlsPolicy(id, domains, apiUrl) {
|
|
|
336
508
|
}
|
|
337
509
|
]
|
|
338
510
|
};
|
|
339
|
-
const res = await
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
511
|
+
const res = await caddyFetch(
|
|
512
|
+
`${getApiUrl(apiUrl)}/config/apps/tls/automation/policies`,
|
|
513
|
+
{
|
|
514
|
+
method: "POST",
|
|
515
|
+
headers: { "Content-Type": "application/json" },
|
|
516
|
+
body: JSON.stringify(policy)
|
|
517
|
+
},
|
|
518
|
+
apiUrl,
|
|
519
|
+
adminOrigin
|
|
520
|
+
);
|
|
344
521
|
if (!res.ok) {
|
|
345
522
|
const text = await res.text();
|
|
346
523
|
if (isTlsPolicyOverlapError(text)) {
|
|
347
524
|
return;
|
|
348
525
|
}
|
|
349
|
-
throw
|
|
526
|
+
throw buildCaddyRequestError("Failed to add TLS policy", res.status, text);
|
|
350
527
|
}
|
|
351
528
|
}
|
|
352
|
-
async function removeRoute(id, apiUrl) {
|
|
353
|
-
const res = await
|
|
354
|
-
|
|
355
|
-
|
|
529
|
+
async function removeRoute(id, apiUrl, adminOrigin) {
|
|
530
|
+
const res = await caddyFetch(
|
|
531
|
+
`${getApiUrl(apiUrl)}/id/${id}`,
|
|
532
|
+
{
|
|
533
|
+
method: "DELETE"
|
|
534
|
+
},
|
|
535
|
+
apiUrl,
|
|
536
|
+
adminOrigin
|
|
537
|
+
);
|
|
356
538
|
if (!res.ok && res.status !== 404) {
|
|
357
|
-
|
|
539
|
+
const text = await res.text();
|
|
540
|
+
const error = buildCaddyRequestError(`Failed to remove route ${id}`, res.status, text);
|
|
541
|
+
console.error(error.message);
|
|
358
542
|
return false;
|
|
359
543
|
}
|
|
360
544
|
return true;
|
|
361
545
|
}
|
|
362
|
-
async function removeTlsPolicy(id, apiUrl) {
|
|
363
|
-
const res = await
|
|
364
|
-
|
|
365
|
-
|
|
546
|
+
async function removeTlsPolicy(id, apiUrl, adminOrigin) {
|
|
547
|
+
const res = await caddyFetch(
|
|
548
|
+
`${getApiUrl(apiUrl)}/id/${id}`,
|
|
549
|
+
{
|
|
550
|
+
method: "DELETE"
|
|
551
|
+
},
|
|
552
|
+
apiUrl,
|
|
553
|
+
adminOrigin
|
|
554
|
+
);
|
|
366
555
|
if (!res.ok && res.status !== 404) {
|
|
367
|
-
|
|
556
|
+
const text = await res.text();
|
|
557
|
+
const error = buildCaddyRequestError(
|
|
558
|
+
`Failed to remove TLS policy ${id}`,
|
|
559
|
+
res.status,
|
|
560
|
+
text
|
|
561
|
+
);
|
|
562
|
+
console.error(error.message);
|
|
368
563
|
return false;
|
|
369
564
|
}
|
|
370
565
|
return true;
|
|
371
566
|
}
|
|
372
|
-
async function ensureCaddyReady(serverName = DEFAULT_SERVER_NAME, apiUrl) {
|
|
567
|
+
async function ensureCaddyReady(serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
|
|
373
568
|
await withApiLock(apiUrl, async () => {
|
|
374
|
-
|
|
375
|
-
if (
|
|
376
|
-
|
|
569
|
+
const status = await checkCaddyAdminStatus(apiUrl, adminOrigin);
|
|
570
|
+
if (status.status === "api-error") {
|
|
571
|
+
throw status.error;
|
|
572
|
+
}
|
|
573
|
+
let running = status.status === "running";
|
|
574
|
+
if (status.status === "connectivity-error") {
|
|
575
|
+
running = await startCaddy(apiUrl, adminOrigin);
|
|
377
576
|
}
|
|
378
577
|
if (!running) {
|
|
379
578
|
throw new Error("Failed to start Caddy server.");
|
|
380
579
|
}
|
|
381
|
-
await ensureBaseConfig(serverName, apiUrl);
|
|
580
|
+
await ensureBaseConfig(serverName, apiUrl, adminOrigin);
|
|
382
581
|
});
|
|
383
582
|
}
|
|
384
583
|
|
|
@@ -474,6 +673,15 @@ function normalizeCaddyApiUrl(url) {
|
|
|
474
673
|
if (!trimmed) return null;
|
|
475
674
|
return trimmed.replace(/\/+$/g, "");
|
|
476
675
|
}
|
|
676
|
+
function normalizeCaddyAdminOrigin(origin) {
|
|
677
|
+
const trimmed = origin.trim();
|
|
678
|
+
if (!trimmed) return null;
|
|
679
|
+
try {
|
|
680
|
+
return new URL(trimmed).origin;
|
|
681
|
+
} catch (e) {
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
477
685
|
function resolveDomains(options) {
|
|
478
686
|
if (options.domain) {
|
|
479
687
|
return normalizeDomains(options.domain);
|
|
@@ -492,16 +700,24 @@ function viteCaddyTlsPlugin({
|
|
|
492
700
|
cors,
|
|
493
701
|
serverName,
|
|
494
702
|
caddyApiUrl,
|
|
703
|
+
caddyAdminOrigin,
|
|
495
704
|
internalTls,
|
|
496
705
|
upstreamHostHeader
|
|
497
706
|
} = {}) {
|
|
498
707
|
const normalizedApiUrl = caddyApiUrl ? normalizeCaddyApiUrl(caddyApiUrl) : null;
|
|
499
708
|
const pluginCaddyApiUrl = normalizedApiUrl ?? DEFAULT_CADDY_API_URL;
|
|
709
|
+
const normalizedAdminOrigin = caddyAdminOrigin ? normalizeCaddyAdminOrigin(caddyAdminOrigin) : null;
|
|
710
|
+
const pluginCaddyAdminOrigin = normalizedAdminOrigin ?? pluginCaddyApiUrl;
|
|
500
711
|
if (caddyApiUrl !== void 0 && !normalizedApiUrl) {
|
|
501
712
|
console.warn(
|
|
502
713
|
`caddyApiUrl is empty after trimming. Falling back to ${DEFAULT_CADDY_API_URL}.`
|
|
503
714
|
);
|
|
504
715
|
}
|
|
716
|
+
if (caddyAdminOrigin !== void 0 && !normalizedAdminOrigin) {
|
|
717
|
+
console.warn(
|
|
718
|
+
`caddyAdminOrigin is invalid. Falling back to ${pluginCaddyApiUrl}.`
|
|
719
|
+
);
|
|
720
|
+
}
|
|
505
721
|
function getInstanceKey(domains, configRoot) {
|
|
506
722
|
const keyMaterial = JSON.stringify({
|
|
507
723
|
domains: [...domains].sort(),
|
|
@@ -655,11 +871,14 @@ function viteCaddyTlsPlugin({
|
|
|
655
871
|
cleanupStarted = true;
|
|
656
872
|
if (tlsPolicyId) {
|
|
657
873
|
await removeWithRetry(
|
|
658
|
-
() => removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl),
|
|
874
|
+
() => removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin),
|
|
659
875
|
"TLS policy"
|
|
660
876
|
);
|
|
661
877
|
}
|
|
662
|
-
await removeWithRetry(
|
|
878
|
+
await removeWithRetry(
|
|
879
|
+
() => removeRoute(routeId, pluginCaddyApiUrl, pluginCaddyAdminOrigin),
|
|
880
|
+
"route"
|
|
881
|
+
);
|
|
663
882
|
}
|
|
664
883
|
function onServerClose() {
|
|
665
884
|
void cleanupRoute();
|
|
@@ -704,7 +923,7 @@ function viteCaddyTlsPlugin({
|
|
|
704
923
|
return;
|
|
705
924
|
}
|
|
706
925
|
try {
|
|
707
|
-
await ensureCaddyReady(serverName, pluginCaddyApiUrl);
|
|
926
|
+
await ensureCaddyReady(serverName, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
|
|
708
927
|
} catch (e) {
|
|
709
928
|
console.error(
|
|
710
929
|
`Failed to configure Caddy base settings. Is the Caddy Admin API reachable at ${pluginCaddyApiUrl}?`,
|
|
@@ -718,13 +937,19 @@ function viteCaddyTlsPlugin({
|
|
|
718
937
|
domainArray,
|
|
719
938
|
routeId,
|
|
720
939
|
serverName,
|
|
721
|
-
pluginCaddyApiUrl
|
|
940
|
+
pluginCaddyApiUrl,
|
|
941
|
+
pluginCaddyAdminOrigin
|
|
722
942
|
);
|
|
723
|
-
await removeRoute(routeId, pluginCaddyApiUrl);
|
|
943
|
+
await removeRoute(routeId, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
|
|
724
944
|
if (tlsPolicyId) {
|
|
725
|
-
await removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl);
|
|
945
|
+
await removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
|
|
726
946
|
try {
|
|
727
|
-
await addTlsPolicy(
|
|
947
|
+
await addTlsPolicy(
|
|
948
|
+
tlsPolicyId,
|
|
949
|
+
domainArray,
|
|
950
|
+
pluginCaddyApiUrl,
|
|
951
|
+
pluginCaddyAdminOrigin
|
|
952
|
+
);
|
|
728
953
|
tlsPolicyAdded = true;
|
|
729
954
|
} catch (e) {
|
|
730
955
|
console.error(
|
|
@@ -743,11 +968,12 @@ function viteCaddyTlsPlugin({
|
|
|
743
968
|
serverName,
|
|
744
969
|
upstreamHost,
|
|
745
970
|
upstreamHostHeader,
|
|
746
|
-
pluginCaddyApiUrl
|
|
971
|
+
pluginCaddyApiUrl,
|
|
972
|
+
pluginCaddyAdminOrigin
|
|
747
973
|
);
|
|
748
974
|
} catch (e) {
|
|
749
975
|
if (tlsPolicyAdded && tlsPolicyId) {
|
|
750
|
-
await removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl);
|
|
976
|
+
await removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
|
|
751
977
|
}
|
|
752
978
|
console.error(
|
|
753
979
|
`Failed to add route to Caddy. Is the Caddy Admin API reachable at ${pluginCaddyApiUrl}?`,
|