Files
spark-control/package/startos/main.ts
T
Keysat 136a4713a1 v0.22.0:0 - configurable vllm port; gitea-release tooling; coexistence roadmap
- Configure Sparks gains a vLLM port field (blank => 8888, our launch-cluster.sh
  default); VLLM_PORT plumbed configureSparks -> sparkConfig.yaml -> main.ts env
  -> config.py. So an adopter whose vLLM listens elsewhere (e.g. 8000) can fix
  the "vLLM unreachable" health check without rebuilding the package.
- Harden numeric env parsing (config._env_int): a blank or malformed port now
  falls back to its default instead of crashing daemon startup (closes a P3
  tech-debt item; the Configure panel passes unset optional fields as "").
- Add scripts/gitea-release.sh + `make release` to publish the built s9pk to
  Gitea Releases, so the OpenClaw adopter pulls updates with a read-only token
  instead of being hand-sent the package.
- Capture the OpenClaw/Johnny-5 coexistence epic and the "control plane, not a
  job runner" stance in ROADMAP.md and Current state.
2026-06-17 19:45:09 -05:00

88 lines
2.7 KiB
TypeScript

import { i18n } from './i18n'
import { sdk } from './sdk'
import { uiPort } from './utils'
import { sparkConfigYaml } from './fileModels/sparkConfig.yaml'
export const main = sdk.setupMain(async ({ effects }) => {
console.info(i18n('Starting Spark Control…'))
// Reactively read SSH targets from the user-configured yaml file.
// Changing this file via the "Configure Sparks" action restarts the daemon.
const cfg = (await sparkConfigYaml.read().const(effects)) ?? {
spark1_host: '',
spark1_user: '',
spark2_host: '',
spark2_user: '',
vllm_port: '',
parakeet_host: '',
parakeet_user: '',
parakeet_container: '',
kokoro_host: '',
kokoro_user: '',
kokoro_container: '',
embed_host: '',
embed_user: '',
embed_container: '',
qdrant_host: '',
qdrant_user: '',
qdrant_container: '',
qdrant_collection: '',
matrix_bridge_user: '',
open_webui_url: '',
ngc_api_key: '',
}
return sdk.Daemons.of(effects).addDaemon('primary', {
subcontainer: await sdk.SubContainer.of(
effects,
{ imageId: 'spark-control' },
sdk.Mounts.of().mountVolume({
volumeId: 'main',
subpath: null,
mountpoint: '/data',
readonly: false,
}),
'spark-control-sub',
),
exec: {
command: ['/app/entrypoint.sh'],
env: {
SPARK1_HOST: cfg.spark1_host,
SPARK1_USER: cfg.spark1_user,
SPARK2_HOST: cfg.spark2_host,
SPARK2_USER: cfg.spark2_user,
VLLM_PORT: cfg.vllm_port,
PARAKEET_HOST: cfg.parakeet_host,
PARAKEET_USER: cfg.parakeet_user,
PARAKEET_CONTAINER: cfg.parakeet_container,
KOKORO_HOST: cfg.kokoro_host,
KOKORO_USER: cfg.kokoro_user,
KOKORO_CONTAINER: cfg.kokoro_container,
EMBED_HOST: cfg.embed_host,
EMBED_USER: cfg.embed_user,
EMBED_CONTAINER: cfg.embed_container,
QDRANT_HOST: cfg.qdrant_host,
QDRANT_USER: cfg.qdrant_user,
QDRANT_CONTAINER: cfg.qdrant_container,
QDRANT_COLLECTION: cfg.qdrant_collection,
MATRIX_BRIDGE_USER: cfg.matrix_bridge_user,
MODELS_OVERRIDES: '/data/models-overrides.yaml',
SERVICES_OVERRIDES: '/data/services-overrides.yaml',
CONNECTIVITY_LOG: '/data/connectivity.json',
OPEN_WEBUI_URL: cfg.open_webui_url,
NGC_API_KEY: cfg.ngc_api_key,
BIND_PORT: String(uiPort),
},
},
ready: {
display: i18n('Web Interface'),
fn: () =>
sdk.healthCheck.checkPortListening(effects, uiPort, {
successMessage: i18n('The web interface is ready'),
errorMessage: i18n('The web interface is not ready'),
}),
},
requires: [],
})
})