okengine 0.10.3 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +76 -10
- package/site/content/docs/deployment/docker-swarm.mdx +48 -111
- package/site/content/docs/deployment/docker.mdx +43 -36
- package/site/content/docs/deployment/index.mdx +9 -9
- package/site/content/docs/deployment/kubernetes.mdx +4 -4
- package/site/content/docs/elements/ai.mdx +7 -7
- package/site/content/docs/elements/channel.mdx +9 -7
- package/site/content/docs/elements/clock.mdx +8 -8
- package/site/content/docs/elements/flow.mdx +46 -47
- package/site/content/docs/elements/gate.mdx +6 -5
- package/site/content/docs/elements/signal.mdx +2 -2
- package/site/content/docs/elements/store.mdx +71 -78
- package/site/content/docs/elements/vault.mdx +89 -70
- package/site/content/docs/get-started/basic-usage.mdx +8 -8
- package/site/content/docs/get-started/installation.mdx +36 -36
- package/site/content/docs/get-started/introduction.mdx +11 -0
- package/site/content/docs/get-started/why.mdx +2 -2
- package/site/content/docs/plugins/anonymous.mdx +1 -1
- package/site/content/docs/plugins/magic-link.mdx +11 -11
- package/site/content/docs/plugins/otp.mdx +2 -2
- package/site/content/docs/plugins/passkey.mdx +1 -1
- package/site/content/docs/plugins/two-factor.mdx +1 -1
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +1 -1
- package/site/content/docs/recipes/cockroachdb.mdx +2 -2
- package/site/content/docs/recipes/dragonfly.mdx +1 -1
- package/site/content/docs/recipes/index.mdx +1 -2
- package/site/content/docs/recipes/llama-cpp.mdx +21 -23
- package/site/content/docs/recipes/mailpit.mdx +11 -11
- package/site/content/docs/recipes/meilisearch.mdx +10 -10
- package/site/content/docs/recipes/meta.json +0 -1
- package/site/content/docs/recipes/ollama.mdx +1 -1
- package/site/content/docs/recipes/pgdog.mdx +12 -9
- package/site/content/docs/recipes/postgres.mdx +3 -3
- package/site/content/docs/recipes/redis.mdx +2 -2
- package/site/content/docs/recipes/rustfs.mdx +10 -7
- package/site/content/docs/recipes/sglang.mdx +1 -1
- package/site/content/docs/recipes/supabase-docker.mdx +1 -1
- package/site/content/docs/recipes/timescale.mdx +2 -2
- package/site/content/docs/recipes/valkey.mdx +1 -1
- package/site/content/docs/recipes/vllm.mdx +1 -1
- package/site/content/docs/recipes/yugabytedb.mdx +2 -2
- package/site/content/docs/reference/cli.md +61 -38
- package/site/content/docs/reference/configuration.mdx +145 -70
- package/site/content/docs/reference/environment-variables.mdx +36 -28
- package/site/content/docs/reference/errors.mdx +28 -29
- package/site/content/docs/reference/fx.mdx +11 -4
- package/site/content/docs/reference/i18n.mdx +17 -6
- package/site/content/docs/reference/index.mdx +5 -0
- package/site/content/docs/reference/meta.json +1 -0
- package/site/content/docs/reference/migrating-environments.mdx +158 -0
- package/site/content/docs/reference/plugins.mdx +2 -2
- package/src/auth/gate-auth.test.ts +12 -10
- package/src/cli/ai-setup/ai-setup.test.ts +27 -15
- package/src/cli/ai-setup/apply.ts +111 -15
- package/src/cli/ai-setup/catalog.ts +3 -3
- package/src/cli/ai-setup/index.ts +1 -1
- package/src/cli/ai-setup/prompts.ts +1 -1
- package/src/cli/ai-setup/recommend.test.ts +1 -1
- package/src/cli/ask-seed.test.ts +3 -3
- package/src/cli/build.ts +1 -1
- package/src/cli/db-auto-push.test.ts +6 -3
- package/src/cli/db-auto-push.ts +7 -5
- package/src/cli/db-seed.ts +7 -8
- package/src/cli/db.test.ts +70 -71
- package/src/cli/db.ts +86 -12
- package/src/cli/dev-controller.test.ts +35 -0
- package/src/cli/dev-controller.ts +298 -0
- package/src/cli/dev-controls.ts +11 -65
- package/src/cli/dev-db-push.test.ts +47 -15
- package/src/cli/dev-schema-sync.test.ts +11 -11
- package/src/cli/dev-schema-sync.ts +10 -10
- package/src/cli/dev-session-lock.test.ts +106 -0
- package/src/cli/dev-session-lock.ts +209 -0
- package/src/cli/dev.test.ts +166 -121
- package/src/cli/dev.ts +138 -192
- package/src/cli/doc-staleness.test.ts +3 -6
- package/src/cli/docker-clean.test.ts +33 -1
- package/src/cli/docker-clean.ts +28 -33
- package/src/cli/docker-cli.test.ts +12 -11
- package/src/cli/docker.ts +95 -20
- package/src/cli/doctor.test.ts +40 -0
- package/src/cli/doctor.ts +55 -15
- package/src/cli/drizzle-env.test.ts +11 -13
- package/src/cli/drizzle-env.ts +18 -11
- package/src/cli/ensure-drizzle-config.ts +11 -14
- package/src/cli/hero-meta.test.ts +15 -15
- package/src/cli/hero-meta.ts +10 -9
- package/src/cli/index.ts +36 -6
- package/src/cli/load-config.images.test.ts +16 -12
- package/src/cli/load-config.source.test.ts +50 -0
- package/src/cli/load-config.ts +34 -28
- package/src/cli/meilisearch-local.ts +1 -2
- package/src/cli/mode.ts +7 -58
- package/src/cli/registry.ts +175 -19
- package/src/cli/resolve-dev-sql-env.test.ts +8 -16
- package/src/cli/resolve-dev-sql-env.ts +13 -15
- package/src/cli/safe-defaults.test.ts +28 -10
- package/src/cli/schema.ts +65 -16
- package/src/cli/test.ts +58 -0
- package/src/cli/tui/App.tsx +252 -0
- package/src/cli/tui/DevLive.tsx +116 -0
- package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
- package/src/cli/tui/components/HeaderBar.tsx +67 -0
- package/src/cli/tui/components/LogStream.tsx +47 -0
- package/src/cli/tui/components/PanelCard.tsx +39 -0
- package/src/cli/tui/components/SelectRow.tsx +37 -0
- package/src/cli/tui/components/SlashPalette.tsx +172 -0
- package/src/cli/tui/components/Spinner.tsx +32 -0
- package/src/cli/tui/components/StatusBar.tsx +51 -0
- package/src/cli/tui/components/StatusDot.tsx +37 -0
- package/src/cli/tui/components/TabBar.tsx +49 -0
- package/src/cli/tui/components/Wordmark.tsx +43 -0
- package/src/cli/tui/format.ts +38 -0
- package/src/cli/tui/keys.test.ts +18 -0
- package/src/cli/tui/keys.ts +26 -0
- package/src/cli/tui/launch.test.ts +18 -0
- package/src/cli/tui/launch.ts +57 -0
- package/src/cli/tui/panels/Dashboard.tsx +201 -0
- package/src/cli/tui/panels/Database.tsx +198 -0
- package/src/cli/tui/panels/Dev.tsx +257 -0
- package/src/cli/tui/panels/Docker.tsx +357 -0
- package/src/cli/tui/panels/Navigator.test.ts +22 -0
- package/src/cli/tui/panels/Navigator.tsx +213 -0
- package/src/cli/tui/slash-catalog.test.ts +31 -0
- package/src/cli/tui/slash-catalog.ts +184 -0
- package/src/cli/tui/slash-run.ts +54 -0
- package/src/cli/tui/state/ports.ts +95 -0
- package/src/cli/tui/theme.ts +24 -0
- package/src/cli/vault-cmd.test.ts +402 -0
- package/src/cli/vault-cmd.ts +727 -69
- package/src/cli/vault-secure-input.test.ts +56 -0
- package/src/cli/vault-secure-input.ts +124 -0
- package/src/compiler/effects-infer.ts +7 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +3 -3
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
- package/src/compiler/fixtures/skyport.expected.json +1 -1
- package/src/compiler-entry.ts +7 -0
- package/src/config/define-config.test.ts +111 -47
- package/src/config/driver-defaults.test.ts +16 -23
- package/src/config/driver-defaults.ts +33 -63
- package/src/config/index.ts +169 -144
- package/src/config/resolve-driver.test.ts +14 -19
- package/src/console/server/app.ts +4 -2
- package/src/console/server/console.test.ts +58 -1
- package/src/console/server/flows.ts +22 -2
- package/src/console/server/index.ts +3 -0
- package/src/console/server/operator-db.test.ts +55 -13
- package/src/console/server/operator-db.ts +305 -263
- package/src/console/server/serve.ts +16 -7
- package/src/console/server/state.ts +31 -9
- package/src/console/server/vault.test.ts +120 -5
- package/src/console/server/vault.ts +208 -12
- package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
- package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
- package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
- package/src/console/ui/dist/index.html +2 -2
- package/src/console/ui/shell/client.ts +16 -0
- package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
- package/src/console/ui/vault/VaultA11yView.tsx +33 -1
- package/src/console/ui/vault/backend.test.ts +75 -0
- package/src/console/ui/vault/backend.ts +117 -0
- package/src/console/ui/vault/fixture.ts +21 -2
- package/src/console/ui/vault/index.ts +11 -1
- package/src/console/ui/vault/types.ts +26 -0
- package/src/docker/compose.ts +164 -61
- package/src/docker/derive.ts +106 -12
- package/src/docker/docker.test.ts +156 -110
- package/src/docker/helpers.ts +98 -0
- package/src/docker/images-config.test.ts +3 -8
- package/src/docker/index.ts +30 -5
- package/src/docker/recipes/index.ts +0 -3
- package/src/docker/recipes/llama-cpp.ts +4 -4
- package/src/docker/recipes/pgdog.ts +9 -2
- package/src/docker/resources.test.ts +71 -0
- package/src/docker/resources.ts +176 -0
- package/src/docker/stack-id.test.ts +11 -4
- package/src/docker/stack-id.ts +42 -32
- package/src/docker/stack.integration.test.ts +4 -15
- package/src/docker/types.ts +35 -3
- package/src/drivers/ai-ollama.integration.test.ts +2 -2
- package/src/drivers/conformance.test.ts +11 -12
- package/src/drivers/drizzle-dialect.test.ts +0 -4
- package/src/drivers/drizzle-dialect.ts +7 -13
- package/src/drivers/index.ts +24 -7
- package/src/drivers/journal-postgres.test.ts +3 -1
- package/src/drivers/journal-postgres.ts +4 -2
- package/src/drivers/meilisearch.ts +1 -1
- package/src/drivers/pglite.ts +28 -5
- package/src/drivers/types.ts +9 -11
- package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
- package/src/drivers/vault-aws-secrets-manager.ts +333 -0
- package/src/drivers/vault-builtin.test.ts +110 -0
- package/src/drivers/vault-builtin.ts +184 -0
- package/src/drivers/vault-driver-removal.test.ts +13 -0
- package/src/drivers/vault-managed.test.ts +95 -0
- package/src/drivers/vault-managed.ts +101 -25
- package/src/drivers/vault-types.ts +25 -5
- package/src/elements/ai/runtime.ts +1 -1
- package/src/elements/clock/chaos.test.ts +4 -1
- package/src/elements/clock/durable.ts +57 -4
- package/src/elements/clock.test.ts +13 -14
- package/src/elements/gate/config.ts +13 -3
- package/src/elements/gate/constants.ts +18 -0
- package/src/elements/gate/declare.ts +1 -1
- package/src/elements/gate/strategies.ts +2 -12
- package/src/elements/gate.test.ts +1 -1
- package/src/elements/gate.ts +2 -2
- package/src/elements/store/domain-ddl.test.ts +5 -12
- package/src/elements/store/emit-drizzle.ts +44 -19
- package/src/elements/store/index-boot.test.ts +15 -116
- package/src/elements/store/load-plugin-tables.ts +1 -1
- package/src/elements/store/resource-list-docs.fixture.ts +12 -6
- package/src/elements/store/resource-list-docs.test.ts +64 -44
- package/src/elements/store/resource.test.ts +17 -10
- package/src/elements/store/runtime.ts +9 -12
- package/src/elements/store/schema-decl.test.ts +16 -32
- package/src/elements/store/seed.test.ts +1 -2
- package/src/elements/store/seed.ts +7 -8
- package/src/elements/store/sql-condition.test.ts +2 -2
- package/src/elements/store/sql-session.test.ts +8 -10
- package/src/elements/store/sql-session.ts +10 -2
- package/src/elements/store.test.ts +13 -13
- package/src/elements/vault/audit.ts +233 -0
- package/src/elements/vault/boot-chain.test.ts +78 -0
- package/src/elements/vault/boot-chain.ts +43 -39
- package/src/elements/vault/builtin-adapter.test.ts +479 -0
- package/src/elements/vault/builtin-adapter.ts +1184 -0
- package/src/elements/vault/crypto.test.ts +237 -0
- package/src/elements/vault/crypto.ts +431 -0
- package/src/elements/vault/declare.ts +164 -3
- package/src/elements/vault/errors.ts +56 -0
- package/src/elements/vault/kms-unseal.test.ts +76 -0
- package/src/elements/vault/kms-unseal.ts +181 -0
- package/src/elements/vault/path.ts +90 -0
- package/src/elements/vault/resilience.test.ts +102 -0
- package/src/elements/vault/resilience.ts +107 -0
- package/src/elements/vault/runtime.ts +36 -4
- package/src/elements/vault/security-checklist.test.ts +130 -0
- package/src/elements/vault/storage.ts +448 -0
- package/src/elements/vault/types.ts +288 -0
- package/src/elements/vault/unseal.ts +150 -0
- package/src/elements/vault.test.ts +72 -4
- package/src/elements/vault.ts +91 -2
- package/src/full.ts +248 -0
- package/src/http.ts +41 -0
- package/src/i18n/interpolate-simple.ts +37 -0
- package/src/i18n/messages.ts +51 -4
- package/src/i18n-entry.ts +7 -0
- package/src/index.ts +35 -145
- package/src/journal-entry.ts +20 -0
- package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
- package/src/kernel/app-auth.ts +98 -0
- package/src/kernel/app.ts +77 -110
- package/src/kernel/auto-registry.test.ts +2 -2
- package/src/kernel/boot-bind/ai.test.ts +5 -5
- package/src/kernel/boot-bind/ai.ts +2 -1
- package/src/kernel/boot-bind/channel.test.ts +6 -6
- package/src/kernel/boot-bind/channel.ts +3 -10
- package/src/kernel/boot-bind/clock.ts +2 -2
- package/src/kernel/boot-bind/gate.ts +5 -9
- package/src/kernel/boot-bind/honor-config.test.ts +42 -42
- package/src/kernel/boot-bind/journal.ts +2 -2
- package/src/kernel/boot-bind/store.test.ts +43 -44
- package/src/kernel/boot-bind/store.ts +16 -41
- package/src/kernel/boot-bind/vault.ts +16 -1
- package/src/kernel/boot.test.ts +13 -19
- package/src/kernel/boot.ts +31 -19
- package/src/kernel/budget-entry.ts +1 -1
- package/src/kernel/compensate.test.ts +288 -1
- package/src/kernel/compensate.ts +126 -0
- package/src/kernel/concurrency.test.ts +10 -9
- package/src/kernel/effects-stamping.test.ts +27 -2
- package/src/kernel/element-registries.ts +5 -3
- package/src/kernel/flow.ts +4 -2
- package/src/kernel/fx.test.ts +119 -3
- package/src/kernel/fx.ts +215 -40
- package/src/kernel/index.ts +14 -0
- package/src/kernel/journal.ts +130 -7
- package/src/kernel/pipeline.test.ts +20 -6
- package/src/kernel/redacted.test.ts +35 -0
- package/src/kernel/redacted.ts +24 -2
- package/src/kernel/router/create-default.ts +16 -0
- package/src/kernel/router/create-edge.ts +18 -0
- package/src/kernel/router/create.ts +23 -0
- package/src/kernel/router/index.ts +23 -0
- package/src/kernel/router/linear.ts +88 -0
- package/src/kernel/router/regexp.ts +188 -0
- package/src/kernel/router/smart.ts +120 -0
- package/src/kernel/router/trie.ts +108 -0
- package/src/kernel/router/types.ts +56 -0
- package/src/kernel/router.ts +18 -532
- package/src/kernel-entry.ts +43 -0
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/fixtures/skyport.manifest.json +1 -1
- package/src/plugins/config-source.test.ts +7 -7
- package/src/release/build-lib.ts +61 -0
- package/src/release/exports.test.ts +4 -2
- package/src/release/exports.ts +30 -4
- package/src/release/measure.ts +101 -22
- package/src/runs/duckdb.ts +7 -2
- package/src/shared/lazy-src.ts +79 -0
- package/src/shared/optional-peer.ts +41 -0
- package/src/term.test.ts +3 -44
- package/src/test/create-test-app.ts +12 -0
- package/src/test/provisions.integration.test.ts +1 -1
- package/src/test/reset-element-registries.ts +2 -0
- package/src/upgrade/codemods.test.ts +7 -20
- package/src/upgrade/codemods.ts +93 -60
- package/site/content/docs/recipes/openbao.mdx +0 -141
- package/src/cli/ask-dev-mode.ts +0 -40
- package/src/cli/dev-mode.test.ts +0 -45
- package/src/cli/dev-mode.ts +0 -78
- package/src/cli/openbao-bootstrap.test.ts +0 -150
- package/src/cli/openbao-bootstrap.ts +0 -321
- package/src/cli/openbao-restart.integration.test.ts +0 -145
- package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
- package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
- package/src/docker/recipes/openbao.ts +0 -47
- package/src/drivers/libsql.ts +0 -179
- package/src/drivers/sqlite.ts +0 -44
- package/src/drivers/vault-openbao.test.ts +0 -97
- package/src/drivers/vault-openbao.ts +0 -143
package/src/docker/index.ts
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Recipes translate a normalised {@link ServiceSpec} into image-specific
|
|
5
5
|
* env / command / healthcheck and expose {@link ImageRecipe.url} so the
|
|
6
|
-
* kernel never learns an env-var name.
|
|
7
|
-
*
|
|
6
|
+
* kernel never learns an env-var name. Default layout is a single
|
|
7
|
+
* production-grade `docker-compose.yml` (opt-in `--split` / `--stack`).
|
|
8
|
+
* Credentials never enter YAML.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
export type {
|
|
11
12
|
ComposeHealthcheck,
|
|
13
|
+
ComposeLayout,
|
|
12
14
|
DeriveOptions,
|
|
13
15
|
DeriveResult,
|
|
14
16
|
GeneratedFile,
|
|
@@ -26,7 +28,11 @@ export { DEFAULT_DOCKER_DIR } from "./types.ts";
|
|
|
26
28
|
|
|
27
29
|
export {
|
|
28
30
|
COMPOSE_ALL,
|
|
31
|
+
COMPOSE_BASE,
|
|
29
32
|
COMPOSE_OVERRIDE,
|
|
33
|
+
DOCKER_COMPOSE,
|
|
34
|
+
DOCKER_COMPOSE_OVERRIDE,
|
|
35
|
+
DOCKER_STACK,
|
|
30
36
|
assertNoCredentialsInYaml,
|
|
31
37
|
buildSpecs,
|
|
32
38
|
buildStackEnv,
|
|
@@ -36,9 +42,24 @@ export {
|
|
|
36
42
|
formatStackEnv,
|
|
37
43
|
} from "./compose.ts";
|
|
38
44
|
|
|
45
|
+
export {
|
|
46
|
+
DEFAULT_SERVER_BUDGET,
|
|
47
|
+
allocateServiceResources,
|
|
48
|
+
mergeDeployResources,
|
|
49
|
+
resolveServerBudget,
|
|
50
|
+
type ServerBudget,
|
|
51
|
+
type ServiceResourceLimit,
|
|
52
|
+
} from "./resources.ts";
|
|
53
|
+
|
|
39
54
|
export { deriveInfrastructure, writeDerivedFiles } from "./derive.ts";
|
|
40
55
|
export { emitDockerfile } from "./dockerfile.ts";
|
|
41
|
-
export {
|
|
56
|
+
export {
|
|
57
|
+
composeToYaml,
|
|
58
|
+
credEnv,
|
|
59
|
+
envPrefix,
|
|
60
|
+
serviceNameFor,
|
|
61
|
+
type ComposeYamlOptions,
|
|
62
|
+
} from "./helpers.ts";
|
|
42
63
|
export { generateCredentials } from "./credentials.ts";
|
|
43
64
|
export {
|
|
44
65
|
ensureOllamaModel,
|
|
@@ -92,7 +113,6 @@ export {
|
|
|
92
113
|
ollama,
|
|
93
114
|
OLLAMA_IMAGE,
|
|
94
115
|
OLLAMA_MIN_SAFE_VERSION,
|
|
95
|
-
openbao,
|
|
96
116
|
pgdog,
|
|
97
117
|
postgres,
|
|
98
118
|
recipeFor,
|
|
@@ -108,7 +128,12 @@ export {
|
|
|
108
128
|
yugabyte,
|
|
109
129
|
} from "./recipes/index.ts";
|
|
110
130
|
export { CADDY_APP_SERVICE, buildCaddyfile } from "./recipes/caddy.ts";
|
|
111
|
-
export {
|
|
131
|
+
export {
|
|
132
|
+
PGDOG_BACKEND_SERVICE,
|
|
133
|
+
PGDOG_CONFIG_DIR,
|
|
134
|
+
buildPgDogToml,
|
|
135
|
+
buildPgDogUsersToml,
|
|
136
|
+
} from "./recipes/pgdog.ts";
|
|
112
137
|
export { SOCKET_PROXY_IMAGE, SOCKET_PROXY_SERVICE, traefikAppLabels } from "./recipes/traefik.ts";
|
|
113
138
|
export {
|
|
114
139
|
formatStackPreview,
|
|
@@ -10,7 +10,6 @@ import { llamaCpp } from "./llama-cpp.ts";
|
|
|
10
10
|
import { mailpit } from "./mailpit.ts";
|
|
11
11
|
import { meilisearch } from "./meilisearch.ts";
|
|
12
12
|
import { ollama } from "./ollama.ts";
|
|
13
|
-
import { openbao } from "./openbao.ts";
|
|
14
13
|
import { pgdog } from "./pgdog.ts";
|
|
15
14
|
import { postgres } from "./postgres.ts";
|
|
16
15
|
import { redis } from "./redis.ts";
|
|
@@ -36,7 +35,6 @@ export const builtinRecipes: readonly ImageRecipe[] = [
|
|
|
36
35
|
redis,
|
|
37
36
|
mailpit,
|
|
38
37
|
rustfs,
|
|
39
|
-
openbao,
|
|
40
38
|
meilisearch,
|
|
41
39
|
llamaCpp,
|
|
42
40
|
vllm,
|
|
@@ -54,7 +52,6 @@ export {
|
|
|
54
52
|
mailpit,
|
|
55
53
|
meilisearch,
|
|
56
54
|
ollama,
|
|
57
|
-
openbao,
|
|
58
55
|
pgdog,
|
|
59
56
|
postgres,
|
|
60
57
|
redis,
|
|
@@ -55,7 +55,7 @@ def cache_dir() -> str:
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
def parse_model(model: str) -> tuple[str, str]:
|
|
58
|
-
raw = model.strip() or "
|
|
58
|
+
raw = model.strip() or "granite3.3:2b"
|
|
59
59
|
if "/" not in raw.split(":", 1)[0]:
|
|
60
60
|
raw = f"ai/{raw}"
|
|
61
61
|
if ":" in raw:
|
|
@@ -187,7 +187,7 @@ def hub_pull(repo: str, tag: str, dest: str) -> None:
|
|
|
187
187
|
def ensure_model(model: str) -> str:
|
|
188
188
|
repo, tag = parse_model(model)
|
|
189
189
|
# llama download -dr expects id without forced ai/ prefix when using default org.
|
|
190
|
-
dr = model.strip() or "
|
|
190
|
+
dr = model.strip() or "granite3.3:2b"
|
|
191
191
|
if dr.startswith("ai/"):
|
|
192
192
|
dr = dr[3:]
|
|
193
193
|
dest = os.path.join(cache_dir(), expected_gguf(repo, tag))
|
|
@@ -219,7 +219,7 @@ def ensure_model(model: str) -> str:
|
|
|
219
219
|
|
|
220
220
|
|
|
221
221
|
def main() -> None:
|
|
222
|
-
model = (os.environ.get("OKE_AI_MODEL") or "
|
|
222
|
+
model = (os.environ.get("OKE_AI_MODEL") or "granite3.3:2b").strip() or "granite3.3:2b"
|
|
223
223
|
gguf = ensure_model(model)
|
|
224
224
|
# Without an explicit --ctx-size, llama-server defaults to the model's
|
|
225
225
|
# full native training context (32K-256K+ for many current models). The
|
|
@@ -259,7 +259,7 @@ export const llamaCpp: ImageRecipe = {
|
|
|
259
259
|
match: (i) => /llama\.cpp|llamacpp/i.test(i),
|
|
260
260
|
apply: (s) => ({
|
|
261
261
|
// OKE_AI_MODEL comes from compose `env_file` (`.env.docker`). Do not
|
|
262
|
-
// re-declare it as `${OKE_AI_MODEL:-
|
|
262
|
+
// re-declare it as `${OKE_AI_MODEL:-granite3.3:2b}` here — Compose interpolates
|
|
263
263
|
// that from the *host* shell and can override the stack file with the
|
|
264
264
|
// default. Never set LLAMA_ARG_MODELS_PRESET / LLAMA_ARG_DOCKER_REPO
|
|
265
265
|
// (router children inherit `LLAMA_ARG_*` and can OOM).
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Sits in front of the `postgres` recipe. Transaction pooling is set
|
|
5
5
|
* explicitly (`pooler_mode = "transaction"` — also PgDog's upstream default).
|
|
6
|
-
* Config shape matches upstream docs
|
|
6
|
+
* Config shape matches upstream docs under `pgdog/`:
|
|
7
|
+
* `pgdog/pgdog.toml` + `pgdog/users.toml`
|
|
7
8
|
* (https://docs.pgdog.dev/configuration/).
|
|
8
9
|
*/
|
|
9
10
|
|
|
11
|
+
/** Relative directory for generated PgDog TOML (under the compose dir). */
|
|
12
|
+
export const PGDOG_CONFIG_DIR = "pgdog";
|
|
13
|
+
|
|
10
14
|
import type { ImageRecipe } from "../types.ts";
|
|
11
15
|
|
|
12
16
|
/** Backend Postgres service name in the compose network. */
|
|
@@ -66,7 +70,10 @@ export const pgdog: ImageRecipe = {
|
|
|
66
70
|
port: 6432,
|
|
67
71
|
match: (i) => /pgdog/i.test(i),
|
|
68
72
|
apply: () => ({
|
|
69
|
-
volumes: [
|
|
73
|
+
volumes: [
|
|
74
|
+
`./${PGDOG_CONFIG_DIR}/pgdog.toml:/pgdog/pgdog.toml:ro`,
|
|
75
|
+
`./${PGDOG_CONFIG_DIR}/users.toml:/pgdog/users.toml:ro`,
|
|
76
|
+
],
|
|
70
77
|
dependsOn: {
|
|
71
78
|
[PGDOG_BACKEND_SERVICE]: { condition: "service_healthy" },
|
|
72
79
|
},
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server budget → per-service resource allocation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_SERVER_BUDGET,
|
|
8
|
+
allocateServiceResources,
|
|
9
|
+
mergeDeployResources,
|
|
10
|
+
resolveServerBudget,
|
|
11
|
+
} from "./resources.ts";
|
|
12
|
+
|
|
13
|
+
describe("resolveServerBudget", () => {
|
|
14
|
+
test("defaults to 4 CPU / 8 GiB", () => {
|
|
15
|
+
expect(resolveServerBudget(undefined)).toEqual(DEFAULT_SERVER_BUDGET);
|
|
16
|
+
expect(DEFAULT_SERVER_BUDGET).toEqual({ cpus: 4, memoryGb: 8 });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("accepts client overrides", () => {
|
|
20
|
+
expect(resolveServerBudget({ cpus: 8, memoryGb: 16 })).toEqual({
|
|
21
|
+
cpus: 8,
|
|
22
|
+
memoryGb: 16,
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("allocateServiceResources", () => {
|
|
28
|
+
test("apportion 4/8 across app + sql + kv without exceeding the usable pool", () => {
|
|
29
|
+
const limits = allocateServiceResources(["app", "store-sql", "store-kv"], {
|
|
30
|
+
cpus: 4,
|
|
31
|
+
memoryGb: 8,
|
|
32
|
+
});
|
|
33
|
+
expect(limits.size).toBe(3);
|
|
34
|
+
let cpuSum = 0;
|
|
35
|
+
let memSum = 0;
|
|
36
|
+
for (const limit of limits.values()) {
|
|
37
|
+
cpuSum += Number(limit.cpus);
|
|
38
|
+
memSum += limit.memory.endsWith("G")
|
|
39
|
+
? Number(limit.memory.slice(0, -1)) * 1024
|
|
40
|
+
: Number(limit.memory.slice(0, -1));
|
|
41
|
+
}
|
|
42
|
+
// 10% host overhead → usable 3.6 CPU / ~7372 MiB
|
|
43
|
+
expect(cpuSum).toBeLessThanOrEqual(3.6 + 0.05);
|
|
44
|
+
expect(memSum).toBeLessThanOrEqual(8 * 1024 * 0.9 + 8);
|
|
45
|
+
expect(Number(limits.get("store-sql")!.cpus)).toBeGreaterThan(
|
|
46
|
+
Number(limits.get("store-kv")!.cpus),
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("mergeDeployResources preserves GPU reservations", () => {
|
|
51
|
+
const merged = mergeDeployResources(
|
|
52
|
+
{ cpus: "1.5", memory: "2G" },
|
|
53
|
+
{
|
|
54
|
+
resources: {
|
|
55
|
+
reservations: {
|
|
56
|
+
devices: [{ driver: "nvidia", count: 1, capabilities: ["gpu"] }],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
const resources = merged.resources as {
|
|
62
|
+
limits: { cpus: string; memory: string };
|
|
63
|
+
reservations: { devices: unknown[]; cpus: string; memory: string };
|
|
64
|
+
};
|
|
65
|
+
expect(resources.limits).toEqual({ cpus: "1.5", memory: "2G" });
|
|
66
|
+
expect(resources.reservations.devices).toEqual([
|
|
67
|
+
{ driver: "nvidia", count: 1, capabilities: ["gpu"] },
|
|
68
|
+
]);
|
|
69
|
+
expect(resources.reservations.cpus).toBe("0.75");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server resource budget → per-service Compose `deploy.resources` limits.
|
|
3
|
+
*
|
|
4
|
+
* Defaults assume a small production host (4 CPU / 8 GiB). Callers may override
|
|
5
|
+
* via {@link ServerBudget}. ~10% stays unallocated for the Docker daemon / OS.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Host capacity used when apportioning container limits. */
|
|
9
|
+
export type ServerBudget = {
|
|
10
|
+
/** Logical CPUs available to the stack (default 4). */
|
|
11
|
+
readonly cpus: number;
|
|
12
|
+
/** RAM in GiB available to the stack (default 8). */
|
|
13
|
+
readonly memoryGb: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Default production host size when the client does not specify one. */
|
|
17
|
+
export const DEFAULT_SERVER_BUDGET: ServerBudget = {
|
|
18
|
+
cpus: 4,
|
|
19
|
+
memoryGb: 8,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Fraction of the host reserved for Docker / OS (not given to services). */
|
|
23
|
+
const HOST_OVERHEAD = 0.1;
|
|
24
|
+
|
|
25
|
+
/** Relative weights by compose role — heavier roles get a larger share. */
|
|
26
|
+
const ROLE_WEIGHT: Readonly<Record<string, number>> = {
|
|
27
|
+
app: 20,
|
|
28
|
+
"store.sql": 30,
|
|
29
|
+
pgdog: 5,
|
|
30
|
+
"store.kv": 15,
|
|
31
|
+
"store.files": 10,
|
|
32
|
+
"store.index": 10,
|
|
33
|
+
"channel.email": 5,
|
|
34
|
+
ai: 40,
|
|
35
|
+
proxy: 5,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const DEFAULT_WEIGHT = 10;
|
|
39
|
+
const MIN_CPUS = 0.1;
|
|
40
|
+
const MIN_MEMORY_MB = 64;
|
|
41
|
+
|
|
42
|
+
/** Resolved CPU + memory limit for one compose service. */
|
|
43
|
+
export type ServiceResourceLimit = {
|
|
44
|
+
readonly cpus: string;
|
|
45
|
+
readonly memory: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Normalise a partial budget against {@link DEFAULT_SERVER_BUDGET}.
|
|
50
|
+
*
|
|
51
|
+
* @param budget - Optional caller overrides
|
|
52
|
+
*/
|
|
53
|
+
export function resolveServerBudget(budget: Partial<ServerBudget> | undefined): ServerBudget {
|
|
54
|
+
const cpus = budget?.cpus ?? DEFAULT_SERVER_BUDGET.cpus;
|
|
55
|
+
const memoryGb = budget?.memoryGb ?? DEFAULT_SERVER_BUDGET.memoryGb;
|
|
56
|
+
if (!(cpus > 0) || !Number.isFinite(cpus)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`oke docker: server cpus must be a positive number (got ${String(budget?.cpus)})`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (!(memoryGb > 0) || !Number.isFinite(memoryGb)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`oke docker: server memoryGb must be a positive number (got ${String(budget?.memoryGb)})`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return { cpus, memoryGb };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Apportion a server budget across named services (compose service names).
|
|
71
|
+
*
|
|
72
|
+
* @param serviceNames - Services that receive limits (`app`, `store-sql`, …)
|
|
73
|
+
* @param budget - Host capacity
|
|
74
|
+
* @returns Map of service name → deploy.resources.limits fields
|
|
75
|
+
*/
|
|
76
|
+
export function allocateServiceResources(
|
|
77
|
+
serviceNames: readonly string[],
|
|
78
|
+
budget: Partial<ServerBudget> | undefined = undefined,
|
|
79
|
+
): ReadonlyMap<string, ServiceResourceLimit> {
|
|
80
|
+
const resolved = resolveServerBudget(budget);
|
|
81
|
+
const usableCpus = resolved.cpus * (1 - HOST_OVERHEAD);
|
|
82
|
+
const usableMemoryMb = resolved.memoryGb * 1024 * (1 - HOST_OVERHEAD);
|
|
83
|
+
|
|
84
|
+
const weights = serviceNames.map((name) => {
|
|
85
|
+
const role = serviceNameToRole(name);
|
|
86
|
+
return ROLE_WEIGHT[role] ?? DEFAULT_WEIGHT;
|
|
87
|
+
});
|
|
88
|
+
const total = weights.reduce((a, b) => a + b, 0);
|
|
89
|
+
const out = new Map<string, ServiceResourceLimit>();
|
|
90
|
+
if (total <= 0 || serviceNames.length === 0) return out;
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < serviceNames.length; i++) {
|
|
93
|
+
const name = serviceNames[i]!;
|
|
94
|
+
const share = weights[i]! / total;
|
|
95
|
+
const cpus = Math.max(MIN_CPUS, roundCpus(usableCpus * share));
|
|
96
|
+
const memoryMb = Math.max(MIN_MEMORY_MB, Math.round(usableMemoryMb * share));
|
|
97
|
+
out.set(name, {
|
|
98
|
+
cpus: formatCpus(cpus),
|
|
99
|
+
memory: formatMemoryMb(memoryMb),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build a Compose `deploy.resources` block from a limit, preserving any
|
|
107
|
+
* existing reservations (e.g. GPU devices from AI recipes).
|
|
108
|
+
*
|
|
109
|
+
* @param limit - Allocated CPU / memory
|
|
110
|
+
* @param existing - Prior `deploy` object from a recipe, if any
|
|
111
|
+
*/
|
|
112
|
+
export function mergeDeployResources(
|
|
113
|
+
limit: ServiceResourceLimit,
|
|
114
|
+
existing: Readonly<Record<string, unknown>> | undefined,
|
|
115
|
+
): Record<string, unknown> {
|
|
116
|
+
const prev = existing ? { ...existing } : {};
|
|
117
|
+
const prevResources = isPlainObject(prev.resources)
|
|
118
|
+
? { ...prev.resources }
|
|
119
|
+
: ({} as Record<string, unknown>);
|
|
120
|
+
const prevLimits = isPlainObject(prevResources.limits)
|
|
121
|
+
? { ...prevResources.limits }
|
|
122
|
+
: ({} as Record<string, unknown>);
|
|
123
|
+
prevResources.limits = {
|
|
124
|
+
...prevLimits,
|
|
125
|
+
cpus: limit.cpus,
|
|
126
|
+
memory: limit.memory,
|
|
127
|
+
};
|
|
128
|
+
// Soft reservation ≈ half the limit so the scheduler can pack peers.
|
|
129
|
+
const prevReservations = isPlainObject(prevResources.reservations)
|
|
130
|
+
? { ...prevResources.reservations }
|
|
131
|
+
: ({} as Record<string, unknown>);
|
|
132
|
+
if (prevReservations.cpus === undefined) {
|
|
133
|
+
prevReservations.cpus = formatCpus(Math.max(MIN_CPUS, Number(limit.cpus) * 0.5));
|
|
134
|
+
}
|
|
135
|
+
if (prevReservations.memory === undefined) {
|
|
136
|
+
prevReservations.memory = formatMemoryMb(
|
|
137
|
+
Math.max(MIN_MEMORY_MB, Math.round(parseMemoryMb(limit.memory) * 0.5)),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
prevResources.reservations = prevReservations;
|
|
141
|
+
prev.resources = prevResources;
|
|
142
|
+
return prev;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function serviceNameToRole(serviceName: string): string {
|
|
146
|
+
if (serviceName === "app") return "app";
|
|
147
|
+
return serviceName.replaceAll("-", ".");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function roundCpus(n: number): number {
|
|
151
|
+
return Math.round(n * 100) / 100;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function formatCpus(n: number): string {
|
|
155
|
+
return String(Math.round(n * 100) / 100);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function formatMemoryMb(mb: number): string {
|
|
159
|
+
if (mb >= 1024 && mb % 1024 === 0) return `${mb / 1024}G`;
|
|
160
|
+
if (mb >= 1024) {
|
|
161
|
+
const gb = Math.round((mb / 1024) * 100) / 100;
|
|
162
|
+
return `${gb}G`;
|
|
163
|
+
}
|
|
164
|
+
return `${mb}M`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function parseMemoryMb(value: string): number {
|
|
168
|
+
const m = /^(\d+(?:\.\d+)?)(G|M)$/i.exec(value);
|
|
169
|
+
if (!m) return MIN_MEMORY_MB;
|
|
170
|
+
const n = Number(m[1]);
|
|
171
|
+
return m[2]!.toUpperCase() === "G" ? n * 1024 : n;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
175
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
176
|
+
}
|
|
@@ -63,11 +63,18 @@ describe("loadExistingStackControls", () => {
|
|
|
63
63
|
expect(controls?.OKE_AI_MODEL).toBe("gemma4:e4b-q4_K_M");
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
test("seeds commented OKE_AI_MODEL from .env.local when .env.docker omits it", async () => {
|
|
67
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-stack-ai-"));
|
|
68
|
+
await writeFile(join(dir, ".env.local"), "# OKE_AI_MODEL=gemma4:e4b-q4_K_M\n", "utf8");
|
|
69
|
+
const controls = await loadExistingStackControls(dir);
|
|
70
|
+
expect(controls?.OKE_AI_MODEL).toBe("gemma4:e4b-q4_K_M");
|
|
71
|
+
});
|
|
72
|
+
|
|
66
73
|
test("prefers active OKE_AI_MODEL in docker/.env.docker over .env.local", async () => {
|
|
67
74
|
const dir = await mkdtemp(join(tmpdir(), "oke-stack-ai-"));
|
|
68
75
|
await mkdir(join(dir, "docker"), { recursive: true });
|
|
69
76
|
await writeFile(join(dir, "docker", ".env.docker"), "OKE_AI_MODEL=smollm2\n", "utf8");
|
|
70
|
-
await writeFile(join(dir, ".env.local"), "OKE_AI_MODEL=gemma4:e4b-q4_K_M\n", "utf8");
|
|
77
|
+
await writeFile(join(dir, ".env.local"), "# OKE_AI_MODEL=gemma4:e4b-q4_K_M\n", "utf8");
|
|
71
78
|
const controls = await loadExistingStackControls(dir);
|
|
72
79
|
expect(controls?.OKE_AI_MODEL).toBe("smollm2");
|
|
73
80
|
});
|
|
@@ -132,7 +139,7 @@ describe("deriveInfrastructure instanceId", () => {
|
|
|
132
139
|
},
|
|
133
140
|
},
|
|
134
141
|
});
|
|
135
|
-
const base = result.files.find((f) => f.path === "compose.yml")!.content;
|
|
142
|
+
const base = result.files.find((f) => f.path === "docker-compose.yml")!.content;
|
|
136
143
|
expect(base).toContain("oke-dev-abcdef");
|
|
137
144
|
expect(result.stackEnv.DATABASE_URL).toMatch(/:15\d{3}\//);
|
|
138
145
|
expect(result.stackEnv.REDIS_URL).toMatch(/:16\d{3}/);
|
|
@@ -163,8 +170,8 @@ describe("deriveInfrastructure instanceId", () => {
|
|
|
163
170
|
},
|
|
164
171
|
},
|
|
165
172
|
});
|
|
166
|
-
const mailYml = result.files.find((f) => f.path === "compose.
|
|
167
|
-
const filesYml =
|
|
173
|
+
const mailYml = result.files.find((f) => f.path === "docker-compose.yml")!.content;
|
|
174
|
+
const filesYml = mailYml;
|
|
168
175
|
expect(mailYml).toContain(`${20_000 + n}:1025`);
|
|
169
176
|
expect(mailYml).toContain(`${21_000 + n}:8025`);
|
|
170
177
|
expect(mailYml).not.toContain("8025:8025");
|
package/src/docker/stack-id.ts
CHANGED
|
@@ -131,9 +131,6 @@ export const STACK_CONTROL_KEYS = [
|
|
|
131
131
|
"OKE_AI_CTX_SIZE",
|
|
132
132
|
"OKE_PROXY_HOST",
|
|
133
133
|
"OKE_PROXY_ACME_EMAIL",
|
|
134
|
-
// OpenBao app token — minted by bootstrap, must survive `writeDerivedFiles`.
|
|
135
|
-
"OKE_VAULT_TOKEN",
|
|
136
|
-
"OKE_VAULT_MOUNT",
|
|
137
134
|
] as const;
|
|
138
135
|
|
|
139
136
|
/**
|
|
@@ -176,9 +173,6 @@ export async function loadExistingStackCredentials(
|
|
|
176
173
|
/**
|
|
177
174
|
* Load optional controls from `docker/.env.docker` when present.
|
|
178
175
|
*
|
|
179
|
-
* Falls back to `.oke/openbao/app.token` for `OKE_VAULT_TOKEN` when the env
|
|
180
|
-
* file was regenerated without the bootstrap overlay (per-project token).
|
|
181
|
-
*
|
|
182
176
|
* @param cwd - Project root
|
|
183
177
|
*/
|
|
184
178
|
export async function loadExistingStackControls(
|
|
@@ -192,31 +186,17 @@ export async function loadExistingStackControls(
|
|
|
192
186
|
controls = parseStackControls(await file.text());
|
|
193
187
|
break;
|
|
194
188
|
}
|
|
195
|
-
// create-oke / `oke ai setup` write the chosen model into `.env.local
|
|
196
|
-
//
|
|
197
|
-
//
|
|
189
|
+
// create-oke / `oke ai setup` write the chosen model into `.env.local` as a
|
|
190
|
+
// commented hint (active would shadow `docker/.env.docker`). Seed active or
|
|
191
|
+
// commented `OKE_AI_MODEL` on first docker boot so `LLAMA_ARG_DOCKER_REPO`
|
|
192
|
+
// is not stuck on the recipe default (`granite3.3:2b`).
|
|
198
193
|
if (!controls.OKE_AI_MODEL) {
|
|
199
194
|
const localEnv = Bun.file(resolve(cwd, ".env.local"));
|
|
200
195
|
if (await localEnv.exists()) {
|
|
201
|
-
const model =
|
|
202
|
-
.get("OKE_AI_MODEL")
|
|
203
|
-
?.trim();
|
|
196
|
+
const model = readEnvAssignment(await localEnv.text(), "OKE_AI_MODEL");
|
|
204
197
|
if (model) controls = { ...controls, OKE_AI_MODEL: model };
|
|
205
198
|
}
|
|
206
199
|
}
|
|
207
|
-
if (!controls.OKE_VAULT_TOKEN) {
|
|
208
|
-
const tokenFile = Bun.file(resolve(cwd, ".oke", "openbao", "app.token"));
|
|
209
|
-
if (await tokenFile.exists()) {
|
|
210
|
-
const token = (await tokenFile.text()).trim();
|
|
211
|
-
if (token) {
|
|
212
|
-
controls = {
|
|
213
|
-
...controls,
|
|
214
|
-
OKE_VAULT_TOKEN: token,
|
|
215
|
-
OKE_VAULT_MOUNT: controls.OKE_VAULT_MOUNT ?? "secret",
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
200
|
return Object.keys(controls).length > 0 ? controls : undefined;
|
|
221
201
|
}
|
|
222
202
|
|
|
@@ -228,14 +208,44 @@ function parseDotenv(text: string): Map<string, string> {
|
|
|
228
208
|
const eq = trimmed.indexOf("=");
|
|
229
209
|
if (eq <= 0) continue;
|
|
230
210
|
const key = trimmed.slice(0, eq);
|
|
231
|
-
|
|
232
|
-
if (
|
|
233
|
-
(value.startsWith('"') && value.endsWith('"')) ||
|
|
234
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
235
|
-
) {
|
|
236
|
-
value = value.slice(1, -1);
|
|
237
|
-
}
|
|
211
|
+
const value = unquoteEnvValue(trimmed.slice(eq + 1));
|
|
238
212
|
map.set(key, value);
|
|
239
213
|
}
|
|
240
214
|
return map;
|
|
241
215
|
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Read `KEY=value` from dotenv text — active line wins; otherwise the last
|
|
219
|
+
* `# KEY=value` hint (create-oke / `oke ai setup` leave AI stack keys commented).
|
|
220
|
+
*
|
|
221
|
+
* @param text - Dotenv contents
|
|
222
|
+
* @param key - Variable name
|
|
223
|
+
*/
|
|
224
|
+
function readEnvAssignment(text: string, key: string): string | undefined {
|
|
225
|
+
const active = parseDotenv(text).get(key)?.trim();
|
|
226
|
+
if (active) return active;
|
|
227
|
+
let hinted: string | undefined;
|
|
228
|
+
const prefix = `# ${key}=`;
|
|
229
|
+
const altPrefix = `#${key}=`;
|
|
230
|
+
for (const line of text.split("\n")) {
|
|
231
|
+
const trimmed = line.trim();
|
|
232
|
+
if (trimmed.startsWith(prefix)) {
|
|
233
|
+
hinted = unquoteEnvValue(trimmed.slice(prefix.length));
|
|
234
|
+
} else if (trimmed.startsWith(altPrefix)) {
|
|
235
|
+
hinted = unquoteEnvValue(trimmed.slice(altPrefix.length));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const value = hinted?.trim();
|
|
239
|
+
return value || undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function unquoteEnvValue(raw: string): string {
|
|
243
|
+
let value = raw;
|
|
244
|
+
if (
|
|
245
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
246
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
247
|
+
) {
|
|
248
|
+
value = value.slice(1, -1);
|
|
249
|
+
}
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
@@ -55,8 +55,8 @@ describe("oke dev --docker postgres integration", () => {
|
|
|
55
55
|
writeStackEnv: true,
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
// Infra-only:
|
|
59
|
-
const composeFiles = ["compose.yml"
|
|
58
|
+
// Infra-only: single docker-compose.yml (no app build).
|
|
59
|
+
const composeFiles = ["docker-compose.yml"];
|
|
60
60
|
const up = Bun.spawn(
|
|
61
61
|
[
|
|
62
62
|
"docker",
|
|
@@ -122,24 +122,13 @@ describe("oke dev --docker postgres integration", () => {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
// Prove credentials live in docker/.env.docker, not YAML.
|
|
125
|
-
const yml = await Bun.file(join(dockerDir, "compose.
|
|
125
|
+
const yml = await Bun.file(join(dockerDir, "docker-compose.yml")).text();
|
|
126
126
|
expect(yml).not.toContain("stack-integration-pass");
|
|
127
127
|
expect(formatStackEnv(derived.stackEnv)).toContain("stack-integration-pass");
|
|
128
128
|
expect(await Bun.file(join(dockerDir, ".env.docker")).exists()).toBe(true);
|
|
129
129
|
} finally {
|
|
130
130
|
await Bun.spawn(
|
|
131
|
-
[
|
|
132
|
-
"docker",
|
|
133
|
-
"compose",
|
|
134
|
-
"-p",
|
|
135
|
-
project,
|
|
136
|
-
"-f",
|
|
137
|
-
"compose.yml",
|
|
138
|
-
"-f",
|
|
139
|
-
"compose.store.sql.yml",
|
|
140
|
-
"down",
|
|
141
|
-
"-v",
|
|
142
|
-
],
|
|
131
|
+
["docker", "compose", "-p", project, "-f", "docker-compose.yml", "down", "-v"],
|
|
143
132
|
{ cwd: dockerDir, stdout: "pipe", stderr: "pipe" },
|
|
144
133
|
).exited.catch(() => {});
|
|
145
134
|
await rm(dir, { recursive: true, force: true }).catch(() => {});
|
package/src/docker/types.ts
CHANGED
|
@@ -132,6 +132,15 @@ export interface ImageRecipe {
|
|
|
132
132
|
url(spec: ServiceSpec, endpoint: ServiceEndpoint): string;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* How derived compose artefacts are laid out under {@link DeriveOptions.composeDir}.
|
|
137
|
+
*
|
|
138
|
+
* - `single` (default) — one production-grade `docker-compose.yml`
|
|
139
|
+
* - `split` — `compose.yml` + per-role `compose.<role>.yml` (+ `compose.prod.yml` when prod)
|
|
140
|
+
* - `stack` — one Swarm-oriented `docker-stack.yml`
|
|
141
|
+
*/
|
|
142
|
+
export type ComposeLayout = "single" | "split" | "stack";
|
|
143
|
+
|
|
135
144
|
/** Options for compose / Dockerfile derivation. */
|
|
136
145
|
export interface DeriveOptions {
|
|
137
146
|
/** Role → image pin (from `oke.config.ts` / Manifest). */
|
|
@@ -140,10 +149,30 @@ export interface DeriveOptions {
|
|
|
140
149
|
readonly app?: string;
|
|
141
150
|
/** App listen port (default 6530). */
|
|
142
151
|
readonly appPort?: number;
|
|
143
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* When true, fold production readiness (`/_/ready` HEALTHCHECK, deploy,
|
|
154
|
+
* restart policy, secret refs) and apportion {@link serverCpus} /
|
|
155
|
+
* {@link serverMemoryGb} across services. `oke docker` defaults true;
|
|
156
|
+
* `oke dev --docker` keeps false.
|
|
157
|
+
*/
|
|
144
158
|
readonly prod?: boolean;
|
|
145
159
|
/**
|
|
146
|
-
*
|
|
160
|
+
* Compose file layout (default `single` → `docker-compose.yml`).
|
|
161
|
+
* Pass `split` for layered `compose.<role>.yml`, or `stack` for `docker-stack.yml`.
|
|
162
|
+
*/
|
|
163
|
+
readonly layout?: ComposeLayout;
|
|
164
|
+
/**
|
|
165
|
+
* Host CPU count used to size `deploy.resources` when {@link prod} is true
|
|
166
|
+
* (default 4).
|
|
167
|
+
*/
|
|
168
|
+
readonly serverCpus?: number;
|
|
169
|
+
/**
|
|
170
|
+
* Host RAM in GiB used to size `deploy.resources` when {@link prod} is true
|
|
171
|
+
* (default 8).
|
|
172
|
+
*/
|
|
173
|
+
readonly serverMemoryGb?: number;
|
|
174
|
+
/**
|
|
175
|
+
* Include the `app` service in the compose document (default true).
|
|
147
176
|
* `oke dev --docker` sets false — host Bun runs the app; Docker is infra only.
|
|
148
177
|
*/
|
|
149
178
|
readonly includeApp?: boolean;
|
|
@@ -193,6 +222,9 @@ export interface DeriveResult {
|
|
|
193
222
|
* never embedded in YAML.
|
|
194
223
|
*/
|
|
195
224
|
readonly stackEnv: Readonly<Record<string, string>>;
|
|
196
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* Compose `-f` merge order (includes a user-owned override that oke never
|
|
227
|
+
* writes — callers should skip it when the file is absent).
|
|
228
|
+
*/
|
|
197
229
|
readonly composeFiles: readonly string[];
|
|
198
230
|
}
|
|
@@ -147,12 +147,12 @@ describe("ollama live — real completion", () => {
|
|
|
147
147
|
|
|
148
148
|
const runtime = bindAi(
|
|
149
149
|
{
|
|
150
|
-
config: { drivers: { ai: {
|
|
150
|
+
config: { drivers: { ai: { test: "ollama", dev: "ollama" } } },
|
|
151
151
|
ai: { models: [smart], prompts: [ping] },
|
|
152
152
|
},
|
|
153
153
|
undefined,
|
|
154
154
|
() => Date.now(),
|
|
155
|
-
"
|
|
155
|
+
"test",
|
|
156
156
|
false,
|
|
157
157
|
);
|
|
158
158
|
expect(runtime.prompts.has("ollama-ping")).toBe(true);
|