Merge branch 'next/major' of github.com:Start9Labs/start-os into feature/nvidia

This commit is contained in:
Aiden McClelland
2026-01-13 12:33:11 -07:00
24 changed files with 332 additions and 34 deletions

View File

@@ -15,6 +15,7 @@ import {
CreateTaskParams,
MountParams,
StatusInfo,
Manifest,
} from "./osBindings"
import {
PackageId,
@@ -83,6 +84,11 @@ export type Effects = {
mount(options: MountParams): Promise<string>
/** Returns a list of the ids of all installed packages */
getInstalledPackages(): Promise<string[]>
/** Returns the manifest of a service */
getServiceManifest(options: {
packageId: PackageId
callback?: () => void
}): Promise<Manifest>
// health
/** sets the result of a health check */

View File

@@ -0,0 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CallbackId } from "./CallbackId"
import type { PackageId } from "./PackageId"
export type GetServiceManifestParams = {
packageId: PackageId
callback?: CallbackId
}

View File

@@ -91,6 +91,7 @@ export { GetPackageParams } from "./GetPackageParams"
export { GetPackageResponseFull } from "./GetPackageResponseFull"
export { GetPackageResponse } from "./GetPackageResponse"
export { GetServiceInterfaceParams } from "./GetServiceInterfaceParams"
export { GetServiceManifestParams } from "./GetServiceManifestParams"
export { GetServicePortForwardParams } from "./GetServicePortForwardParams"
export { GetSslCertificateParams } from "./GetSslCertificateParams"
export { GetSslKeyParams } from "./GetSslKeyParams"

View File

@@ -13,6 +13,7 @@ import {
RunActionParams,
SetDataVersionParams,
SetMainStatus,
GetServiceManifestParams,
} from ".././osBindings"
import { CreateSubcontainerFsParams } from ".././osBindings"
import { DestroySubcontainerFsParams } from ".././osBindings"
@@ -64,7 +65,6 @@ describe("startosTypeValidation ", () => {
destroyFs: {} as DestroySubcontainerFsParams,
},
clearBindings: {} as ClearBindingsParams,
getInstalledPackages: undefined,
bind: {} as BindParams,
getHostInfo: {} as WithCallback<GetHostInfoParams>,
restart: undefined,
@@ -76,6 +76,8 @@ describe("startosTypeValidation ", () => {
getSslKey: {} as GetSslKeyParams,
getServiceInterface: {} as WithCallback<GetServiceInterfaceParams>,
setDependencies: {} as SetDependenciesParams,
getInstalledPackages: undefined,
getServiceManifest: {} as WithCallback<GetServiceManifestParams>,
getSystemSmtp: {} as WithCallback<GetSystemSmtpParams>,
getContainerIp: {} as WithCallback<GetContainerIpParams>,
getOsIp: undefined,

View File

@@ -46,7 +46,7 @@ import {
CheckDependencies,
checkDependencies,
} from "../../base/lib/dependencies/dependencies"
import { GetSslCertificate } from "./util"
import { GetSslCertificate, getServiceManifest } from "./util"
import { getDataVersion, setDataVersion } from "./version"
import { MaybeFn } from "../../base/lib/actions/setupActions"
import { GetInput } from "../../base/lib/actions/setupActions"
@@ -107,6 +107,7 @@ export class StartSdk<Manifest extends T.SDKManifest> {
| "getContainerIp"
| "getDataVersion"
| "setDataVersion"
| "getServiceManifest"
// prettier-ignore
type StartSdkEffectWrapper = {
@@ -441,11 +442,12 @@ export class StartSdk<Manifest extends T.SDKManifest> {
) => new ServiceInterfaceBuilder({ ...options, effects }),
getSystemSmtp: <E extends Effects>(effects: E) =>
new GetSystemSmtp(effects),
getSslCerificate: <E extends Effects>(
getSslCertificate: <E extends Effects>(
effects: E,
hostnames: string[],
algorithm?: T.Algorithm,
) => new GetSslCertificate(effects, hostnames, algorithm),
getServiceManifest,
healthCheck: {
checkPortListening,
checkWebUrl,

View File

@@ -0,0 +1,152 @@
import { Effects } from "../../../base/lib/Effects"
import { Manifest, PackageId } from "../../../base/lib/osBindings"
import { DropGenerator, DropPromise } from "../../../base/lib/util/Drop"
import { deepEqual } from "../../../base/lib/util/deepEqual"
export class GetServiceManifest<Mapped = Manifest> {
constructor(
readonly effects: Effects,
readonly packageId: PackageId,
readonly map: (manifest: Manifest | null) => Mapped,
readonly eq: (a: Mapped, b: Mapped) => boolean,
) {}
/**
* Returns the manifest of a service. Reruns the context from which it has been called if the underlying value changes
*/
async const() {
let abort = new AbortController()
const watch = this.watch(abort.signal)
const res = await watch.next()
if (this.effects.constRetry) {
watch.next().then(() => {
abort.abort()
this.effects.constRetry && this.effects.constRetry()
})
}
return res.value
}
/**
* Returns the manifest of a service. Does nothing if it changes
*/
async once() {
const manifest = await this.effects.getServiceManifest({
packageId: this.packageId,
})
return this.map(manifest)
}
private async *watchGen(abort?: AbortSignal) {
let prev = null as { value: Mapped } | null
const resolveCell = { resolve: () => {} }
this.effects.onLeaveContext(() => {
resolveCell.resolve()
})
abort?.addEventListener("abort", () => resolveCell.resolve())
while (this.effects.isInContext && !abort?.aborted) {
let callback: () => void = () => {}
const waitForNext = new Promise<void>((resolve) => {
callback = resolve
resolveCell.resolve = resolve
})
const next = this.map(
await this.effects.getServiceManifest({
packageId: this.packageId,
callback: () => callback(),
}),
)
if (!prev || !this.eq(prev.value, next)) {
prev = { value: next }
yield next
}
await waitForNext
}
return new Promise<never>((_, rej) => rej(new Error("aborted")))
}
/**
* Watches the manifest of a service. Returns an async iterator that yields whenever the value changes
*/
watch(abort?: AbortSignal): AsyncGenerator<Mapped, never, unknown> {
const ctrl = new AbortController()
abort?.addEventListener("abort", () => ctrl.abort())
return DropGenerator.of(this.watchGen(ctrl.signal), () => ctrl.abort())
}
/**
* Watches the manifest of a service. Takes a custom callback function to run whenever it changes
*/
onChange(
callback: (
value: Mapped | null,
error?: Error,
) => { cancel: boolean } | Promise<{ cancel: boolean }>,
) {
;(async () => {
const ctrl = new AbortController()
for await (const value of this.watch(ctrl.signal)) {
try {
const res = await callback(value)
if (res.cancel) {
ctrl.abort()
break
}
} catch (e) {
console.error(
"callback function threw an error @ GetServiceManifest.onChange",
e,
)
}
}
})()
.catch((e) => callback(null, e))
.catch((e) =>
console.error(
"callback function threw an error @ GetServiceManifest.onChange",
e,
),
)
}
/**
* Watches the manifest of a service. Returns when the predicate is true
*/
waitFor(pred: (value: Mapped) => boolean): Promise<Mapped> {
const ctrl = new AbortController()
return DropPromise.of(
Promise.resolve().then(async () => {
for await (const next of this.watchGen(ctrl.signal)) {
if (pred(next)) {
return next
}
}
throw new Error("context left before predicate passed")
}),
() => ctrl.abort(),
)
}
}
export function getServiceManifest(
effects: Effects,
packageId: PackageId,
): GetServiceManifest<Manifest>
export function getServiceManifest<Mapped>(
effects: Effects,
packageId: PackageId,
map: (manifest: Manifest | null) => Mapped,
eq?: (a: Mapped, b: Mapped) => boolean,
): GetServiceManifest<Mapped>
export function getServiceManifest<Mapped>(
effects: Effects,
packageId: PackageId,
map?: (manifest: Manifest | null) => Mapped,
eq?: (a: Mapped, b: Mapped) => boolean,
): GetServiceManifest<Mapped> {
return new GetServiceManifest(
effects,
packageId,
map ?? ((a) => a as Mapped),
eq ?? ((a, b) => deepEqual(a, b)),
)
}

View File

@@ -623,7 +623,10 @@ export class FileHelper<A> {
.split("\n")
.map((line) => line.trim())
.filter((line) => !line.startsWith("#") && line.includes("="))
.map((line) => line.split("=", 2)),
.map((line) => {
const pos = line.indexOf("=")
return [line.slice(0, pos), line.slice(pos + 1)]
}),
),
(data) => shape.unsafeCast(data),
transformers,

View File

@@ -1,5 +1,6 @@
export * from "../../../base/lib/util"
export { GetSslCertificate } from "./GetSslCertificate"
export { GetServiceManifest, getServiceManifest } from "./GetServiceManifest"
export { Drop } from "../../../base/lib/util/Drop"
export { Volume, Volumes } from "./Volume"

View File

@@ -1,12 +1,12 @@
{
"name": "@start9labs/start-sdk",
"version": "0.4.0-beta.46",
"version": "0.4.0-beta.47",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@start9labs/start-sdk",
"version": "0.4.0-beta.46",
"version": "0.4.0-beta.47",
"license": "MIT",
"dependencies": {
"@iarna/toml": "^3.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@start9labs/start-sdk",
"version": "0.4.0-beta.46",
"version": "0.4.0-beta.47",
"description": "Software development kit to facilitate packaging services for StartOS",
"main": "./package/lib/index.js",
"types": "./package/lib/index.d.ts",