Files
Keysat 7e0759846f v0.27.0:0 - in-app settings gear + swap-lock route fix
Move the ~20 optional cluster knobs out of the StartOS "Configure Sparks"
action (now just the 4 required fields) and into a dashboard ⚙ Settings gear,
backed by a /data/app_settings.json overlay keyed by env-var names. One shared
mutable Settings instance + Settings.reload() applies edits live without a
restart; existing installs' values migrate automatically on first boot.

Also: support-service ports (parakeet/kokoro/embed/qdrant + vllm) are now
configurable, and GET /api/swap/lock no longer 404s (it was shadowed by the
/api/swap/{job_id} catch-all). WebhookNotifier is re-pointed on save so its
url/secret reload live too.
2026-06-18 13:41:28 -05:00

87 lines
3.2 KiB
TypeScript

import { sdk } from '../sdk'
import { sparkConfigYaml } from '../fileModels/sparkConfig.yaml'
const { InputSpec, Value } = sdk
// This action is intentionally minimal: just the required wiring needed before
// Spark Control can do anything — the two Spark node addresses and SSH users.
// Every other knob (vLLM/service ports, container names, support-service hosts,
// integrations, webhooks) now lives behind the ⚙ Settings gear in the dashboard
// itself, which is where StartOS 0.4 expects routine config to live (and most
// operators never open StartOS actions). The optional keys still exist in the
// config.yaml schema (set by older versions); they're read into env at launch
// and migrated into the in-app settings overlay on first boot, so nothing is
// lost on upgrade — they're simply edited in the dashboard from now on.
const inputSpec = InputSpec.of({
spark1_host: Value.text({
name: 'Spark 1 hostname or IP',
description:
'The head node of your DGX Spark cluster — the one that has ~/spark-vllm-docker cloned and runs the vLLM container. Enter its LAN IP (recommended) or hostname.',
required: true,
default: null,
placeholder: 'e.g. 192.168.1.10',
masked: false,
}),
spark1_user: Value.text({
name: 'Spark 1 SSH user',
description:
'The user account on Spark 1 to SSH in as — whatever you log in as when you ssh into it manually.',
required: true,
default: null,
placeholder: 'your SSH username',
masked: false,
}),
spark2_host: Value.text({
name: 'Spark 2 hostname or IP',
description:
'The worker node of your DGX Spark cluster (also runs always-on services like Parakeet and Kokoro). Enter its LAN IP or hostname.',
required: true,
default: null,
placeholder: 'e.g. 192.168.1.11',
masked: false,
}),
spark2_user: Value.text({
name: 'Spark 2 SSH user',
description:
'The user account on Spark 2 to SSH in as. Usually the same as Spark 1.',
required: true,
default: null,
placeholder: 'your SSH username',
masked: false,
}),
})
export const configureSparks = sdk.Action.withInput(
'configure-sparks',
async () => ({
name: 'Configure Sparks',
description:
'Set your two Spark node addresses and SSH users — the required wiring. Everything else (ports, container names, support services, integrations) is configured under ⚙ Settings in the Spark Control dashboard.',
warning: null,
visibility: 'enabled',
allowedStatuses: 'any',
group: null,
}),
async () => inputSpec,
async ({ effects }) => {
// Prefill from the saved config, but only the keys this (trimmed) form owns.
const cfg = await sparkConfigYaml.read().once()
if (!cfg) return null
return {
spark1_host: cfg.spark1_host,
spark1_user: cfg.spark1_user,
spark2_host: cfg.spark2_host,
spark2_user: cfg.spark2_user,
}
},
async ({ effects, input }) => {
// merge() only touches the four keys we submit, leaving any legacy optional
// values already in config.yaml intact.
const normalized = Object.fromEntries(
Object.entries(input).map(([k, v]) => [k, v ?? '']),
) as Record<string, string>
await sparkConfigYaml.merge(effects, normalized)
return null
},
)