@vandenberghinc/volt 1.2.12 → 1.2.14
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/backend/dist/cjs/backend/src/endpoint.d.ts +1 -1
- package/backend/dist/cjs/backend/src/endpoint.js +1 -1
- package/backend/dist/cjs/backend/src/payments/stripe/products.js +6 -6
- package/backend/dist/cjs/backend/src/payments/stripe/stripe.js +7 -0
- package/backend/dist/cjs/backend/src/payments/stripe/webhooks.js +4 -0
- package/backend/dist/cjs/backend/src/plugins/caddy/caddy.d.ts +1 -0
- package/backend/dist/cjs/backend/src/plugins/caddy/caddy.js +15 -0
- package/backend/dist/cjs/backend/src/server.js +7 -3
- package/backend/dist/cjs/backend/src/users.js +19 -0
- package/backend/dist/esm/backend/src/endpoint.d.ts +1 -1
- package/backend/dist/esm/backend/src/endpoint.js +1 -1
- package/backend/dist/esm/backend/src/payments/stripe/products.js +6 -6
- package/backend/dist/esm/backend/src/payments/stripe/stripe.js +7 -0
- package/backend/dist/esm/backend/src/payments/stripe/webhooks.js +4 -0
- package/backend/dist/esm/backend/src/plugins/caddy/caddy.d.ts +1 -0
- package/backend/dist/esm/backend/src/plugins/caddy/caddy.js +592 -0
- package/backend/dist/esm/backend/src/server.js +4 -0
- package/backend/dist/esm/backend/src/users.js +19 -0
- package/frontend/dist/backend/src/endpoint.d.ts +1 -1
- package/frontend/dist/backend/src/endpoint.js +1 -1
- package/frontend/dist/backend/src/payments/stripe/products.js +6 -6
- package/frontend/dist/backend/src/payments/stripe/stripe.js +7 -0
- package/frontend/dist/backend/src/payments/stripe/webhooks.js +4 -0
- package/frontend/dist/backend/src/server.js +4 -0
- package/frontend/dist/backend/src/users.js +19 -0
- package/package.json +2 -2
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
// /**
|
|
2
|
+
// * @author Daan van den Bergh
|
|
3
|
+
// * @copyright © 2022 - 2026 Daan van den Bergh.
|
|
4
|
+
// */
|
|
5
|
+
export {};
|
|
6
|
+
// import { promises as fs } from "node:fs";
|
|
7
|
+
// import { spawn } from "node:child_process";
|
|
8
|
+
// import { platform } from "node:os";
|
|
9
|
+
// import { createHash } from "node:crypto";
|
|
10
|
+
// /**
|
|
11
|
+
// * Caddy reverse proxy setup utilities.
|
|
12
|
+
// *
|
|
13
|
+
// * Exposes an idempotent setup API for installing Caddy and writing validated configs.
|
|
14
|
+
// */
|
|
15
|
+
// export namespace Caddy {
|
|
16
|
+
// /**
|
|
17
|
+
// * Represents a reverse proxy upstream configuration.
|
|
18
|
+
// */
|
|
19
|
+
// export interface ReverseProxyOpts {
|
|
20
|
+
// /** List of upstream targets (host:port). */
|
|
21
|
+
// readonly upstreams: readonly string[];
|
|
22
|
+
// /**
|
|
23
|
+
// * Optional reverse_proxy sub-directives expressed as a simple key/value map.
|
|
24
|
+
// *
|
|
25
|
+
// * Use this for widely-compatible configuration without forcing a massive typed surface.
|
|
26
|
+
// * Each entry is rendered as: `key value` inside the `reverse_proxy { ... }` block.
|
|
27
|
+
// *
|
|
28
|
+
// * Docs:
|
|
29
|
+
// * - reverse_proxy directive: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy
|
|
30
|
+
// * - reverse_proxy sub-directives: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#syntax
|
|
31
|
+
// *
|
|
32
|
+
// * @example
|
|
33
|
+
// * {Reverse proxy with health checks and header overrides}
|
|
34
|
+
// * Adds active health checks and forwards the original host and client IP information.
|
|
35
|
+
// * ```ts
|
|
36
|
+
// * const reverse_proxy: Caddy.ReverseProxyOpts = {
|
|
37
|
+
// * upstreams: ["127.0.0.1:3000"],
|
|
38
|
+
// * options: {
|
|
39
|
+
// * health_uri: "/health",
|
|
40
|
+
// * health_interval: "10s",
|
|
41
|
+
// * health_timeout: "2s",
|
|
42
|
+
// * lb_policy: "round_robin",
|
|
43
|
+
// * header_up: "Host {host}",
|
|
44
|
+
// * },
|
|
45
|
+
// * };
|
|
46
|
+
// * ```
|
|
47
|
+
// */
|
|
48
|
+
// readonly options?: Record<string, string | number | boolean>;
|
|
49
|
+
// }
|
|
50
|
+
// /**
|
|
51
|
+
// * A header operation in the `header` directive.
|
|
52
|
+
// *
|
|
53
|
+
// * Docs:
|
|
54
|
+
// * - header directive: https://caddyserver.com/docs/caddyfile/directives/header
|
|
55
|
+
// */
|
|
56
|
+
// export type HeaderOperation =
|
|
57
|
+
// | {
|
|
58
|
+
// /** Operation kind. */
|
|
59
|
+
// readonly kind: "set";
|
|
60
|
+
// /** Header field name. */
|
|
61
|
+
// readonly field: string;
|
|
62
|
+
// /** Header value. */
|
|
63
|
+
// readonly value: string;
|
|
64
|
+
// }
|
|
65
|
+
// | {
|
|
66
|
+
// /** Operation kind. */
|
|
67
|
+
// readonly kind: "add";
|
|
68
|
+
// /** Header field name. */
|
|
69
|
+
// readonly field: string;
|
|
70
|
+
// /** Header value. */
|
|
71
|
+
// readonly value: string;
|
|
72
|
+
// }
|
|
73
|
+
// | {
|
|
74
|
+
// /** Operation kind. */
|
|
75
|
+
// readonly kind: "delete";
|
|
76
|
+
// /** Header field name. */
|
|
77
|
+
// readonly field: string;
|
|
78
|
+
// };
|
|
79
|
+
// /**
|
|
80
|
+
// * Represents a structured `header` directive.
|
|
81
|
+
// *
|
|
82
|
+
// * This is rendered as:
|
|
83
|
+
// * `header [<matcher>] { ... }`
|
|
84
|
+
// *
|
|
85
|
+
// * Docs:
|
|
86
|
+
// * - header directive: https://caddyserver.com/docs/caddyfile/directives/header
|
|
87
|
+
// *
|
|
88
|
+
// * @example
|
|
89
|
+
// * {Set common security headers}
|
|
90
|
+
// * Adds HSTS and a few common hardening headers for all responses.
|
|
91
|
+
// * ```ts
|
|
92
|
+
// * const headers: Caddy.HeaderDirective = {
|
|
93
|
+
// * directive: "header",
|
|
94
|
+
// * operations: [
|
|
95
|
+
// * { kind: "set", field: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains; preload" },
|
|
96
|
+
// * { kind: "set", field: "X-Content-Type-Options", value: "nosniff" },
|
|
97
|
+
// * { kind: "set", field: "X-Frame-Options", value: "DENY" },
|
|
98
|
+
// * { kind: "set", field: "Referrer-Policy", value: "no-referrer" },
|
|
99
|
+
// * ],
|
|
100
|
+
// * };
|
|
101
|
+
// * ```
|
|
102
|
+
// */
|
|
103
|
+
// export interface HeaderDirective {
|
|
104
|
+
// /** Discriminator for directive unions. */
|
|
105
|
+
// readonly directive: "header";
|
|
106
|
+
// /**
|
|
107
|
+
// * Optional Caddyfile request matcher token(s), e.g. `@api` or `@static`.
|
|
108
|
+
// *
|
|
109
|
+
// * If you need a complex matcher definition, use a raw string directive in `extra_directives`.
|
|
110
|
+
// */
|
|
111
|
+
// readonly matcher?: string;
|
|
112
|
+
// /** Header operations to apply. */
|
|
113
|
+
// readonly operations: readonly HeaderOperation[];
|
|
114
|
+
// }
|
|
115
|
+
// /**
|
|
116
|
+
// * Represents a structured `encode` directive.
|
|
117
|
+
// *
|
|
118
|
+
// * Docs:
|
|
119
|
+
// * - encode directive: https://caddyserver.com/docs/caddyfile/directives/encode
|
|
120
|
+
// *
|
|
121
|
+
// * @example
|
|
122
|
+
// * {Enable gzip and zstd compression}
|
|
123
|
+
// * Enables response compression where supported.
|
|
124
|
+
// * ```ts
|
|
125
|
+
// * const encode: Caddy.EncodeDirective = {
|
|
126
|
+
// * directive: "encode",
|
|
127
|
+
// * encodings: ["zstd", "gzip"],
|
|
128
|
+
// * };
|
|
129
|
+
// * ```
|
|
130
|
+
// */
|
|
131
|
+
// export interface EncodeDirective {
|
|
132
|
+
// /** Discriminator for directive unions. */
|
|
133
|
+
// readonly directive: "encode";
|
|
134
|
+
// /** List of encodings, e.g. `["zstd", "gzip"]`. */
|
|
135
|
+
// readonly encodings: readonly string[];
|
|
136
|
+
// }
|
|
137
|
+
// /**
|
|
138
|
+
// * Represents a structured `log` directive.
|
|
139
|
+
// *
|
|
140
|
+
// * Docs:
|
|
141
|
+
// * - log directive: https://caddyserver.com/docs/caddyfile/directives/log
|
|
142
|
+
// *
|
|
143
|
+
// * @example
|
|
144
|
+
// * {Write JSON access logs to a file}
|
|
145
|
+
// * Configures JSON formatted access logs written to a persistent file.
|
|
146
|
+
// * ```ts
|
|
147
|
+
// * const log: Caddy.LogDirective = {
|
|
148
|
+
// * directive: "log",
|
|
149
|
+
// * output: { kind: "file", path: "/var/log/caddy/access.json" },
|
|
150
|
+
// * format: "json",
|
|
151
|
+
// * };
|
|
152
|
+
// * ```
|
|
153
|
+
// */
|
|
154
|
+
// export interface LogDirective {
|
|
155
|
+
// /** Discriminator for directive unions. */
|
|
156
|
+
// readonly directive: "log";
|
|
157
|
+
// /**
|
|
158
|
+
// * Optional Caddyfile request matcher token(s).
|
|
159
|
+
// *
|
|
160
|
+
// * If you need a complex matcher definition, use a raw string directive in `extra_directives`.
|
|
161
|
+
// */
|
|
162
|
+
// readonly matcher?: string;
|
|
163
|
+
// /**
|
|
164
|
+
// * Output configuration for logs.
|
|
165
|
+
// *
|
|
166
|
+
// * Note: This intentionally supports only the most common commercial-grade sinks
|
|
167
|
+
// * while keeping escape hatches via raw directives.
|
|
168
|
+
// */
|
|
169
|
+
// readonly output?: LogOutput;
|
|
170
|
+
// /** Log format name, e.g. `json` or `console`. */
|
|
171
|
+
// readonly format?: "json" | "console";
|
|
172
|
+
// }
|
|
173
|
+
// /**
|
|
174
|
+
// * Describes a log output sink.
|
|
175
|
+
// *
|
|
176
|
+
// * Docs:
|
|
177
|
+
// * - log output: https://caddyserver.com/docs/caddyfile/directives/log#output-modules
|
|
178
|
+
// */
|
|
179
|
+
// export type LogOutput =
|
|
180
|
+
// | {
|
|
181
|
+
// /** Output kind. */
|
|
182
|
+
// readonly kind: "file";
|
|
183
|
+
// /** File path for logs. */
|
|
184
|
+
// readonly path: string;
|
|
185
|
+
// }
|
|
186
|
+
// | {
|
|
187
|
+
// /** Output kind. */
|
|
188
|
+
// readonly kind: "stdout";
|
|
189
|
+
// }
|
|
190
|
+
// | {
|
|
191
|
+
// /** Output kind. */
|
|
192
|
+
// readonly kind: "stderr";
|
|
193
|
+
// };
|
|
194
|
+
// /**
|
|
195
|
+
// * Represents a structured `tls` directive for common use cases.
|
|
196
|
+
// *
|
|
197
|
+
// * Docs:
|
|
198
|
+
// * - tls directive: https://caddyserver.com/docs/caddyfile/directives/tls
|
|
199
|
+
// *
|
|
200
|
+
// * @example
|
|
201
|
+
// * {Use a DNS challenge issuer}
|
|
202
|
+
// * Configures TLS with a DNS provider module (requires the appropriate Caddy build).
|
|
203
|
+
// * ```ts
|
|
204
|
+
// * const tls: Caddy.TlsDirective = {
|
|
205
|
+
// * directive: "tls",
|
|
206
|
+
// * issuer: "dns",
|
|
207
|
+
// * issuer_args: ["cloudflare", "{env.CLOUDFLARE_API_TOKEN}"],
|
|
208
|
+
// * };
|
|
209
|
+
// * ```
|
|
210
|
+
// */
|
|
211
|
+
// export interface TlsDirective {
|
|
212
|
+
// /** Discriminator for directive unions. */
|
|
213
|
+
// readonly directive: "tls";
|
|
214
|
+
// /**
|
|
215
|
+
// * Issuer module name, commonly `internal`, `acme`, or `dns`.
|
|
216
|
+
// *
|
|
217
|
+
// * For advanced TLS configuration, use raw directives.
|
|
218
|
+
// */
|
|
219
|
+
// readonly issuer?: "internal" | "acme" | "dns";
|
|
220
|
+
// /**
|
|
221
|
+
// * Optional issuer arguments.
|
|
222
|
+
// *
|
|
223
|
+
// * These are rendered as raw tokens after the issuer declaration.
|
|
224
|
+
// */
|
|
225
|
+
// readonly issuer_args?: readonly string[];
|
|
226
|
+
// }
|
|
227
|
+
// /**
|
|
228
|
+
// * A typed extra directive which can be rendered into a Caddyfile snippet.
|
|
229
|
+
// *
|
|
230
|
+
// * This provides common structured directives while keeping a raw string escape hatch.
|
|
231
|
+
// */
|
|
232
|
+
// export type ExtraDirective = string | HeaderDirective | EncodeDirective | LogDirective | TlsDirective;
|
|
233
|
+
// /**
|
|
234
|
+
// * Represents a single site served by Caddy.
|
|
235
|
+
// */
|
|
236
|
+
// export interface SiteOpts {
|
|
237
|
+
// /** Fully-qualified domain name. */
|
|
238
|
+
// readonly domain: string;
|
|
239
|
+
// /** Reverse proxy configuration. */
|
|
240
|
+
// readonly reverse_proxy: ReverseProxyOpts;
|
|
241
|
+
// /**
|
|
242
|
+
// * Optional extra Caddyfile directives added inside the site block.
|
|
243
|
+
// *
|
|
244
|
+
// * Supports both raw strings (fully flexible) and typed directive objects for common cases.
|
|
245
|
+
// *
|
|
246
|
+
// * Docs:
|
|
247
|
+
// * - Caddyfile directives overview: https://caddyserver.com/docs/caddyfile/directives
|
|
248
|
+
// *
|
|
249
|
+
// * @example
|
|
250
|
+
// * {Mix typed and raw directives}
|
|
251
|
+
// * Uses typed directives for common needs while falling back to raw Caddyfile lines when needed.
|
|
252
|
+
// * ```ts
|
|
253
|
+
// * const site: Caddy.SiteOpts = {
|
|
254
|
+
// * domain: "example.com",
|
|
255
|
+
// * reverse_proxy: { upstreams: ["127.0.0.1:3000"] },
|
|
256
|
+
// * extra_directives: [
|
|
257
|
+
// * { directive: "encode", encodings: ["zstd", "gzip"] },
|
|
258
|
+
// * {
|
|
259
|
+
// * directive: "header",
|
|
260
|
+
// * operations: [
|
|
261
|
+
// * { kind: "set", field: "X-Frame-Options", value: "DENY" },
|
|
262
|
+
// * { kind: "set", field: "X-Content-Type-Options", value: "nosniff" },
|
|
263
|
+
// * ],
|
|
264
|
+
// * },
|
|
265
|
+
// * // Raw escape hatch for anything not covered by types:
|
|
266
|
+
// * "request_body { max_size 10MB }",
|
|
267
|
+
// * ],
|
|
268
|
+
// * };
|
|
269
|
+
// * ```
|
|
270
|
+
// */
|
|
271
|
+
// readonly extra_directives?: readonly ExtraDirective[];
|
|
272
|
+
// }
|
|
273
|
+
// /**
|
|
274
|
+
// * Top-level Caddy configuration options.
|
|
275
|
+
// */
|
|
276
|
+
// export interface ConfigOpts {
|
|
277
|
+
// /** Destination path of the generated Caddy config file. */
|
|
278
|
+
// readonly config_path: string;
|
|
279
|
+
// /** List of sites to configure. */
|
|
280
|
+
// readonly sites: readonly SiteOpts[];
|
|
281
|
+
// /**
|
|
282
|
+
// * Optional global Caddyfile options written in the global options block `{ ... }`.
|
|
283
|
+
// *
|
|
284
|
+
// * Each entry is rendered as: `key value` within the global block.
|
|
285
|
+
// *
|
|
286
|
+
// * Docs:
|
|
287
|
+
// * - Global options block: https://caddyserver.com/docs/caddyfile/options
|
|
288
|
+
// * - Common global options: https://caddyserver.com/docs/caddyfile/options#global-options
|
|
289
|
+
// *
|
|
290
|
+
// * @example
|
|
291
|
+
// * {Set ACME email and enable debug logging globally}
|
|
292
|
+
// * Configures the email used for ACME registration and turns on debug mode.
|
|
293
|
+
// * ```ts
|
|
294
|
+
// * const config: Caddy.ConfigOpts = {
|
|
295
|
+
// * config_path: "/etc/caddy/Caddyfile",
|
|
296
|
+
// * global_options: {
|
|
297
|
+
// * email: "ops@example.com",
|
|
298
|
+
// * debug: true,
|
|
299
|
+
// * },
|
|
300
|
+
// * sites: [
|
|
301
|
+
// * {
|
|
302
|
+
// * domain: "example.com",
|
|
303
|
+
// * reverse_proxy: { upstreams: ["127.0.0.1:3000"] },
|
|
304
|
+
// * },
|
|
305
|
+
// * ],
|
|
306
|
+
// * };
|
|
307
|
+
// * ```
|
|
308
|
+
// */
|
|
309
|
+
// readonly global_options?: Record<string, string | number | boolean>;
|
|
310
|
+
// }
|
|
311
|
+
// /**
|
|
312
|
+
// * Ensures that the Caddy binary is installed on the system.
|
|
313
|
+
// *
|
|
314
|
+
// * This function is idempotent and safe to run multiple times.
|
|
315
|
+
// *
|
|
316
|
+
// * @example
|
|
317
|
+
// * {Install Caddy if it is missing}
|
|
318
|
+
// * Ensures the `caddy` binary is available before writing configs.
|
|
319
|
+
// * ```ts
|
|
320
|
+
// * import { Caddy } from "./caddy_reverse_proxy_setup";
|
|
321
|
+
// *
|
|
322
|
+
// * await Caddy.ensure_installed();
|
|
323
|
+
// * ```
|
|
324
|
+
// */
|
|
325
|
+
// export async function ensure_installed(): Promise<void> {
|
|
326
|
+
// if (await is_caddy_available()) {
|
|
327
|
+
// return;
|
|
328
|
+
// }
|
|
329
|
+
// const current_platform: string = platform();
|
|
330
|
+
// // On Linux we install via the official Caddy apt repository (production-friendly).
|
|
331
|
+
// if (current_platform === "linux") {
|
|
332
|
+
// await install_caddy_linux();
|
|
333
|
+
// return;
|
|
334
|
+
// }
|
|
335
|
+
// throw new Error(`unsupported_platform: ${current_platform}`);
|
|
336
|
+
// }
|
|
337
|
+
// /**
|
|
338
|
+
// * Writes a Caddy configuration file in an idempotent manner.
|
|
339
|
+
// *
|
|
340
|
+
// * The file is only replaced if content has changed, then validated via `caddy validate`.
|
|
341
|
+
// *
|
|
342
|
+
// * @example
|
|
343
|
+
// * {Write and validate a reverse proxy configuration}
|
|
344
|
+
// * Generates a Caddyfile, writes it only if changed, and validates it.
|
|
345
|
+
// * ```ts
|
|
346
|
+
// * import { Caddy } from "./caddy_reverse_proxy_setup";
|
|
347
|
+
// *
|
|
348
|
+
// * await Caddy.write_config({
|
|
349
|
+
// * config_path: "/etc/caddy/Caddyfile",
|
|
350
|
+
// * global_options: { email: "ops@example.com" },
|
|
351
|
+
// * sites: [
|
|
352
|
+
// * {
|
|
353
|
+
// * domain: "example.com",
|
|
354
|
+
// * reverse_proxy: {
|
|
355
|
+
// * upstreams: ["127.0.0.1:3000"],
|
|
356
|
+
// * options: {
|
|
357
|
+
// * health_uri: "/health",
|
|
358
|
+
// * health_interval: "10s",
|
|
359
|
+
// * },
|
|
360
|
+
// * },
|
|
361
|
+
// * extra_directives: [
|
|
362
|
+
// * { directive: "encode", encodings: ["gzip", "zstd"] },
|
|
363
|
+
// * { directive: "log", output: { kind: "file", path: "/var/log/caddy/access.json" }, format: "json" },
|
|
364
|
+
// * {
|
|
365
|
+
// * directive: "header",
|
|
366
|
+
// * operations: [
|
|
367
|
+
// * { kind: "set", field: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains; preload" },
|
|
368
|
+
// * { kind: "set", field: "X-Content-Type-Options", value: "nosniff" },
|
|
369
|
+
// * ],
|
|
370
|
+
// * },
|
|
371
|
+
// * // Raw directive escape hatch:
|
|
372
|
+
// * "request_body { max_size 10MB }",
|
|
373
|
+
// * ],
|
|
374
|
+
// * },
|
|
375
|
+
// * ],
|
|
376
|
+
// * });
|
|
377
|
+
// * ```
|
|
378
|
+
// */
|
|
379
|
+
// export async function write_config(opts: ConfigOpts): Promise<void> {
|
|
380
|
+
// const rendered_config: string = render_caddyfile(opts);
|
|
381
|
+
// await write_file_if_changed(opts.config_path, rendered_config);
|
|
382
|
+
// await validate_config(opts.config_path);
|
|
383
|
+
// }
|
|
384
|
+
// /**
|
|
385
|
+
// * Checks whether the caddy binary is available.
|
|
386
|
+
// */
|
|
387
|
+
// async function is_caddy_available(): Promise<boolean> {
|
|
388
|
+
// try {
|
|
389
|
+
// await exec("caddy", ["version"]);
|
|
390
|
+
// return true;
|
|
391
|
+
// } catch {
|
|
392
|
+
// return false;
|
|
393
|
+
// }
|
|
394
|
+
// }
|
|
395
|
+
// /**
|
|
396
|
+
// * Installs Caddy on Linux using official repositories.
|
|
397
|
+
// *
|
|
398
|
+
// * Note: This requires appropriate privileges (typically root).
|
|
399
|
+
// */
|
|
400
|
+
// async function install_caddy_linux(): Promise<void> {
|
|
401
|
+
// // Ensure apt can use HTTPS and has required keyring tooling.
|
|
402
|
+
// await exec("sh", [
|
|
403
|
+
// "-c",
|
|
404
|
+
// "apt-get update && apt-get install -y debian-keyring debian-archive-keyring apt-transport-https",
|
|
405
|
+
// ]);
|
|
406
|
+
// // Install the official Caddy signing key into the system keyrings.
|
|
407
|
+
// await exec("sh", [
|
|
408
|
+
// "-c",
|
|
409
|
+
// "curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable.gpg",
|
|
410
|
+
// ]);
|
|
411
|
+
// // Add the official Caddy stable repository.
|
|
412
|
+
// await exec("sh", [
|
|
413
|
+
// "-c",
|
|
414
|
+
// "curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list",
|
|
415
|
+
// ]);
|
|
416
|
+
// // Install Caddy.
|
|
417
|
+
// await exec("apt-get", ["update"]);
|
|
418
|
+
// await exec("apt-get", ["install", "-y", "caddy"]);
|
|
419
|
+
// }
|
|
420
|
+
// /**
|
|
421
|
+
// * Renders a Caddyfile from typed configuration.
|
|
422
|
+
// *
|
|
423
|
+
// * This keeps the surface area minimal while allowing users to pass raw directives
|
|
424
|
+
// * via `extra_directives` and reverse_proxy `options`.
|
|
425
|
+
// */
|
|
426
|
+
// function render_caddyfile(opts: ConfigOpts): string {
|
|
427
|
+
// const lines: string[] = [];
|
|
428
|
+
// // Global options block (e.g. email, acme_ca, servers, etc).
|
|
429
|
+
// if (opts.global_options) {
|
|
430
|
+
// lines.push("{");
|
|
431
|
+
// for (const [key, value] of Object.entries(opts.global_options)) {
|
|
432
|
+
// lines.push(` ${key} ${value}`);
|
|
433
|
+
// }
|
|
434
|
+
// lines.push("}");
|
|
435
|
+
// lines.push("");
|
|
436
|
+
// }
|
|
437
|
+
// // Each site gets its own server block.
|
|
438
|
+
// for (const site of opts.sites) {
|
|
439
|
+
// lines.push(`${site.domain} {`);
|
|
440
|
+
// // reverse_proxy is the core directive for proxying to an upstream app.
|
|
441
|
+
// lines.push(` reverse_proxy ${site.reverse_proxy.upstreams.join(" ")} {`);
|
|
442
|
+
// // Sub-directives within reverse_proxy (health checks, header manipulation, lb policy, etc).
|
|
443
|
+
// if (site.reverse_proxy.options) {
|
|
444
|
+
// for (const [key, value] of Object.entries(site.reverse_proxy.options)) {
|
|
445
|
+
// lines.push(` ${key} ${value}`);
|
|
446
|
+
// }
|
|
447
|
+
// }
|
|
448
|
+
// lines.push(" }");
|
|
449
|
+
// // Extra directives: render typed objects safely, or pass through raw strings.
|
|
450
|
+
// if (site.extra_directives) {
|
|
451
|
+
// for (const directive of site.extra_directives) {
|
|
452
|
+
// for (const rendered_line of render_extra_directive(directive)) {
|
|
453
|
+
// lines.push(` ${rendered_line}`);
|
|
454
|
+
// }
|
|
455
|
+
// }
|
|
456
|
+
// }
|
|
457
|
+
// lines.push("}");
|
|
458
|
+
// lines.push("");
|
|
459
|
+
// }
|
|
460
|
+
// return lines.join("\n");
|
|
461
|
+
// }
|
|
462
|
+
// /**
|
|
463
|
+
// * Renders an extra directive into one or more Caddyfile lines.
|
|
464
|
+
// */
|
|
465
|
+
// function render_extra_directive(directive: ExtraDirective): readonly string[] {
|
|
466
|
+
// // Raw directives are passed through unchanged to preserve full Caddy compatibility.
|
|
467
|
+
// if (typeof directive === "string") {
|
|
468
|
+
// return directive.split("\n");
|
|
469
|
+
// }
|
|
470
|
+
// if (directive.directive === "encode") {
|
|
471
|
+
// return [`encode ${directive.encodings.join(" ")}`];
|
|
472
|
+
// }
|
|
473
|
+
// if (directive.directive === "tls") {
|
|
474
|
+
// // Keep TLS typed support intentionally narrow and escape-hatch friendly.
|
|
475
|
+
// const issuer_tokens: string[] = [];
|
|
476
|
+
// if (directive.issuer) {
|
|
477
|
+
// issuer_tokens.push(directive.issuer);
|
|
478
|
+
// }
|
|
479
|
+
// if (directive.issuer_args) {
|
|
480
|
+
// for (const arg of directive.issuer_args) {
|
|
481
|
+
// issuer_tokens.push(arg);
|
|
482
|
+
// }
|
|
483
|
+
// }
|
|
484
|
+
// if (issuer_tokens.length === 0) {
|
|
485
|
+
// return ["tls"];
|
|
486
|
+
// }
|
|
487
|
+
// return [`tls ${issuer_tokens.join(" ")}`];
|
|
488
|
+
// }
|
|
489
|
+
// if (directive.directive === "log") {
|
|
490
|
+
// const lines: string[] = [];
|
|
491
|
+
// const matcher_prefix: string = directive.matcher ? ` ${directive.matcher}` : "";
|
|
492
|
+
// lines.push(`log${matcher_prefix} {`);
|
|
493
|
+
// if (directive.output) {
|
|
494
|
+
// if (directive.output.kind === "file") {
|
|
495
|
+
// lines.push(` output file ${directive.output.path}`);
|
|
496
|
+
// } else if (directive.output.kind === "stdout") {
|
|
497
|
+
// lines.push(" output stdout");
|
|
498
|
+
// } else if (directive.output.kind === "stderr") {
|
|
499
|
+
// lines.push(" output stderr");
|
|
500
|
+
// }
|
|
501
|
+
// }
|
|
502
|
+
// if (directive.format) {
|
|
503
|
+
// lines.push(` format ${directive.format}`);
|
|
504
|
+
// }
|
|
505
|
+
// lines.push("}");
|
|
506
|
+
// return lines;
|
|
507
|
+
// }
|
|
508
|
+
// if (directive.directive === "header") {
|
|
509
|
+
// const lines: string[] = [];
|
|
510
|
+
// const matcher_prefix: string = directive.matcher ? ` ${directive.matcher}` : "";
|
|
511
|
+
// lines.push(`header${matcher_prefix} {`);
|
|
512
|
+
// // Render each operation line in the most widely-compatible Caddyfile form.
|
|
513
|
+
// for (const op of directive.operations) {
|
|
514
|
+
// if (op.kind === "set") {
|
|
515
|
+
// lines.push(` ${op.field} "${escape_header_value(op.value)}"`);
|
|
516
|
+
// continue;
|
|
517
|
+
// }
|
|
518
|
+
// if (op.kind === "add") {
|
|
519
|
+
// // In Caddyfile, repeated header fields effectively "add" values.
|
|
520
|
+
// lines.push(` +${op.field} "${escape_header_value(op.value)}"`);
|
|
521
|
+
// continue;
|
|
522
|
+
// }
|
|
523
|
+
// if (op.kind === "delete") {
|
|
524
|
+
// lines.push(` -${op.field}`);
|
|
525
|
+
// continue;
|
|
526
|
+
// }
|
|
527
|
+
// }
|
|
528
|
+
// lines.push("}");
|
|
529
|
+
// return lines;
|
|
530
|
+
// }
|
|
531
|
+
// // Exhaustiveness guard: if a new directive is added to the union, TypeScript will flag this.
|
|
532
|
+
// // Returning raw empty would be unsafe; we throw to avoid silently misconfiguring production.
|
|
533
|
+
// throw new Error("unsupported_extra_directive");
|
|
534
|
+
// }
|
|
535
|
+
// /**
|
|
536
|
+
// * Escapes a header value for safe embedding into a quoted Caddyfile token.
|
|
537
|
+
// */
|
|
538
|
+
// function escape_header_value(value: string): string {
|
|
539
|
+
// // We only escape backslashes and double-quotes to keep the resulting Caddyfile valid.
|
|
540
|
+
// return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
541
|
+
// }
|
|
542
|
+
// /**
|
|
543
|
+
// * Writes a file only if its content has changed.
|
|
544
|
+
// *
|
|
545
|
+
// * This makes the setup idempotent and reduces unnecessary disk writes/reloads.
|
|
546
|
+
// */
|
|
547
|
+
// async function write_file_if_changed(file_path: string, content: string): Promise<void> {
|
|
548
|
+
// const new_hash: string = hash_content(content);
|
|
549
|
+
// try {
|
|
550
|
+
// const existing: string = await fs.readFile(file_path, "utf8");
|
|
551
|
+
// const existing_hash: string = hash_content(existing);
|
|
552
|
+
// if (existing_hash === new_hash) {
|
|
553
|
+
// return;
|
|
554
|
+
// }
|
|
555
|
+
// } catch {
|
|
556
|
+
// // File does not exist or is unreadable, proceed with write.
|
|
557
|
+
// }
|
|
558
|
+
// await fs.writeFile(file_path, content, { mode: 0o644 });
|
|
559
|
+
// }
|
|
560
|
+
// /**
|
|
561
|
+
// * Validates a Caddy configuration file.
|
|
562
|
+
// *
|
|
563
|
+
// * This catches invalid directives before the user attempts to reload/start Caddy.
|
|
564
|
+
// */
|
|
565
|
+
// async function validate_config(config_path: string): Promise<void> {
|
|
566
|
+
// await exec("caddy", ["validate", "--config", config_path]);
|
|
567
|
+
// }
|
|
568
|
+
// /**
|
|
569
|
+
// * Executes a system command safely.
|
|
570
|
+
// *
|
|
571
|
+
// * Uses inherited stdio so the user sees relevant output (useful for install/validate).
|
|
572
|
+
// */
|
|
573
|
+
// async function exec(command: string, args: readonly string[]): Promise<void> {
|
|
574
|
+
// await new Promise<void>((resolve, reject) => {
|
|
575
|
+
// const child = spawn(command, args, { stdio: "inherit" });
|
|
576
|
+
// child.on("error", reject);
|
|
577
|
+
// child.on("exit", (code: number | null) => {
|
|
578
|
+
// if (code === 0) {
|
|
579
|
+
// resolve();
|
|
580
|
+
// return;
|
|
581
|
+
// }
|
|
582
|
+
// reject(new Error(`command_failed: ${command}`));
|
|
583
|
+
// });
|
|
584
|
+
// });
|
|
585
|
+
// }
|
|
586
|
+
// /**
|
|
587
|
+
// * Computes a stable hash for content comparison.
|
|
588
|
+
// */
|
|
589
|
+
// function hash_content(content: string): string {
|
|
590
|
+
// return createHash("sha256").update(content).digest("hex");
|
|
591
|
+
// }
|
|
592
|
+
// }
|
|
@@ -609,6 +609,7 @@ export class Server {
|
|
|
609
609
|
content_type: this.get_content_type(favicon.extension()),
|
|
610
610
|
_is_static: true,
|
|
611
611
|
server: this,
|
|
612
|
+
cache: true,
|
|
612
613
|
});
|
|
613
614
|
}
|
|
614
615
|
// Create status endpoint.
|
|
@@ -632,6 +633,7 @@ export class Server {
|
|
|
632
633
|
params: {
|
|
633
634
|
key: "string",
|
|
634
635
|
},
|
|
636
|
+
cache: false,
|
|
635
637
|
callback: async (stream, params) => {
|
|
636
638
|
// Check key.
|
|
637
639
|
if (params.key !== status_key) {
|
|
@@ -699,6 +701,7 @@ export class Server {
|
|
|
699
701
|
data: sitemap,
|
|
700
702
|
content_type: "application/xml",
|
|
701
703
|
_compress: false,
|
|
704
|
+
cache: true,
|
|
702
705
|
});
|
|
703
706
|
}
|
|
704
707
|
// Create the robots.txt endpoint.
|
|
@@ -724,6 +727,7 @@ export class Server {
|
|
|
724
727
|
content_type: "text/plain",
|
|
725
728
|
data: robots,
|
|
726
729
|
_compress: false,
|
|
730
|
+
cache: true,
|
|
727
731
|
});
|
|
728
732
|
}
|
|
729
733
|
// Create admin endpoint.
|