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

This commit is contained in:
Aiden McClelland
2025-04-07 14:00:42 -06:00
77 changed files with 1474 additions and 716 deletions

View File

@@ -130,7 +130,10 @@ export type Effects = {
callback?: () => void
}): Promise<Host | null>
/** Returns the IP address of the container */
getContainerIp(): Promise<string>
getContainerIp(options: {
packageId?: PackageId
callback?: () => void
}): Promise<string>
/** Returns the IP address of StartOS */
getOsIp(): Promise<string>
// interface

View File

@@ -33,18 +33,6 @@ export const knownProtocols = {
secure: { ssl: false },
defaultPort: 22,
},
bitcoin: {
secure: { ssl: false },
defaultPort: 8333,
},
lightning: {
secure: { ssl: true },
defaultPort: 9735,
},
grpc: {
secure: { ssl: true },
defaultPort: 50051,
},
dns: {
secure: { ssl: false },
defaultPort: 53,

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 GetContainerIpParams = {
packageId?: PackageId
callback?: CallbackId
}

View File

@@ -81,6 +81,7 @@ export { ForgetInterfaceParams } from "./ForgetInterfaceParams"
export { FullIndex } from "./FullIndex"
export { FullProgress } from "./FullProgress"
export { GetActionInputParams } from "./GetActionInputParams"
export { GetContainerIpParams } from "./GetContainerIpParams"
export { GetHostInfoParams } from "./GetHostInfoParams"
export { GetOsAssetParams } from "./GetOsAssetParams"
export { GetOsVersionParams } from "./GetOsVersionParams"

View File

@@ -7,6 +7,7 @@ import {
ClearCallbacksParams,
ClearServiceInterfacesParams,
GetActionInputParams,
GetContainerIpParams,
GetStatusParams,
RequestActionParams,
RunActionParams,
@@ -77,7 +78,7 @@ describe("startosTypeValidation ", () => {
set: {} as any, // as SetStoreParams,
},
getSystemSmtp: {} as WithCallback<GetSystemSmtpParams>,
getContainerIp: undefined,
getContainerIp: {} as WithCallback<GetContainerIpParams>,
getOsIp: undefined,
getServicePortForward: {} as GetServicePortForwardParams,
clearServiceInterfaces: {} as ClearServiceInterfacesParams,

View File

@@ -47,7 +47,12 @@ import { GetSystemSmtp } from "./util"
import { nullIfEmpty } from "./util"
import { getServiceInterface, getServiceInterfaces } from "./util"
import { getStore } from "./store/getStore"
import { CommandOptions, MountOptions, SubContainer } from "./util/SubContainer"
import {
CommandOptions,
ExitError,
MountOptions,
SubContainer,
} from "./util/SubContainer"
import { splitCommand } from "./util"
import { Mounts } from "./mainFn/Mounts"
import { setupDependencies } from "../../base/lib/dependencies/setupDependencies"
@@ -104,7 +109,10 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
| "getHostInfo"
type MainUsedEffects = "setMainStatus" | "setHealth"
type CallbackEffects = "constRetry" | "clearCallbacks"
type AlreadyExposed = "getSslCertificate" | "getSystemSmtp"
type AlreadyExposed =
| "getSslCertificate"
| "getSystemSmtp"
| "getContainerIp"
// prettier-ignore
type StartSdkEffectWrapper = {
@@ -123,7 +131,6 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
getServicePortForward: (effects, ...args) =>
effects.getServicePortForward(...args),
clearBindings: (effects, ...args) => effects.clearBindings(...args),
getContainerIp: (effects, ...args) => effects.getContainerIp(...args),
getOsIp: (effects, ...args) => effects.getOsIp(...args),
getSslKey: (effects, ...args) => effects.getSslKey(...args),
setDataVersion: (effects, ...args) => effects.setDataVersion(...args),
@@ -191,7 +198,61 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
opts: { packageId: PackageId },
) => getServiceInterfaces(effects, opts),
},
getContainerIp: (
effects: T.Effects,
options: Omit<
Parameters<T.Effects["getContainerIp"]>[0],
"callback"
> = {},
) => {
async function* watch() {
while (true) {
let callback: () => void = () => {}
const waitForNext = new Promise<void>((resolve) => {
callback = resolve
})
yield await effects.getContainerIp({ ...options, callback })
await waitForNext
}
}
return {
const: () =>
effects.getContainerIp({
...options,
callback:
effects.constRetry &&
(() => effects.constRetry && effects.constRetry()),
}),
once: () => effects.getContainerIp(options),
watch,
onChange: (
callback: (
value: string | null,
error?: Error,
) => void | Promise<void>,
) => {
;(async () => {
for await (const value of watch()) {
try {
await callback(value)
} catch (e) {
console.error(
"callback function threw an error @ getContainerIp.onChange",
e,
)
}
}
})()
.catch((e) => callback(null, e))
.catch((e) =>
console.error(
"callback function threw an error @ getContainerIp.onChange",
e,
),
)
},
}
},
store: {
get: <E extends Effects, StoreValue = unknown>(
effects: E,
@@ -230,7 +291,7 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
},
command: T.CommandType,
options: CommandOptions & {
mounts?: { mountpoint: string; options: MountOptions }[]
mounts: Mounts<Manifest>
},
/**
* A name to use to refer to the ephemeral subcontainer for debugging purposes
@@ -527,7 +588,10 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
})
* ```
*/
setupInstall: (fn: InstallFn<Manifest, Store>) => Install.of(fn),
setupInstall: (
fn: InstallFn<Manifest, Store>,
preFn?: InstallFn<Manifest, Store>,
) => Install.of(fn, preFn),
/**
* @description Use this function to determine how this service will be hosted and served. The function executes on service install, service update, and inputSpec save.
*
@@ -1076,7 +1140,7 @@ export async function runCommand<Manifest extends T.SDKManifest>(
image: { imageId: keyof Manifest["images"] & T.ImageId; sharedRun?: boolean },
command: T.CommandType,
options: CommandOptions & {
mounts?: { mountpoint: string; options: MountOptions }[]
mounts: Mounts<Manifest>
},
name?: string,
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> {
@@ -1094,7 +1158,7 @@ export async function runCommand<Manifest extends T.SDKManifest>(
return SubContainer.with(
effects,
image,
options.mounts || [],
options.mounts.build(),
name ||
commands
.map((c) => {
@@ -1105,6 +1169,13 @@ export async function runCommand<Manifest extends T.SDKManifest>(
}
})
.join(" "),
(subcontainer) => subcontainer.exec(commands),
async (subcontainer) => {
const res = await subcontainer.exec(commands)
if (res.exitCode || res.exitSignal) {
throw new ExitError(commands[0], res)
} else {
return res
}
},
)
}

View File

@@ -3,6 +3,7 @@ import { ExtendedVersion } from "../../../base/lib/exver"
import { UpdateServiceInterfaces } from "../../../base/lib/interfaces/setupInterfaces"
import { ExposedStorePaths } from "../../../base/lib/types"
import * as T from "../../../base/lib/types"
import { StorePath } from "../util"
import { VersionGraph } from "../version/VersionGraph"
import { Install } from "./setupInstall"
import { Uninstall } from "./setupUninstall"
@@ -16,6 +17,7 @@ export function setupInit<Manifest extends T.SDKManifest, Store>(
effects: T.Effects
}) => Promise<null | void | undefined>,
actions: Actions<Store, any>,
initStore: Store,
exposedStore: ExposedStorePaths,
): {
packageInit: T.ExpectedExports.packageInit
@@ -53,6 +55,14 @@ export function setupInit<Manifest extends T.SDKManifest, Store>(
}
},
containerInit: async (opts) => {
const prev = await opts.effects.getDataVersion()
if (!prev) {
await opts.effects.store.set({
path: "" as StorePath,
value: initStore,
})
await install.preInstall(opts)
}
await setServiceInterfaces({
...opts,
})

View File

@@ -4,11 +4,15 @@ export type InstallFn<Manifest extends T.SDKManifest, Store> = (opts: {
effects: T.Effects
}) => Promise<null | void | undefined>
export class Install<Manifest extends T.SDKManifest, Store> {
private constructor(readonly fn: InstallFn<Manifest, Store>) {}
private constructor(
readonly fn: InstallFn<Manifest, Store>,
readonly preFn?: InstallFn<Manifest, Store>,
) {}
static of<Manifest extends T.SDKManifest, Store>(
fn: InstallFn<Manifest, Store>,
preFn?: InstallFn<Manifest, Store>,
) {
return new Install(fn)
return new Install(fn, preFn)
}
async install({ effects }: Parameters<T.ExpectedExports.packageInit>[0]) {
@@ -16,10 +20,18 @@ export class Install<Manifest extends T.SDKManifest, Store> {
effects,
})
}
async preInstall({ effects }: Parameters<T.ExpectedExports.packageInit>[0]) {
this.preFn &&
(await this.preFn({
effects,
}))
}
}
export function setupInstall<Manifest extends T.SDKManifest, Store>(
fn: InstallFn<Manifest, Store>,
preFn?: InstallFn<Manifest, Store>,
) {
return Install.of(fn)
return Install.of(fn, preFn)
}

View File

@@ -172,7 +172,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
daemon,
daemonIndex,
options.requires
.map((x) => this.ids.indexOf(id as any))
.map((x) => this.ids.indexOf(x))
.filter((x) => x >= 0)
.map((id) => this.healthDaemons[id]),
id,

View File

@@ -467,3 +467,25 @@ export type MountOptionsBackup = {
function wait(time: number) {
return new Promise((resolve) => setTimeout(resolve, time))
}
export class ExitError extends Error {
constructor(
readonly command: string,
readonly result: {
exitCode: number | null
exitSignal: T.Signals | null
stdout: string | Buffer
stderr: string | Buffer
},
) {
let message: string
if (result.exitCode) {
message = `${command} failed with exit code ${result.exitCode}: ${result.stderr}`
} else if (result.exitSignal) {
message = `${command} terminated with signal ${result.exitSignal}: ${result.stderr}`
} else {
message = `${command} succeeded: ${result.stdout}`
}
super(message)
}
}

View File

@@ -1,12 +1,12 @@
{
"name": "@start9labs/start-sdk",
"version": "0.3.6-beta.18",
"version": "0.3.6-beta.20",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@start9labs/start-sdk",
"version": "0.3.6-beta.18",
"version": "0.3.6-beta.20",
"license": "MIT",
"dependencies": {
"@iarna/toml": "^2.2.5",

View File

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