redkite 0.1.6
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/LICENSE +21 -0
- package/README.md +819 -0
- package/bin/redkite.mjs +12 -0
- package/dist/build.d.ts +27 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +132 -0
- package/dist/build.js.map +1 -0
- package/dist/checks.d.ts +4 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +49 -0
- package/dist/checks.js.map +1 -0
- package/dist/cli/agent.d.ts +2 -0
- package/dist/cli/agent.d.ts.map +1 -0
- package/dist/cli/agent.js +24 -0
- package/dist/cli/agent.js.map +1 -0
- package/dist/cli/config.d.ts +5 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +217 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +438 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/log.d.ts +9 -0
- package/dist/cli/log.d.ts.map +1 -0
- package/dist/cli/log.js +128 -0
- package/dist/cli/log.js.map +1 -0
- package/dist/cli/screen.d.ts +34 -0
- package/dist/cli/screen.d.ts.map +1 -0
- package/dist/cli/screen.js +298 -0
- package/dist/cli/screen.js.map +1 -0
- package/dist/cli/viewer.d.ts +9 -0
- package/dist/cli/viewer.d.ts.map +1 -0
- package/dist/cli/viewer.js +139 -0
- package/dist/cli/viewer.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/deploy.d.ts +19 -0
- package/dist/deploy.d.ts.map +1 -0
- package/dist/deploy.js +275 -0
- package/dist/deploy.js.map +1 -0
- package/dist/docker.d.ts +96 -0
- package/dist/docker.d.ts.map +1 -0
- package/dist/docker.js +325 -0
- package/dist/docker.js.map +1 -0
- package/dist/dockerfile.d.ts +14 -0
- package/dist/dockerfile.d.ts.map +1 -0
- package/dist/dockerfile.js +160 -0
- package/dist/dockerfile.js.map +1 -0
- package/dist/health.d.ts +13 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +54 -0
- package/dist/health.js.map +1 -0
- package/dist/host.d.ts +21 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +27 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +5 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +45 -0
- package/dist/layout.js.map +1 -0
- package/dist/localHost.d.ts +7 -0
- package/dist/localHost.d.ts.map +1 -0
- package/dist/localHost.js +32 -0
- package/dist/localHost.js.map +1 -0
- package/dist/log.d.ts +14 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +15 -0
- package/dist/log.js.map +1 -0
- package/dist/nginx.d.ts +4 -0
- package/dist/nginx.d.ts.map +1 -0
- package/dist/nginx.js +53 -0
- package/dist/nginx.js.map +1 -0
- package/dist/pipeline.d.ts +103 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +142 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/presets/index.d.ts +3 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +3 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/presets/nextApp.d.ts +15 -0
- package/dist/presets/nextApp.d.ts.map +1 -0
- package/dist/presets/nextApp.js +53 -0
- package/dist/presets/nextApp.js.map +1 -0
- package/dist/presets/nodeApp.d.ts +19 -0
- package/dist/presets/nodeApp.d.ts.map +1 -0
- package/dist/presets/nodeApp.js +29 -0
- package/dist/presets/nodeApp.js.map +1 -0
- package/dist/secrets/bitwarden.d.ts +3 -0
- package/dist/secrets/bitwarden.d.ts.map +1 -0
- package/dist/secrets/bitwarden.js +6 -0
- package/dist/secrets/bitwarden.js.map +1 -0
- package/dist/secrets/refs.d.ts +9 -0
- package/dist/secrets/refs.d.ts.map +1 -0
- package/dist/secrets/refs.js +20 -0
- package/dist/secrets/refs.js.map +1 -0
- package/dist/secrets/store.d.ts +9 -0
- package/dist/secrets/store.d.ts.map +1 -0
- package/dist/secrets/store.js +104 -0
- package/dist/secrets/store.js.map +1 -0
- package/dist/services/ensure.d.ts +18 -0
- package/dist/services/ensure.d.ts.map +1 -0
- package/dist/services/ensure.js +93 -0
- package/dist/services/ensure.js.map +1 -0
- package/dist/services/index.d.ts +19 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +27 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/planned.d.ts +20 -0
- package/dist/services/planned.d.ts.map +1 -0
- package/dist/services/planned.js +77 -0
- package/dist/services/planned.js.map +1 -0
- package/dist/shell.d.ts +11 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +65 -0
- package/dist/shell.js.map +1 -0
- package/dist/source.d.ts +16 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +108 -0
- package/dist/source.js.map +1 -0
- package/dist/sshHost.d.ts +15 -0
- package/dist/sshHost.d.ts.map +1 -0
- package/dist/sshHost.js +91 -0
- package/dist/sshHost.js.map +1 -0
- package/dist/steps.d.ts +27 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +69 -0
- package/dist/steps.js.map +1 -0
- package/dist/topology.d.ts +40 -0
- package/dist/topology.d.ts.map +1 -0
- package/dist/topology.js +113 -0
- package/dist/topology.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
- package/src/build.ts +236 -0
- package/src/checks.ts +65 -0
- package/src/cli/agent.ts +28 -0
- package/src/cli/config.ts +275 -0
- package/src/cli/index.ts +559 -0
- package/src/cli/log.ts +168 -0
- package/src/cli/screen.ts +409 -0
- package/src/cli/viewer.ts +192 -0
- package/src/config.ts +90 -0
- package/src/deploy.ts +409 -0
- package/src/docker.ts +433 -0
- package/src/dockerfile.ts +228 -0
- package/src/health.ts +83 -0
- package/src/host.ts +54 -0
- package/src/index.ts +25 -0
- package/src/layout.ts +41 -0
- package/src/localHost.ts +50 -0
- package/src/log.ts +38 -0
- package/src/nginx.ts +63 -0
- package/src/pipeline.ts +335 -0
- package/src/presets/index.ts +2 -0
- package/src/presets/nextApp.ts +70 -0
- package/src/presets/nodeApp.ts +46 -0
- package/src/secrets/bitwarden.ts +7 -0
- package/src/secrets/refs.ts +32 -0
- package/src/secrets/store.ts +147 -0
- package/src/services/ensure.ts +160 -0
- package/src/services/index.ts +48 -0
- package/src/services/planned.ts +119 -0
- package/src/shell.ts +95 -0
- package/src/source.ts +171 -0
- package/src/sshHost.ts +145 -0
- package/src/steps.ts +101 -0
- package/src/topology.ts +199 -0
- package/src/types.ts +243 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { nodeApp } from "./nodeApp.js";
|
|
2
|
+
// The standalone layout is three directories that have to land in the right
|
|
3
|
+
// places, which is the only part of a Next build that is not a node build
|
|
4
|
+
export function nextApp(options = {}) {
|
|
5
|
+
const port = options.port ?? 3000;
|
|
6
|
+
const standalone = options.standalone ?? true;
|
|
7
|
+
const shared = {
|
|
8
|
+
builder: options.builder ?? "24-alpine",
|
|
9
|
+
runtime: options.runtime ?? "22-alpine",
|
|
10
|
+
steps: options.steps ?? ["yarn build"],
|
|
11
|
+
dependencies: options.dependencies,
|
|
12
|
+
};
|
|
13
|
+
// Relative, because the runtime starts at the app's root and a monorepo puts
|
|
14
|
+
// that under its own directory rather than at the top of the image
|
|
15
|
+
const start = (command) => ["sh", "-c", `HOSTNAME=0.0.0.0 PORT=${port} ${command}`];
|
|
16
|
+
if (!standalone) {
|
|
17
|
+
return {
|
|
18
|
+
...nodeApp({
|
|
19
|
+
...shared,
|
|
20
|
+
// The whole tree, because next start reads the source layout it built
|
|
21
|
+
// from and resolves its own dependencies at runtime
|
|
22
|
+
output: "/app",
|
|
23
|
+
// npx resolves the binary from node_modules/.bin upwards, so this
|
|
24
|
+
// finds it whether a workspace hoisted it or the app owns it
|
|
25
|
+
entrypoint: start("npx --no-install next start"),
|
|
26
|
+
// Without the modules cache, so node_modules is a layer of the image
|
|
27
|
+
// rather than a mount the runtime stage never sees
|
|
28
|
+
caches: ["yarn", "npm", "next-app"],
|
|
29
|
+
}),
|
|
30
|
+
preset: "nextApp",
|
|
31
|
+
keepsLayout: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
...nodeApp({
|
|
36
|
+
...shared,
|
|
37
|
+
output: "/app/.next/standalone",
|
|
38
|
+
// static is what the build produced and the server serves. public is
|
|
39
|
+
// whatever the repository put there, and plenty of apps have none
|
|
40
|
+
carry: ["/app/.next/static", { path: "/app/public", optional: true }],
|
|
41
|
+
entrypoint: start("node server.js"),
|
|
42
|
+
// app-modules is what makes the build fast: node_modules on a mount
|
|
43
|
+
// rather than in a layer, which the standalone tree makes safe by
|
|
44
|
+
// carrying its own copy of everything the server reaches
|
|
45
|
+
caches: ["yarn", "npm", "modules", "app-modules", "next-app"],
|
|
46
|
+
}),
|
|
47
|
+
preset: "nextApp",
|
|
48
|
+
// The standalone tree traces from the workspace root, so an app in a
|
|
49
|
+
// subdirectory arrives at that subdirectory and not at the top of it
|
|
50
|
+
keepsLayout: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=nextApp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextApp.js","sourceRoot":"","sources":["../../src/presets/nextApp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAavC,4EAA4E;AAC5E,0EAA0E;AAC1E,MAAM,UAAU,OAAO,CAAC,UAA0B,EAAE;IAClD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC;IAClC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC;IAE9C,MAAM,MAAM,GAAG;QACb,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,WAAW;QACvC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,WAAW;QACvC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,YAAY,CAAC;QACtC,YAAY,EAAE,OAAO,CAAC,YAAY;KACnC,CAAC;IAEF,6EAA6E;IAC7E,mEAAmE;IACnE,MAAM,KAAK,GAAG,CAAC,OAAe,EAAE,EAAE,CAChC,CAAC,IAAI,EAAE,IAAI,EAAE,yBAAyB,IAAI,IAAI,OAAO,EAAE,CAAC,CAAC;IAE3D,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO;YACL,GAAG,OAAO,CAAC;gBACT,GAAG,MAAM;gBACT,sEAAsE;gBACtE,oDAAoD;gBACpD,MAAM,EAAE,MAAM;gBACd,kEAAkE;gBAClE,6DAA6D;gBAC7D,UAAU,EAAE,KAAK,CAAC,6BAA6B,CAAC;gBAChD,qEAAqE;gBACrE,mDAAmD;gBACnD,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC;aACpC,CAAC;YACF,MAAM,EAAE,SAAS;YACjB,WAAW,EAAE,IAAI;SAClB,CAAC;IACJ,CAAC;IAED,OAAO;QACL,GAAG,OAAO,CAAC;YACT,GAAG,MAAM;YACT,MAAM,EAAE,uBAAuB;YAC/B,qEAAqE;YACrE,kEAAkE;YAClE,KAAK,EAAE,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YACrE,UAAU,EAAE,KAAK,CAAC,gBAAgB,CAAC;YACnC,oEAAoE;YACpE,kEAAkE;YAClE,yDAAyD;YACzD,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,CAAC;SAC9D,CAAC;QACF,MAAM,EAAE,SAAS;QACjB,qEAAqE;QACrE,qEAAqE;QACrE,WAAW,EAAE,IAAI;KAClB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BuildSpec, CarryPath, SourcemapSpec } from "../types.js";
|
|
2
|
+
type NodeAppOptions = {
|
|
3
|
+
builder?: string;
|
|
4
|
+
runtime?: string;
|
|
5
|
+
steps: string[];
|
|
6
|
+
dependencies?: {
|
|
7
|
+
files: string[];
|
|
8
|
+
step: string;
|
|
9
|
+
} | false;
|
|
10
|
+
output: string;
|
|
11
|
+
entrypoint: string[];
|
|
12
|
+
carry?: CarryPath[];
|
|
13
|
+
submodules?: boolean;
|
|
14
|
+
caches?: string[];
|
|
15
|
+
sourcemaps?: SourcemapSpec;
|
|
16
|
+
};
|
|
17
|
+
export declare function nodeApp(options: NodeAppOptions): BuildSpec;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=nodeApp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeApp.d.ts","sourceRoot":"","sources":["../../src/presets/nodeApp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEvE,KAAK,cAAc,GAAG;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAGhB,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,aAAa,CAAC;CAC5B,CAAC;AAEF,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,CA4B1D"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function nodeApp(options) {
|
|
2
|
+
return {
|
|
3
|
+
preset: "nodeApp",
|
|
4
|
+
builderImage: `node:${options.builder ?? "22-alpine"}`,
|
|
5
|
+
runtimeImage: `node:${options.runtime ?? "24-alpine"}`,
|
|
6
|
+
dependencies: options.dependencies === false
|
|
7
|
+
? undefined
|
|
8
|
+
: (options.dependencies ?? {
|
|
9
|
+
files: ["package.json", "yarn.lock"],
|
|
10
|
+
step: "yarn install --frozen-lockfile",
|
|
11
|
+
stripScripts: ["preinstall", "prepare", "postinstall"],
|
|
12
|
+
}),
|
|
13
|
+
steps: options.steps,
|
|
14
|
+
output: options.output,
|
|
15
|
+
carry: options.carry ?? [],
|
|
16
|
+
entrypoint: options.entrypoint,
|
|
17
|
+
// Not the app's own node_modules: a step after the build runs in this image
|
|
18
|
+
// without the mounts, and a migration that cannot find prisma is what a
|
|
19
|
+
// cache mount instead of a layer looks like from the outside
|
|
20
|
+
caches: options.caches ?? ["yarn", "npm", "modules"],
|
|
21
|
+
submodules: options.submodules ?? false,
|
|
22
|
+
// The checkout arrives complete, so this is what a build step itself needs
|
|
23
|
+
aptPackages: ["git"],
|
|
24
|
+
runtimePackages: ["curl"],
|
|
25
|
+
runtimeSteps: [],
|
|
26
|
+
sourcemaps: options.sourcemaps,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=nodeApp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeApp.js","sourceRoot":"","sources":["../../src/presets/nodeApp.ts"],"names":[],"mappings":"AAiBA,MAAM,UAAU,OAAO,CAAC,OAAuB;IAC7C,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,YAAY,EAAE,QAAQ,OAAO,CAAC,OAAO,IAAI,WAAW,EAAE;QACtD,YAAY,EAAE,QAAQ,OAAO,CAAC,OAAO,IAAI,WAAW,EAAE;QACtD,YAAY,EACV,OAAO,CAAC,YAAY,KAAK,KAAK;YAC5B,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,IAAI;gBACvB,KAAK,EAAE,CAAC,cAAc,EAAE,WAAW,CAAC;gBACpC,IAAI,EAAE,gCAAgC;gBACtC,YAAY,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,aAAa,CAAC;aACvD,CAAC;QACR,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE;QAC1B,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,4EAA4E;QAC5E,wEAAwE;QACxE,6DAA6D;QAC7D,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC;QACpD,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,KAAK;QACvC,2EAA2E;QAC3E,WAAW,EAAE,CAAC,KAAK,CAAC;QACpB,eAAe,EAAE,CAAC,MAAM,CAAC;QACzB,YAAY,EAAE,EAAE;QAChB,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bitwarden.d.ts","sourceRoot":"","sources":["../../src/secrets/bitwarden.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAE/C"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Names an item in a Bitwarden vault. The id is not a secret, it is a pointer,
|
|
2
|
+
// so it belongs in the config while the credentials arrive at deploy time
|
|
3
|
+
export function bitwarden(id) {
|
|
4
|
+
return { provider: "bitwarden", id };
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=bitwarden.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bitwarden.js","sourceRoot":"","sources":["../../src/secrets/bitwarden.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,0EAA0E;AAC1E,MAAM,UAAU,SAAS,CAAC,EAAU;IAClC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SecretRef, SecretRefs } from "../types.js";
|
|
2
|
+
export type SecretStore = {
|
|
3
|
+
read(id: string): Promise<string>;
|
|
4
|
+
};
|
|
5
|
+
export type SecretStores = Record<string, SecretStore>;
|
|
6
|
+
export declare function listRefs(refs: SecretRefs | undefined): SecretRef[];
|
|
7
|
+
export declare function readRef(ref: SecretRef, stores: SecretStores): Promise<string>;
|
|
8
|
+
export declare function readEnv(refs: SecretRefs | undefined, stores: SecretStores): Promise<string>;
|
|
9
|
+
//# sourceMappingURL=refs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../../src/secrets/refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzD,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAEvD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,EAAE,CAGlE;AAED,wBAAsB,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,mBAMjE;AAKD,wBAAsB,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,EAAE,MAAM,EAAE,YAAY,mBAM/E"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function listRefs(refs) {
|
|
2
|
+
if (!refs)
|
|
3
|
+
return [];
|
|
4
|
+
return Array.isArray(refs) ? refs : [refs];
|
|
5
|
+
}
|
|
6
|
+
export async function readRef(ref, stores) {
|
|
7
|
+
const store = stores[ref.provider];
|
|
8
|
+
if (store)
|
|
9
|
+
return await store.read(ref.id);
|
|
10
|
+
const known = Object.keys(stores).join(", ") || "none";
|
|
11
|
+
throw new Error(`No store for provider ${ref.provider}, deploy was given ${known}`);
|
|
12
|
+
}
|
|
13
|
+
// Concatenated in the order written. Every dotenv parser builds an object as it
|
|
14
|
+
// reads, so a key that appears twice takes its later value, which is the
|
|
15
|
+
// precedence the config file reads as having
|
|
16
|
+
export async function readEnv(refs, stores) {
|
|
17
|
+
const contents = await Promise.all(listRefs(refs).map(async (ref) => await readRef(ref, stores)));
|
|
18
|
+
return contents.map((text) => (text.endsWith("\n") ? text : `${text}\n`)).join("");
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=refs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refs.js","sourceRoot":"","sources":["../../src/secrets/refs.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,QAAQ,CAAC,IAA4B;IACnD,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,GAAc,EAAE,MAAoB;IAChE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,KAAK;QAAE,OAAO,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAE3C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC;IACvD,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,CAAC,QAAQ,sBAAsB,KAAK,EAAE,CAAC,CAAC;AACtF,CAAC;AAED,gFAAgF;AAChF,yEAAyE;AACzE,6CAA6C;AAC7C,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAA4B,EAAE,MAAoB;IAC9E,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAC9D,CAAC;IAEF,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACrF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SecretStore } from "./refs.js";
|
|
2
|
+
export type BitwardenCredentials = {
|
|
3
|
+
clientId: string;
|
|
4
|
+
clientSecret: string;
|
|
5
|
+
password: string;
|
|
6
|
+
detail?: (message: string) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function bitwardenStore(credentials: BitwardenCredentials): Promise<SecretStore>;
|
|
9
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/secrets/store.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAW7C,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,CAAC;AAUF,wBAAsB,cAAc,CAClC,WAAW,EAAE,oBAAoB,GAChC,OAAO,CAAC,WAAW,CAAC,CA8DtB"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
// One implementation of the interface deploy consumes. A second provider only
|
|
8
|
+
// has to satisfy read().
|
|
9
|
+
//
|
|
10
|
+
// The CLI runs here as an ordinary process. It used to run inside a container
|
|
11
|
+
// purely as a sandbox, which meant installing several thousand packages on
|
|
12
|
+
// every deploy to read three items.
|
|
13
|
+
const run = promisify(execFile);
|
|
14
|
+
// Used when bw is not already on PATH. Pinned, because an unpinned CLI is a
|
|
15
|
+
// different program on a machine that has never run a deploy before
|
|
16
|
+
const CLI = "@bitwarden/cli@2026.4.2";
|
|
17
|
+
// Where that install lands. Keyed by the pinned version, so bumping it installs
|
|
18
|
+
// beside the old one rather than over a vault command that is mid-flight
|
|
19
|
+
const CLIS = join(homedir(), ".cache", "redkite", "cli");
|
|
20
|
+
export async function bitwardenStore(credentials) {
|
|
21
|
+
const detail = credentials.detail ?? (() => { });
|
|
22
|
+
// Its own state directory, so a deploy neither reads nor disturbs whatever
|
|
23
|
+
// vault the person running it happens to be logged into
|
|
24
|
+
const appdata = join(homedir(), ".cache", "redkite", "bitwarden");
|
|
25
|
+
await mkdir(appdata, { recursive: true });
|
|
26
|
+
const command = await resolveCli(detail);
|
|
27
|
+
const bw = async (args, env = {}) => await run(command.file, [...command.prefix, ...args], {
|
|
28
|
+
env: {
|
|
29
|
+
...process.env,
|
|
30
|
+
BITWARDENCLI_APPDATA_DIR: appdata,
|
|
31
|
+
...env,
|
|
32
|
+
},
|
|
33
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
34
|
+
});
|
|
35
|
+
detail("unlocking the vault");
|
|
36
|
+
// Already logged in is not an error, the session is what matters
|
|
37
|
+
await bw(["login", "--apikey"], {
|
|
38
|
+
BW_CLIENTID: credentials.clientId,
|
|
39
|
+
BW_CLIENTSECRET: credentials.clientSecret,
|
|
40
|
+
}).catch(() => undefined);
|
|
41
|
+
const unlocked = await bw(["unlock", "--passwordenv", "BW_PASSWORD", "--raw"], {
|
|
42
|
+
BW_PASSWORD: credentials.password,
|
|
43
|
+
}).catch((error) => {
|
|
44
|
+
throw new Error(`Could not unlock the Bitwarden vault: ${messageOf(error)}`);
|
|
45
|
+
});
|
|
46
|
+
const session = unlocked.stdout.trim();
|
|
47
|
+
if (!session)
|
|
48
|
+
throw new Error("Bitwarden unlocked without a session");
|
|
49
|
+
// An item added since the last deploy is not in the local vault otherwise,
|
|
50
|
+
// and bw get answers "not found" rather than fetching it
|
|
51
|
+
await bw(["sync"], { BW_SESSION: session }).catch(() => undefined);
|
|
52
|
+
// Fetched once each. A second read of the same item during a deploy is the
|
|
53
|
+
// same answer, and this avoids a process per call site
|
|
54
|
+
const cache = new Map();
|
|
55
|
+
const fetch = async (id) => {
|
|
56
|
+
const item = await bw(["get", "notes", id, "--raw"], {
|
|
57
|
+
BW_SESSION: session,
|
|
58
|
+
}).catch((error) => {
|
|
59
|
+
throw new Error(`Could not read ${id} from Bitwarden: ${messageOf(error)}`);
|
|
60
|
+
});
|
|
61
|
+
return item.stdout;
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
read: async (id) => {
|
|
65
|
+
const pending = cache.get(id) ?? fetch(id);
|
|
66
|
+
cache.set(id, pending);
|
|
67
|
+
return await pending;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// A machine with the CLI installed pays nothing. One without it installs the
|
|
72
|
+
// pinned version once, rather than resolving it again on every call below
|
|
73
|
+
async function resolveCli(detail) {
|
|
74
|
+
const override = process.env["REDKITE_BW_BIN"];
|
|
75
|
+
if (override)
|
|
76
|
+
return { file: override, prefix: [] };
|
|
77
|
+
const found = await run("bw", ["--version"]).then(() => true, () => false);
|
|
78
|
+
if (found)
|
|
79
|
+
return { file: "bw", prefix: [] };
|
|
80
|
+
return { file: await install(detail), prefix: [] };
|
|
81
|
+
}
|
|
82
|
+
// npx resolves the package again on every invocation, and unlocking a vault is
|
|
83
|
+
// three commands plus one per secret. That was most of a second, six times over
|
|
84
|
+
async function install(detail) {
|
|
85
|
+
const directory = join(CLIS, CLI.replace(/[^\w.]+/g, "-"));
|
|
86
|
+
const binary = join(directory, "node_modules", ".bin", "bw");
|
|
87
|
+
if (existsSync(binary))
|
|
88
|
+
return binary;
|
|
89
|
+
detail(`installing ${CLI}, once for this machine`);
|
|
90
|
+
await mkdir(directory, { recursive: true });
|
|
91
|
+
await run("npm", ["install", "--prefix", directory, "--no-save", "--no-audit", "--no-fund", CLI], { maxBuffer: 32 * 1024 * 1024 }).catch((error) => {
|
|
92
|
+
throw new Error(`Could not install ${CLI}: ${messageOf(error)}`);
|
|
93
|
+
});
|
|
94
|
+
return binary;
|
|
95
|
+
}
|
|
96
|
+
function messageOf(error) {
|
|
97
|
+
if (error instanceof Error && "stderr" in error) {
|
|
98
|
+
const stderr = String(error.stderr).trim();
|
|
99
|
+
if (stderr)
|
|
100
|
+
return stderr;
|
|
101
|
+
}
|
|
102
|
+
return error instanceof Error ? error.message : String(error);
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/secrets/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Docker } from "../docker.js";
|
|
2
|
+
import type { Host } from "../host.js";
|
|
3
|
+
import type { Log } from "../log.js";
|
|
4
|
+
import { type SecretStores } from "../secrets/refs.js";
|
|
5
|
+
import type { ServiceTopology, Topology } from "../topology.js";
|
|
6
|
+
import type { ServiceSpec } from "../types.js";
|
|
7
|
+
export type EnsureContext = {
|
|
8
|
+
host: Host;
|
|
9
|
+
docker: Docker;
|
|
10
|
+
topology: Topology;
|
|
11
|
+
files: Record<string, string>;
|
|
12
|
+
secrets: SecretStores;
|
|
13
|
+
log: Log;
|
|
14
|
+
publish?: number;
|
|
15
|
+
};
|
|
16
|
+
export type Ensured = "adopted" | "started" | "created" | "recreated";
|
|
17
|
+
export declare function ensureService(spec: ServiceSpec, service: ServiceTopology, context: EnsureContext): Promise<Ensured>;
|
|
18
|
+
//# sourceMappingURL=ensure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensure.d.ts","sourceRoot":"","sources":["../../src/services/ensure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IAEnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAG9B,OAAO,EAAE,YAAY,CAAC;IACtB,GAAG,EAAE,GAAG,CAAC;IAGT,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAIF,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAEtE,wBAAsB,aAAa,CACjC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,OAAO,CAAC,CA8BlB"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { SPEC_LABEL } from "../docker.js";
|
|
2
|
+
import { LISTEN_PORT } from "../nginx.js";
|
|
3
|
+
import { readEnv } from "../secrets/refs.js";
|
|
4
|
+
import { fingerprintOf } from "./planned.js";
|
|
5
|
+
export async function ensureService(spec, service, context) {
|
|
6
|
+
const { docker } = context;
|
|
7
|
+
const name = service.container;
|
|
8
|
+
const fingerprint = fingerprintOf(plannedFrom(spec, service, context), context.topology);
|
|
9
|
+
if (await docker.container.exists(name)) {
|
|
10
|
+
// A service outlives a deploy, so being there is usually the whole answer.
|
|
11
|
+
// Being there having been created from something else is not: a changed
|
|
12
|
+
// public port or a rendered config only reaches a container that is made
|
|
13
|
+
if (await docker.container.specOf(name) === fingerprint) {
|
|
14
|
+
if (await docker.container.isRunning(name))
|
|
15
|
+
return "adopted";
|
|
16
|
+
context.log.warn(`${name} was not running, starting it`);
|
|
17
|
+
await docker.container.start(name);
|
|
18
|
+
return "started";
|
|
19
|
+
}
|
|
20
|
+
context.log(`${name} is not what the deployment says, recreating it`);
|
|
21
|
+
await docker.container.stop(name);
|
|
22
|
+
await docker.container.remove(name);
|
|
23
|
+
await createService(spec, service, context, fingerprint);
|
|
24
|
+
await docker.container.start(name);
|
|
25
|
+
return "recreated";
|
|
26
|
+
}
|
|
27
|
+
context.log(`Creating ${name} at ${service.address}`);
|
|
28
|
+
await createService(spec, service, context, fingerprint);
|
|
29
|
+
await docker.container.start(name);
|
|
30
|
+
return "created";
|
|
31
|
+
}
|
|
32
|
+
// The same shape the plan compares against, assembled from what this call was
|
|
33
|
+
// handed. Two spellings of it would drift from each other rather than from the
|
|
34
|
+
// host, which is the one drift nothing here would report
|
|
35
|
+
function plannedFrom(spec, service, context) {
|
|
36
|
+
return { spec, service, files: context.files, publish: context.publish };
|
|
37
|
+
}
|
|
38
|
+
async function createService(spec, service, context, fingerprint) {
|
|
39
|
+
const { docker, topology } = context;
|
|
40
|
+
const name = service.container;
|
|
41
|
+
await buildImage(spec, name, context);
|
|
42
|
+
const builder = docker.container
|
|
43
|
+
.builder()
|
|
44
|
+
.name(name)
|
|
45
|
+
.image(name)
|
|
46
|
+
.network(topology.network)
|
|
47
|
+
.label(SPEC_LABEL, fingerprint)
|
|
48
|
+
.ip(service.address);
|
|
49
|
+
if (spec.restart)
|
|
50
|
+
builder.restart(spec.restart);
|
|
51
|
+
for (const [name, value] of Object.entries(spec.environment ?? {})) {
|
|
52
|
+
builder.env(name, value);
|
|
53
|
+
}
|
|
54
|
+
// Written to the host and handed over as a file, so the value never appears
|
|
55
|
+
// in an argument list. Docker reads it when the container is created and
|
|
56
|
+
// keeps the values, so the file goes with the rest of the deploy's scratch
|
|
57
|
+
if (spec.secrets) {
|
|
58
|
+
const contents = await readEnv(spec.secrets, context.secrets);
|
|
59
|
+
builder.envFile(await context.host.write(`services/${name}/env`, contents));
|
|
60
|
+
}
|
|
61
|
+
for (const volume of service.volumes) {
|
|
62
|
+
builder.volume(volume.volume, volume.mountPath);
|
|
63
|
+
}
|
|
64
|
+
if (context.publish) {
|
|
65
|
+
for (const [host, ip] of Object.entries(topology.extraHosts)) {
|
|
66
|
+
builder.extraHost(host, ip);
|
|
67
|
+
}
|
|
68
|
+
builder.port(context.publish, LISTEN_PORT);
|
|
69
|
+
}
|
|
70
|
+
await builder.create();
|
|
71
|
+
}
|
|
72
|
+
// Three lines of Dockerfile on the host, in place of a container assembled in
|
|
73
|
+
// an engine, exported as a tarball and loaded back in
|
|
74
|
+
async function buildImage(spec, name, context) {
|
|
75
|
+
const { host, docker } = context;
|
|
76
|
+
const directory = `services/${name}`;
|
|
77
|
+
const copies = await Promise.all(Object.entries(context.files).map(async ([path, contents], index) => {
|
|
78
|
+
await host.write(`${directory}/file.${index}`, contents);
|
|
79
|
+
return `COPY file.${index} ${path}`;
|
|
80
|
+
}));
|
|
81
|
+
const dockerfile = await host.write(`${directory}/Dockerfile`, [
|
|
82
|
+
`FROM ${spec.image}`,
|
|
83
|
+
...copies,
|
|
84
|
+
...(context.publish ? [`EXPOSE ${LISTEN_PORT}`] : []),
|
|
85
|
+
"",
|
|
86
|
+
].join("\n"));
|
|
87
|
+
await docker.image.build({
|
|
88
|
+
context: `${host.directory}/${directory}`,
|
|
89
|
+
dockerfile,
|
|
90
|
+
tags: [name],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=ensure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensure.js","sourceRoot":"","sources":["../../src/services/ensure.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SecretRefs, ServiceSpec } from "../types.js";
|
|
2
|
+
type RedisOptions = {
|
|
3
|
+
image?: string;
|
|
4
|
+
alias?: string;
|
|
5
|
+
address?: number;
|
|
6
|
+
volumes?: Record<string, string>;
|
|
7
|
+
};
|
|
8
|
+
export declare function redis(options?: RedisOptions): ServiceSpec;
|
|
9
|
+
type PostgresOptions = {
|
|
10
|
+
secrets: SecretRefs;
|
|
11
|
+
image?: string;
|
|
12
|
+
alias?: string;
|
|
13
|
+
address?: number;
|
|
14
|
+
volumes?: Record<string, string>;
|
|
15
|
+
environment?: Record<string, string>;
|
|
16
|
+
};
|
|
17
|
+
export declare function postgres(options: PostgresOptions): ServiceSpec;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE3D,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF,wBAAgB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,WAAW,CAW7D;AAED,KAAK,eAAe,GAAG;IAGrB,OAAO,EAAE,UAAU,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,WAAW,CAa9D"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function redis(options = {}) {
|
|
2
|
+
return {
|
|
3
|
+
name: "redis",
|
|
4
|
+
image: options.image ?? "redis:7",
|
|
5
|
+
alias: options.alias ?? "redis",
|
|
6
|
+
restart: "always",
|
|
7
|
+
address: options.address,
|
|
8
|
+
// The image declares VOLUME /data, so without this the data lands in an
|
|
9
|
+
// anonymous volume that survives nothing and can be named by no one
|
|
10
|
+
volumes: options.volumes ?? { data: "/data" },
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function postgres(options) {
|
|
14
|
+
return {
|
|
15
|
+
name: "postgres",
|
|
16
|
+
image: options.image ?? "postgres:17-alpine",
|
|
17
|
+
alias: options.alias ?? "postgres",
|
|
18
|
+
restart: "always",
|
|
19
|
+
address: options.address,
|
|
20
|
+
secrets: options.secrets,
|
|
21
|
+
environment: options.environment,
|
|
22
|
+
// The image declares VOLUME on this path, so without naming it the data
|
|
23
|
+
// lands in an anonymous volume that survives nothing and nobody can name
|
|
24
|
+
volumes: options.volumes ?? { data: "/var/lib/postgresql/data" },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,KAAK,CAAC,UAAwB,EAAE;IAC9C,OAAO;QACL,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,SAAS;QACjC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,OAAO;QAC/B,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,wEAAwE;QACxE,oEAAoE;QACpE,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE;KAC9C,CAAC;AACJ,CAAC;AAcD,MAAM,UAAU,QAAQ,CAAC,OAAwB;IAC/C,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,oBAAoB;QAC5C,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,UAAU;QAClC,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,wEAAwE;QACxE,yEAAyE;QACzE,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,IAAI,EAAE,0BAA0B,EAAE;KACjE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Docker } from "../docker.js";
|
|
2
|
+
import type { Run } from "../pipeline.js";
|
|
3
|
+
import type { ServiceTopology, Topology } from "../topology.js";
|
|
4
|
+
import type { Deployment, ServiceSpec } from "../types.js";
|
|
5
|
+
export type PlannedService = {
|
|
6
|
+
spec: ServiceSpec;
|
|
7
|
+
service: ServiceTopology;
|
|
8
|
+
files: Record<string, string>;
|
|
9
|
+
publish?: number;
|
|
10
|
+
};
|
|
11
|
+
export declare function plannedServices(config: Deployment, topology: Topology, run?: Run): PlannedService[];
|
|
12
|
+
export declare function router(config: Deployment): ServiceSpec;
|
|
13
|
+
export declare function fingerprintOf(planned: PlannedService, topology: Topology): string;
|
|
14
|
+
export type Drift = {
|
|
15
|
+
container: string;
|
|
16
|
+
name: string;
|
|
17
|
+
reason: "missing" | "stopped" | "changed" | "unrecognised";
|
|
18
|
+
};
|
|
19
|
+
export declare function driftOf(planned: PlannedService[], topology: Topology, docker: Docker): Promise<Drift[]>;
|
|
20
|
+
//# sourceMappingURL=planned.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planned.d.ts","sourceRoot":"","sources":["../../src/services/planned.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO3D,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,eAAe,CAAC;IAEzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,QAAQ,EAClB,GAAG,GAAE,GAAc,GAClB,cAAc,EAAE,CAsBlB;AAID,wBAAgB,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,CAMtD;AAKD,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,UAkBxE;AAID,MAAM,MAAM,KAAK,GAAG;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,cAAc,CAAC;CAC5D,CAAC;AAEF,wBAAsB,OAAO,CAC3B,OAAO,EAAE,cAAc,EAAE,EACzB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,EAAE,CAAC,CASlB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { LISTEN_PORT, renderNginx } from "../nginx.js";
|
|
3
|
+
export function plannedServices(config, topology, run = "deploy") {
|
|
4
|
+
const rest = config.services.map((spec) => {
|
|
5
|
+
const service = topology.services.find((item) => item.name === spec.name);
|
|
6
|
+
if (!service)
|
|
7
|
+
throw new Error(`No topology for service ${spec.name}`);
|
|
8
|
+
return { spec, service, files: spec.files ?? {} };
|
|
9
|
+
});
|
|
10
|
+
// Nothing serves in a verify run, so a proxy there would resolve upstreams
|
|
11
|
+
// that were never created and publish a port for them
|
|
12
|
+
if (run === "verify")
|
|
13
|
+
return rest;
|
|
14
|
+
const proxy = {
|
|
15
|
+
spec: router(config),
|
|
16
|
+
service: topology.router,
|
|
17
|
+
files: {
|
|
18
|
+
"/etc/nginx/conf.d/default.conf": renderNginx(topology, config.maxBodySize),
|
|
19
|
+
},
|
|
20
|
+
publish: topology.publicPort,
|
|
21
|
+
};
|
|
22
|
+
return [proxy, ...rest];
|
|
23
|
+
}
|
|
24
|
+
// Not something a deployment lists. Apps carry routes, routes need a proxy to
|
|
25
|
+
// resolve them, and the one that renders them is this
|
|
26
|
+
export function router(config) {
|
|
27
|
+
return {
|
|
28
|
+
name: "nginx",
|
|
29
|
+
image: config.proxyImage ?? "nginx:stable",
|
|
30
|
+
restart: "always",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Everything a recreate would change, and nothing a deploy resolves. Secrets
|
|
34
|
+
// are named by their refs rather than their values: whether the config is the
|
|
35
|
+
// one running is a question a plan should answer without unlocking a vault
|
|
36
|
+
export function fingerprintOf(planned, topology) {
|
|
37
|
+
const { spec, service, files, publish } = planned;
|
|
38
|
+
const shape = {
|
|
39
|
+
image: spec.image,
|
|
40
|
+
restart: spec.restart,
|
|
41
|
+
address: service.address,
|
|
42
|
+
network: topology.network,
|
|
43
|
+
volumes: service.volumes,
|
|
44
|
+
environment: spec.environment,
|
|
45
|
+
secrets: spec.secrets,
|
|
46
|
+
files,
|
|
47
|
+
// The proxy alone, and the two things being published costs it
|
|
48
|
+
publish: publish && { host: publish, container: LISTEN_PORT },
|
|
49
|
+
extraHosts: publish ? topology.extraHosts : undefined,
|
|
50
|
+
};
|
|
51
|
+
return createHash("sha256").update(JSON.stringify(shape)).digest("hex").slice(0, 16);
|
|
52
|
+
}
|
|
53
|
+
export async function driftOf(planned, topology, docker) {
|
|
54
|
+
const drifted = [];
|
|
55
|
+
for (const item of planned) {
|
|
56
|
+
const reason = await reasonFor(item, topology, docker);
|
|
57
|
+
if (reason)
|
|
58
|
+
drifted.push({ container: item.service.container, name: item.spec.name, reason });
|
|
59
|
+
}
|
|
60
|
+
return drifted;
|
|
61
|
+
}
|
|
62
|
+
async function reasonFor(planned, topology, docker) {
|
|
63
|
+
const name = planned.service.container;
|
|
64
|
+
if (!(await docker.container.exists(name)))
|
|
65
|
+
return "missing";
|
|
66
|
+
const held = await docker.container.specOf(name);
|
|
67
|
+
// Created by hand, or by a redkite that did not record what it created from.
|
|
68
|
+
// Either way nothing here can say it matches, so it is rebuilt once
|
|
69
|
+
if (!held)
|
|
70
|
+
return "unrecognised";
|
|
71
|
+
if (held !== fingerprintOf(planned, topology))
|
|
72
|
+
return "changed";
|
|
73
|
+
if (!(await docker.container.isRunning(name)))
|
|
74
|
+
return "stopped";
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=planned.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planned.js","sourceRoot":"","sources":["../../src/services/planned.ts"],"names":[],"mappings":"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"}
|
package/dist/shell.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type OnLine, type Result } from "./host.js";
|
|
2
|
+
export type SpawnOptions = {
|
|
3
|
+
stdin?: string;
|
|
4
|
+
onLine?: OnLine;
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
};
|
|
7
|
+
export declare function signalEverything(name: NodeJS.Signals): number;
|
|
8
|
+
export declare function stillRunning(): number;
|
|
9
|
+
export declare function spawnCollect(command: string, args: string[], options?: SpawnOptions): Promise<Result>;
|
|
10
|
+
export declare function quote(value: string): string;
|
|
11
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAKvE,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAIhB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAQF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,UAUpD;AAED,wBAAgB,YAAY,WAE3B;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,MAAM,CAAC,CA8CjB;AAID,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,UAElC"}
|