redkite 0.1.9 → 0.1.11
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 +191 -23
- package/dist/build.d.ts +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +7 -1
- package/dist/build.js.map +1 -1
- package/dist/cli/agent.js +2 -2
- package/dist/cli/agent.js.map +1 -1
- package/dist/cli/config.d.ts +1 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +67 -14
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/dotenv.d.ts +2 -0
- package/dist/cli/dotenv.d.ts.map +1 -0
- package/dist/cli/dotenv.js +30 -0
- package/dist/cli/dotenv.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +16 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/screen.d.ts +10 -3
- package/dist/cli/screen.d.ts.map +1 -1
- package/dist/cli/screen.js +153 -36
- package/dist/cli/screen.js.map +1 -1
- package/dist/cli/viewer.d.ts.map +1 -1
- package/dist/cli/viewer.js +36 -4
- package/dist/cli/viewer.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +67 -0
- package/dist/config.js.map +1 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +17 -6
- package/dist/deploy.js.map +1 -1
- package/dist/docker.d.ts +1 -0
- package/dist/docker.d.ts.map +1 -1
- package/dist/docker.js +3 -0
- package/dist/docker.js.map +1 -1
- package/dist/dockerfile.d.ts +1 -0
- package/dist/dockerfile.d.ts.map +1 -1
- package/dist/dockerfile.js +16 -3
- package/dist/dockerfile.js.map +1 -1
- package/dist/environment.d.ts +2 -0
- package/dist/environment.d.ts.map +1 -1
- package/dist/environment.js +56 -1
- package/dist/environment.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +1 -0
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +1 -0
- package/dist/log.js.map +1 -1
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +3 -0
- package/dist/pipeline.js.map +1 -1
- package/dist/plugins/bitwarden.d.ts.map +1 -1
- package/dist/plugins/bitwarden.js +25 -6
- package/dist/plugins/bitwarden.js.map +1 -1
- package/dist/presets/nextApp.d.ts.map +1 -1
- package/dist/presets/nextApp.js +5 -4
- package/dist/presets/nextApp.js.map +1 -1
- package/dist/presets/nodeApp.d.ts.map +1 -1
- package/dist/presets/nodeApp.js +15 -6
- package/dist/presets/nodeApp.js.map +1 -1
- package/dist/secrets/manager.d.ts +9 -0
- package/dist/secrets/manager.d.ts.map +1 -0
- package/dist/secrets/manager.js +130 -0
- package/dist/secrets/manager.js.map +1 -0
- package/dist/secrets/store.d.ts.map +1 -1
- package/dist/secrets/store.js +46 -12
- package/dist/secrets/store.js.map +1 -1
- package/dist/services/ensure.js +1 -1
- package/dist/services/ensure.js.map +1 -1
- package/dist/services/index.d.ts +2 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +11 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/planned.d.ts +0 -1
- package/dist/services/planned.d.ts.map +1 -1
- package/dist/services/planned.js +3 -12
- package/dist/services/planned.js.map +1 -1
- package/dist/services/proxy.d.ts +7 -0
- package/dist/services/proxy.d.ts.map +1 -0
- package/dist/services/proxy.js +103 -0
- package/dist/services/proxy.js.map +1 -0
- package/dist/source.d.ts +1 -0
- package/dist/source.d.ts.map +1 -1
- package/dist/source.js +5 -5
- package/dist/source.js.map +1 -1
- package/dist/steps.d.ts +0 -6
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +4 -12
- package/dist/steps.js.map +1 -1
- package/dist/types.d.ts +10 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/build.ts +10 -1
- package/src/cli/agent.ts +2 -2
- package/src/cli/config.ts +77 -13
- package/src/cli/dotenv.ts +35 -0
- package/src/cli/index.ts +16 -8
- package/src/cli/screen.ts +193 -43
- package/src/cli/viewer.ts +41 -4
- package/src/config.ts +80 -0
- package/src/deploy.ts +19 -6
- package/src/docker.ts +5 -0
- package/src/dockerfile.ts +23 -3
- package/src/environment.ts +71 -1
- package/src/index.ts +2 -1
- package/src/log.ts +4 -0
- package/src/pipeline.ts +4 -0
- package/src/plugins/bitwarden.ts +38 -11
- package/src/presets/nextApp.ts +5 -4
- package/src/presets/nodeApp.ts +15 -6
- package/src/secrets/manager.ts +172 -0
- package/src/secrets/store.ts +51 -16
- package/src/services/ensure.ts +1 -1
- package/src/services/index.ts +13 -1
- package/src/services/planned.ts +3 -13
- package/src/services/proxy.ts +126 -0
- package/src/source.ts +13 -4
- package/src/steps.ts +4 -19
- package/src/types.ts +29 -4
- package/dist/nginx.d.ts +0 -4
- package/dist/nginx.d.ts.map +0 -1
- package/dist/nginx.js +0 -53
- package/dist/nginx.js.map +0 -1
- package/src/nginx.ts +0 -63
package/src/services/planned.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
import type { Docker } from "../docker.js";
|
|
4
|
-
import { LISTEN_PORT,
|
|
4
|
+
import { LISTEN_PORT, proxyService, renderProxy } from "./proxy.js";
|
|
5
5
|
import type { Run } from "../pipeline.js";
|
|
6
6
|
import type { ServiceTopology, Topology } from "../topology.js";
|
|
7
7
|
import type { Deployment, ServiceSpec } from "../types.js";
|
|
@@ -36,10 +36,10 @@ export function plannedServices(
|
|
|
36
36
|
if (run === "verify") return rest;
|
|
37
37
|
|
|
38
38
|
const proxy: PlannedService = {
|
|
39
|
-
spec:
|
|
39
|
+
spec: proxyService(config),
|
|
40
40
|
service: topology.router,
|
|
41
41
|
files: {
|
|
42
|
-
"/etc/nginx/conf.d/default.conf":
|
|
42
|
+
"/etc/nginx/conf.d/default.conf": renderProxy(topology, config.proxy),
|
|
43
43
|
},
|
|
44
44
|
publish: topology.publicPort,
|
|
45
45
|
};
|
|
@@ -47,16 +47,6 @@ export function plannedServices(
|
|
|
47
47
|
return [proxy, ...rest];
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
// Not something a deployment lists. Apps carry routes, routes need a proxy to
|
|
51
|
-
// resolve them, and the one that renders them is this
|
|
52
|
-
export function router(config: Deployment): ServiceSpec {
|
|
53
|
-
return {
|
|
54
|
-
name: "nginx",
|
|
55
|
-
image: config.proxyImage ?? "nginx:stable",
|
|
56
|
-
restart: "always",
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
50
|
// Everything a recreate would change, and nothing a deploy resolves. Secrets
|
|
61
51
|
// are named by their refs rather than their values: whether the config is the
|
|
62
52
|
// one running is a question a plan should answer without unlocking a vault
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { AppTopology, Topology } from "../topology.js";
|
|
2
|
+
import type { Deployment, ProxySpec, ServiceSpec } from "../types.js";
|
|
3
|
+
|
|
4
|
+
// The proxy every route is resolved by: what it is, and the configuration it
|
|
5
|
+
// runs. Both halves here, because a deployment does not list this service and
|
|
6
|
+
// the only thing that decides either is the app list.
|
|
7
|
+
//
|
|
8
|
+
// Header policy is set once rather than per location, which is what let the two
|
|
9
|
+
// blocks drift apart and lose X-Forwarded-For on one of them.
|
|
10
|
+
|
|
11
|
+
const FORWARDING = [
|
|
12
|
+
"proxy_set_header Host $http_host;",
|
|
13
|
+
"proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;",
|
|
14
|
+
"proxy_set_header X-Forwarded-Host $http_host;",
|
|
15
|
+
"proxy_set_header X-Forwarded-Proto $scheme;",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const FAILOVER = [
|
|
19
|
+
"proxy_connect_timeout 2s;",
|
|
20
|
+
"proxy_read_timeout 30s;",
|
|
21
|
+
"proxy_next_upstream error timeout http_502 http_503;",
|
|
22
|
+
"proxy_next_upstream_tries 2;",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// The port the proxy listens on inside its container. The published port maps
|
|
26
|
+
// onto this one, so both sides have to agree and only one of them may say it
|
|
27
|
+
export const LISTEN_PORT = 3000;
|
|
28
|
+
|
|
29
|
+
// What the derived proxy is called. A service by this name would be a second
|
|
30
|
+
// container claiming the first one's, so the config surface refuses it
|
|
31
|
+
export const PROXY = "nginx";
|
|
32
|
+
|
|
33
|
+
// Not something a deployment lists. Apps carry routes, routes need something
|
|
34
|
+
// to resolve them, and this says what that something is
|
|
35
|
+
export function proxyService(config: Deployment): ServiceSpec {
|
|
36
|
+
return {
|
|
37
|
+
name: PROXY,
|
|
38
|
+
image: config.proxy?.image ?? "nginx:stable",
|
|
39
|
+
restart: "always",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function renderProxy(topology: Topology, proxy: ProxySpec = {}) {
|
|
44
|
+
const upstreams = topology.apps.map((app) => upstream(app)).join("\n\n");
|
|
45
|
+
// Longest route first, so "/api/" is not shadowed by the "/" catch-all
|
|
46
|
+
const ordered = [...topology.apps].sort((a, b) => b.route.length - a.route.length);
|
|
47
|
+
const locations = ordered.map((app) => location(app, proxy.location ?? [])).join("\n\n");
|
|
48
|
+
|
|
49
|
+
assertNotDerived(proxy.server ?? []);
|
|
50
|
+
|
|
51
|
+
const server = settled([
|
|
52
|
+
`merge_slashes off;`,
|
|
53
|
+
`client_max_body_size ${proxy.maxBodySize ?? "1M"};`,
|
|
54
|
+
...(proxy.server ?? []),
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
return `resolver 127.0.0.1 valid=5s;
|
|
58
|
+
|
|
59
|
+
${upstreams}
|
|
60
|
+
|
|
61
|
+
server {
|
|
62
|
+
listen ${LISTEN_PORT};
|
|
63
|
+
${indented(server, 4)}
|
|
64
|
+
${locations}
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// nginx refuses a second proxy_read_timeout but expects several
|
|
70
|
+
// proxy_set_header, so what identifies a line is the directive and, for the
|
|
71
|
+
// ones that carry a name, the name after it
|
|
72
|
+
const NAMED = new Set(["add_header", "proxy_set_header", "proxy_hide_header", "set"]);
|
|
73
|
+
|
|
74
|
+
function directive(line: string) {
|
|
75
|
+
const [name = "", argument = ""] = line.trim().split(/\s+/);
|
|
76
|
+
return NAMED.has(name) ? `${name} ${argument}` : name;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The last of each wins. A line written by hand replaces the one redkite would
|
|
80
|
+
// have put there rather than repeating it, which nginx would refuse outright
|
|
81
|
+
function settled(lines: string[]) {
|
|
82
|
+
const kept = new Map<string, string>();
|
|
83
|
+
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
if (!line.trim()) continue;
|
|
86
|
+
kept.set(directive(line), line.trim());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [...kept.values()];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// The published port maps onto this one, so both sides have to agree and only
|
|
93
|
+
// one of them may say it
|
|
94
|
+
function assertNotDerived(lines: string[]) {
|
|
95
|
+
const listening = lines.find((line) => directive(line) === "listen");
|
|
96
|
+
if (!listening) return;
|
|
97
|
+
|
|
98
|
+
throw new Error(
|
|
99
|
+
`The proxy's server block says ${listening.trim()}, and the port it listens ` +
|
|
100
|
+
"on inside its container is what publicPort is published onto",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function indented(lines: string[], by: number) {
|
|
105
|
+
return lines.map((line) => " ".repeat(by) + line).join("\n");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function upstream(app: AppTopology) {
|
|
109
|
+
return `upstream ${app.name} {
|
|
110
|
+
server ${app.container}:${app.port} max_fails=1 fail_timeout=2s;
|
|
111
|
+
server ${app.retired}:${app.port} backup;
|
|
112
|
+
}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function location(app: AppTopology, added: string[]) {
|
|
116
|
+
// A trailing slash on proxy_pass strips the location prefix. The catch-all
|
|
117
|
+
// must not carry one, a mounted route must
|
|
118
|
+
const target = app.route === "/" ? `http://${app.name}` : `http://${app.name}/`;
|
|
119
|
+
|
|
120
|
+
// Last, so one of these replaces what redkite set rather than repeating it
|
|
121
|
+
const body = settled([`proxy_pass ${target};`, ...FAILOVER, ...FORWARDING, ...added]);
|
|
122
|
+
|
|
123
|
+
return ` location ${app.route} {
|
|
124
|
+
${indented(body, 8)}
|
|
125
|
+
}`;
|
|
126
|
+
}
|
package/src/source.ts
CHANGED
|
@@ -32,6 +32,9 @@ export type SourceRequest = {
|
|
|
32
32
|
ref: Ref;
|
|
33
33
|
submodules: boolean;
|
|
34
34
|
detail?: (message: string) => void;
|
|
35
|
+
// Every line git writes. A first clone of a large repository is the slowest
|
|
36
|
+
// part of a build and it used to say nothing at all while it happened
|
|
37
|
+
output?: (line: string) => void;
|
|
35
38
|
};
|
|
36
39
|
|
|
37
40
|
// Where each kind lives. A tag is peeled, so an annotated one answers with the
|
|
@@ -135,7 +138,7 @@ async function clonedSource(
|
|
|
135
138
|
` mkdir -p '${host.cache}/mirrors'`,
|
|
136
139
|
` git clone --mirror '${repo}' '${mirror}'`,
|
|
137
140
|
"fi",
|
|
138
|
-
]);
|
|
141
|
+
], request.output);
|
|
139
142
|
|
|
140
143
|
const resolved = await run(host, `resolving ${name}`, [
|
|
141
144
|
`git -C '${mirror}' rev-parse '${REVISION[kind](name)}'`,
|
|
@@ -160,7 +163,7 @@ async function clonedSource(
|
|
|
160
163
|
// Leaves the submodules alone: they are tracked, and clean only removes
|
|
161
164
|
// what is not
|
|
162
165
|
`git -C '${path}' clean -ffdx`,
|
|
163
|
-
]);
|
|
166
|
+
], request.output);
|
|
164
167
|
|
|
165
168
|
if (request.submodules) {
|
|
166
169
|
// --remote follows the branch named in .gitmodules rather than the commit
|
|
@@ -169,15 +172,21 @@ async function clonedSource(
|
|
|
169
172
|
await run(host, "updating submodules", [
|
|
170
173
|
`git -C '${path}' submodule sync --recursive`,
|
|
171
174
|
`git -C '${path}' submodule update --init --remote --recursive`,
|
|
172
|
-
]);
|
|
175
|
+
], request.output);
|
|
173
176
|
}
|
|
174
177
|
|
|
175
178
|
return { release, tree: path };
|
|
176
179
|
}
|
|
177
180
|
|
|
178
|
-
async function run(
|
|
181
|
+
async function run(
|
|
182
|
+
host: Host,
|
|
183
|
+
what: string,
|
|
184
|
+
script: string[],
|
|
185
|
+
output?: (line: string) => void,
|
|
186
|
+
) {
|
|
179
187
|
const result = await host.sh(
|
|
180
188
|
[`export GIT_SSH_COMMAND='${GIT_SSH}'`, "set -e", ...script].join("\n"),
|
|
189
|
+
output,
|
|
181
190
|
);
|
|
182
191
|
|
|
183
192
|
if (result.code === 0) return result;
|
package/src/steps.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { environmentOf } from "./config.js";
|
|
2
1
|
import { envFlags } from "./environment.js";
|
|
3
2
|
import type { Built, Plan, Step } from "./pipeline.js";
|
|
4
3
|
import type { Topology } from "./topology.js";
|
|
@@ -30,10 +29,6 @@ type MigrateOptions = {
|
|
|
30
29
|
// already reach whatever the app's environment file points at. A database
|
|
31
30
|
// this deployment runs as a service is on "deployment" instead
|
|
32
31
|
network?: StepNetwork;
|
|
33
|
-
// Written when the step reached its database through a forward. It runs on
|
|
34
|
-
// the deploy host now, which is the machine that bastion named, so nothing
|
|
35
|
-
// is forwarded and the deploy refuses a bastion that is anything else
|
|
36
|
-
tunnel?: { bastion: string; from: string; alias?: string; port?: number };
|
|
37
32
|
};
|
|
38
33
|
|
|
39
34
|
// An ordinary step at an ordinary point. Hung before the swap, it runs while
|
|
@@ -43,7 +38,7 @@ export function migrate(options: MigrateOptions): Step<`swap:before:${string}`>
|
|
|
43
38
|
|
|
44
39
|
return {
|
|
45
40
|
point: `swap:before:migrate-${options.app}`,
|
|
46
|
-
check: (plan) =>
|
|
41
|
+
check: (plan) => assertApp(plan, options),
|
|
47
42
|
|
|
48
43
|
run: async (input, context) => {
|
|
49
44
|
const image = builderOf(input, options.app);
|
|
@@ -73,19 +68,9 @@ export function migrate(options: MigrateOptions): Step<`swap:before:${string}`>
|
|
|
73
68
|
|
|
74
69
|
// Checked before the run starts, so a config naming an app that never kept a
|
|
75
70
|
// builder fails without having touched the host
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const bastion = environmentOf(plan.config, plan.environment)?.host?.bastion;
|
|
81
|
-
const tunnel = options.tunnel;
|
|
82
|
-
if (!tunnel || tunnel.bastion === bastion) return;
|
|
83
|
-
|
|
84
|
-
throw new Error(
|
|
85
|
-
`${options.app} tunnels its migration through ${tunnel.bastion}, ` +
|
|
86
|
-
`but ${plan.environment} deploys to ${bastion ?? "this machine"}. ` +
|
|
87
|
-
"The step runs on the deploy host, so they have to be the same",
|
|
88
|
-
);
|
|
71
|
+
function assertApp(plan: Plan, options: MigrateOptions) {
|
|
72
|
+
if (plan.config.apps.some((item) => item.name === options.app)) return;
|
|
73
|
+
throw new Error(`${options.app} names no app in this deployment`);
|
|
89
74
|
}
|
|
90
75
|
|
|
91
76
|
// A deployment may replace the build step, and a command hung on the pipeline
|
package/src/types.ts
CHANGED
|
@@ -26,6 +26,16 @@ export type Environment = {
|
|
|
26
26
|
// derives. For a database or a legacy service that has no DNS the apps can
|
|
27
27
|
// use, and whose address is what differs between environments
|
|
28
28
|
extraHosts?: Record<string, string>;
|
|
29
|
+
// App name to the items this environment reads, after the app's own. Which
|
|
30
|
+
// item is usually what differs between staging and production, and the rest
|
|
31
|
+
// of what differs already lives here
|
|
32
|
+
secrets?: Record<string, SecretRefs>;
|
|
33
|
+
// App name to container path to item, laid over the app's own files
|
|
34
|
+
files?: Record<string, Record<string, SecretRef>>;
|
|
35
|
+
// Steps for this environment alone. One at a point the deployment already
|
|
36
|
+
// fills replaces it here, where it stood. One only this environment has runs
|
|
37
|
+
// ahead of the deployment's, so a snapshot lands above the migration
|
|
38
|
+
steps?: AnyStep[];
|
|
29
39
|
};
|
|
30
40
|
|
|
31
41
|
// Where a step's container is attached. "host" is the deploy host's own stack,
|
|
@@ -68,6 +78,22 @@ export type SecretRef = {
|
|
|
68
78
|
// One entry, or several merged in order. A key set by a later ref wins
|
|
69
79
|
export type SecretRefs = SecretRef | SecretRef[];
|
|
70
80
|
|
|
81
|
+
// The proxy every app's route is resolved by. Written with nginx(), because
|
|
82
|
+
// what redkite derives is the server block and the upstreams and what this
|
|
83
|
+
// says is everything around them
|
|
84
|
+
export type ProxySpec = {
|
|
85
|
+
// Image it runs, pinned rather than floating
|
|
86
|
+
image?: string;
|
|
87
|
+
// Largest request body it accepts before answering 413
|
|
88
|
+
maxBodySize?: string;
|
|
89
|
+
// Lines put inside the server block, above the locations. For what belongs
|
|
90
|
+
// to the whole server: a redirect, a rate limit zone, an error page
|
|
91
|
+
server?: string[];
|
|
92
|
+
// Lines put inside every location, below what redkite sets, so one of these
|
|
93
|
+
// replaces a header rather than being replaced by it
|
|
94
|
+
location?: string[];
|
|
95
|
+
};
|
|
96
|
+
|
|
71
97
|
export type ServiceSpec = {
|
|
72
98
|
// Identifies the service, and becomes part of its container name
|
|
73
99
|
name: string;
|
|
@@ -239,10 +265,9 @@ export type Deployment = {
|
|
|
239
265
|
// them: the thing that differs between staging and production is a file, not
|
|
240
266
|
// a key several levels down a literal
|
|
241
267
|
environments?: Record<string, Environment>;
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
proxyImage?: string;
|
|
268
|
+
// The derived proxy, which is not something a deployment lists: apps carry
|
|
269
|
+
// routes, routes need something to resolve them, so there is exactly one
|
|
270
|
+
proxy?: ProxySpec;
|
|
246
271
|
// Long-lived containers shared by the apps, not rebuilt on every deploy.
|
|
247
272
|
// The proxy is not one of them: apps with routes imply exactly one, so it is
|
|
248
273
|
// derived rather than listed
|
package/dist/nginx.d.ts
DELETED
package/dist/nginx.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nginx.d.ts","sourceRoot":"","sources":["../src/nginx.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,QAAQ,EAAE,MAAM,eAAe,CAAC;AAsB3D,eAAO,MAAM,WAAW,OAAO,CAAC;AAEhC,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,UAoBnE"}
|
package/dist/nginx.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// The nginx configuration is a function of the app list. Header policy is set
|
|
2
|
-
// here once rather than per location, which is what let the two blocks drift
|
|
3
|
-
// apart and lose X-Forwarded-For on one of them.
|
|
4
|
-
const FORWARDING = [
|
|
5
|
-
"proxy_set_header Host $http_host;",
|
|
6
|
-
"proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;",
|
|
7
|
-
"proxy_set_header X-Forwarded-Host $http_host;",
|
|
8
|
-
"proxy_set_header X-Forwarded-Proto $scheme;",
|
|
9
|
-
];
|
|
10
|
-
const FAILOVER = [
|
|
11
|
-
"proxy_connect_timeout 2s;",
|
|
12
|
-
"proxy_read_timeout 30s;",
|
|
13
|
-
"proxy_next_upstream error timeout http_502 http_503;",
|
|
14
|
-
"proxy_next_upstream_tries 2;",
|
|
15
|
-
];
|
|
16
|
-
// The port the proxy listens on inside its container. The published port maps
|
|
17
|
-
// onto this one, so both sides have to agree and only one of them may say it
|
|
18
|
-
export const LISTEN_PORT = 3000;
|
|
19
|
-
export function renderNginx(topology, maxBodySize) {
|
|
20
|
-
const upstreams = topology.apps.map((app) => upstream(app)).join("\n\n");
|
|
21
|
-
// Longest route first, so "/api/" is not shadowed by the "/" catch-all
|
|
22
|
-
const ordered = [...topology.apps].sort((a, b) => b.route.length - a.route.length);
|
|
23
|
-
const locations = ordered.map((app) => location(app)).join("\n\n");
|
|
24
|
-
return `resolver 127.0.0.1 valid=5s;
|
|
25
|
-
|
|
26
|
-
${upstreams}
|
|
27
|
-
|
|
28
|
-
server {
|
|
29
|
-
listen ${LISTEN_PORT};
|
|
30
|
-
merge_slashes off;
|
|
31
|
-
|
|
32
|
-
client_max_body_size ${maxBodySize ?? "1M"};
|
|
33
|
-
|
|
34
|
-
${locations}
|
|
35
|
-
}
|
|
36
|
-
`;
|
|
37
|
-
}
|
|
38
|
-
function upstream(app) {
|
|
39
|
-
return `upstream ${app.name} {
|
|
40
|
-
server ${app.container}:${app.port} max_fails=1 fail_timeout=2s;
|
|
41
|
-
server ${app.retired}:${app.port} backup;
|
|
42
|
-
}`;
|
|
43
|
-
}
|
|
44
|
-
function location(app) {
|
|
45
|
-
// A trailing slash on proxy_pass strips the location prefix. The catch-all
|
|
46
|
-
// must not carry one, a mounted route must
|
|
47
|
-
const target = app.route === "/" ? `http://${app.name}` : `http://${app.name}/`;
|
|
48
|
-
const body = [`proxy_pass ${target};`, ...FAILOVER, ...FORWARDING];
|
|
49
|
-
return ` location ${app.route} {
|
|
50
|
-
${body.map((line) => ` ${line}`).join("\n")}
|
|
51
|
-
}`;
|
|
52
|
-
}
|
|
53
|
-
//# sourceMappingURL=nginx.js.map
|
package/dist/nginx.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nginx.js","sourceRoot":"","sources":["../src/nginx.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,6EAA6E;AAC7E,iDAAiD;AAEjD,MAAM,UAAU,GAAG;IACjB,mCAAmC;IACnC,8DAA8D;IAC9D,+CAA+C;IAC/C,6CAA6C;CAC9C,CAAC;AAEF,MAAM,QAAQ,GAAG;IACf,2BAA2B;IAC3B,yBAAyB;IACzB,sDAAsD;IACtD,8BAA8B;CAC/B,CAAC;AAEF,8EAA8E;AAC9E,6EAA6E;AAC7E,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC;AAEhC,MAAM,UAAU,WAAW,CAAC,QAAkB,EAAE,WAAoB;IAElE,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzE,uEAAuE;IACvE,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACnF,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEnE,OAAO;;EAEP,SAAS;;;aAGE,WAAW;;;2BAGG,WAAW,IAAI,IAAI;;EAE5C,SAAS;;CAEV,CAAC;AACF,CAAC;AAED,SAAS,QAAQ,CAAC,GAAgB;IAChC,OAAO,YAAY,GAAG,CAAC,IAAI;aAChB,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,IAAI;aACzB,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI;EAClC,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,GAAgB;IAChC,2EAA2E;IAC3E,2CAA2C;IAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,IAAI,GAAG,CAAC;IAChF,MAAM,IAAI,GAAG,CAAC,cAAc,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,UAAU,CAAC,CAAC;IAEnE,OAAO,gBAAgB,GAAG,CAAC,KAAK;EAChC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;MAC5C,CAAC;AACP,CAAC"}
|
package/src/nginx.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type { AppTopology, Topology } from "./topology.js";
|
|
2
|
-
|
|
3
|
-
// The nginx configuration is a function of the app list. Header policy is set
|
|
4
|
-
// here once rather than per location, which is what let the two blocks drift
|
|
5
|
-
// apart and lose X-Forwarded-For on one of them.
|
|
6
|
-
|
|
7
|
-
const FORWARDING = [
|
|
8
|
-
"proxy_set_header Host $http_host;",
|
|
9
|
-
"proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;",
|
|
10
|
-
"proxy_set_header X-Forwarded-Host $http_host;",
|
|
11
|
-
"proxy_set_header X-Forwarded-Proto $scheme;",
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
const FAILOVER = [
|
|
15
|
-
"proxy_connect_timeout 2s;",
|
|
16
|
-
"proxy_read_timeout 30s;",
|
|
17
|
-
"proxy_next_upstream error timeout http_502 http_503;",
|
|
18
|
-
"proxy_next_upstream_tries 2;",
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
// The port the proxy listens on inside its container. The published port maps
|
|
22
|
-
// onto this one, so both sides have to agree and only one of them may say it
|
|
23
|
-
export const LISTEN_PORT = 3000;
|
|
24
|
-
|
|
25
|
-
export function renderNginx(topology: Topology, maxBodySize?: string) {
|
|
26
|
-
|
|
27
|
-
const upstreams = topology.apps.map((app) => upstream(app)).join("\n\n");
|
|
28
|
-
// Longest route first, so "/api/" is not shadowed by the "/" catch-all
|
|
29
|
-
const ordered = [...topology.apps].sort((a, b) => b.route.length - a.route.length);
|
|
30
|
-
const locations = ordered.map((app) => location(app)).join("\n\n");
|
|
31
|
-
|
|
32
|
-
return `resolver 127.0.0.1 valid=5s;
|
|
33
|
-
|
|
34
|
-
${upstreams}
|
|
35
|
-
|
|
36
|
-
server {
|
|
37
|
-
listen ${LISTEN_PORT};
|
|
38
|
-
merge_slashes off;
|
|
39
|
-
|
|
40
|
-
client_max_body_size ${maxBodySize ?? "1M"};
|
|
41
|
-
|
|
42
|
-
${locations}
|
|
43
|
-
}
|
|
44
|
-
`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function upstream(app: AppTopology) {
|
|
48
|
-
return `upstream ${app.name} {
|
|
49
|
-
server ${app.container}:${app.port} max_fails=1 fail_timeout=2s;
|
|
50
|
-
server ${app.retired}:${app.port} backup;
|
|
51
|
-
}`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function location(app: AppTopology) {
|
|
55
|
-
// A trailing slash on proxy_pass strips the location prefix. The catch-all
|
|
56
|
-
// must not carry one, a mounted route must
|
|
57
|
-
const target = app.route === "/" ? `http://${app.name}` : `http://${app.name}/`;
|
|
58
|
-
const body = [`proxy_pass ${target};`, ...FAILOVER, ...FORWARDING];
|
|
59
|
-
|
|
60
|
-
return ` location ${app.route} {
|
|
61
|
-
${body.map((line) => ` ${line}`).join("\n")}
|
|
62
|
-
}`;
|
|
63
|
-
}
|