misc fixes for alpha.16 (#3091)

* port misc fixes from feature/nvidia

* switch back to official tor proxy on 9050

* refactor OpenUI

* fix typo

* fixes, plus getServiceManifest

* fix EffectCreator, bump to beta.47

* fixes
This commit is contained in:
Aiden McClelland
2026-01-10 12:58:17 -07:00
committed by GitHub
parent 466b9217b5
commit e8ef39adad
37 changed files with 491 additions and 231 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

@@ -153,10 +153,21 @@ export type SDKManifest = {
// this is hacky but idk a more elegant way
type ArchOptions = {
0: ["x86_64", "aarch64"]
1: ["aarch64", "x86_64"]
2: ["x86_64"]
3: ["aarch64"]
0: ["x86_64", "aarch64", "riscv64"]
1: ["aarch64", "x86_64", "riscv64"]
2: ["x86_64", "riscv64", "aarch64"]
3: ["aarch64", "riscv64", "x86_64"]
4: ["riscv64", "x86_64", "aarch64"]
5: ["riscv64", "aarch64", "x86_64"]
6: ["x86_64", "aarch64"]
7: ["aarch64", "x86_64"]
8: ["x86_64", "riscv64"]
9: ["aarch64", "riscv64"]
10: ["riscv64", "aarch64"]
11: ["riscv64", "x86_64"]
12: ["x86_64"]
13: ["aarch64"]
14: ["riscv64"]
}
export type SDKImageInputSpec = {
[A in keyof ArchOptions]: {

View File

@@ -5,6 +5,7 @@ import { Effects } from "../Effects"
import { DropGenerator, DropPromise } from "./Drop"
import { IpAddress, IPV6_LINK_LOCAL } from "./ip"
import { deepEqual } from "./deepEqual"
import { once } from "./once"
export type UrlString = string
export type HostId = string
@@ -255,6 +256,21 @@ export const filledAddress = (
function filledAddressFromHostnames<F extends Filter>(
hostnames: HostnameInfo[],
): Filled<F> & AddressInfo {
const getNonLocal = once(() =>
filledAddressFromHostnames<typeof nonLocalFilter & F>(
filterRec(hostnames, nonLocalFilter, false),
),
)
const getPublic = once(() =>
filledAddressFromHostnames<typeof publicFilter & F>(
filterRec(hostnames, publicFilter, false),
),
)
const getOnion = once(() =>
filledAddressFromHostnames<typeof onionFilter & F>(
filterRec(hostnames, onionFilter, false),
),
)
return {
...addressInfo,
hostnames,
@@ -273,19 +289,13 @@ export const filledAddress = (
)
},
get nonLocal(): Filled<typeof nonLocalFilter & F> {
return filledAddressFromHostnames<typeof nonLocalFilter & F>(
filterRec(hostnames, nonLocalFilter, false),
)
return getNonLocal()
},
get public(): Filled<typeof publicFilter & F> {
return filledAddressFromHostnames<typeof publicFilter & F>(
filterRec(hostnames, publicFilter, false),
)
return getPublic()
},
get onion(): Filled<typeof onionFilter & F> {
return filledAddressFromHostnames<typeof onionFilter & F>(
filterRec(hostnames, onionFilter, false),
)
return getOnion()
},
}
}

View File

@@ -4,7 +4,11 @@ export { getDefaultString } from "./getDefaultString"
export * from "./ip"
/// Not being used, but known to be browser compatible
export { GetServiceInterface, getServiceInterface } from "./getServiceInterface"
export {
GetServiceInterface,
getServiceInterface,
filledAddress,
} from "./getServiceInterface"
export { getServiceInterfaces } from "./getServiceInterfaces"
export { once } from "./once"
export { asError } from "./asError"

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

@@ -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",