diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts index 7852a1ec7..1a8b54e22 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts @@ -28,9 +28,15 @@ export class DockerProcedureContainer { await fs.mkdir(path, { recursive: true }) const volumeMount = volumes[mount] if (volumeMount.type === "data") { - await overlay.mount({ type: "volume", id: mount }, mounts[mount]) + await overlay.mount( + { type: "volume", id: mount, subpath: null, readonly: false }, + mounts[mount], + ) } else if (volumeMount.type === "assets") { - await overlay.mount({ type: "assets", id: mount }, mounts[mount]) + await overlay.mount( + { type: "assets", id: mount, subpath: null }, + mounts[mount], + ) } else if (volumeMount.type === "certificate") { volumeMount const certChain = await effects.getSslCertificate({ @@ -56,7 +62,7 @@ export class DockerProcedureContainer { location: path, target: { packageId: volumeMount["package-id"], - path: volumeMount.path, + subpath: volumeMount.path, readonly: volumeMount.readonly, volumeId: volumeMount["volume-id"], }, diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts index 9a64fd33d..e5e532246 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts @@ -2,8 +2,7 @@ import { PolyfillEffects } from "./polyfillEffects" import { DockerProcedureContainer } from "./DockerProcedureContainer" import { SystemForEmbassy } from "." import { HostSystemStartOs } from "../../HostSystemStartOs" -import { util, Daemons, types as T } from "@start9labs/start-sdk" -import { exec } from "child_process" +import { Daemons, T, daemons } from "@start9labs/start-sdk" const EMBASSY_HEALTH_INTERVAL = 15 * 1000 const EMBASSY_PROPERTIES_LOOP = 30 * 1000 @@ -39,7 +38,6 @@ export class MainLoop { private async constructMainEvent() { const { system, effects } = this - const utils = util.createUtils(effects) const currentCommand: [string, ...string[]] = [ system.manifest.main.entrypoint, ...system.manifest.main.args, @@ -67,7 +65,8 @@ export class MainLoop { // }), // } } - const daemon = await utils.runDaemon( + const daemon = await daemons.runDaemon()( + this.effects, this.system.manifest.main.image, currentCommand, { diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index 79c797293..a774d7ba0 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -1,4 +1,4 @@ -import { types as T, util, EmVer, Utils } from "@start9labs/start-sdk" +import { types as T, util, EmVer } from "@start9labs/start-sdk" import * as fs from "fs/promises" import { PolyfillEffects } from "./polyfillEffects" @@ -22,6 +22,9 @@ import { any, tuple, number, + anyOf, + deferred, + Parser, } from "ts-matches" import { HostSystemStartOs } from "../../HostSystemStartOs" import { JsonPath, unNestPath } from "../../../Models/JsonPath" @@ -37,58 +40,94 @@ const MANIFEST_LOCATION = "/usr/lib/startos/package/embassyManifest.json" const EMBASSY_JS_LOCATION = "/usr/lib/startos/package/embassy.js" const EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig" -const matchPackagePropertyObject = object({ - value: any, - type: literal("object"), - description: string, -}) - -const matchPackagePropertyString = object( - { - type: literal("string"), +export type PackagePropertiesV2 = { + [name: string]: PackagePropertyObject | PackagePropertyString +} +export type PackagePropertyString = { + type: "string" + description?: string + value: string + /** Let's the ui make this copyable button */ + copyable?: boolean + /** Let the ui create a qr for this field */ + qr?: boolean + /** Hiding the value unless toggled off for field */ + masked?: boolean +} +export type PackagePropertyObject = { + value: PackagePropertiesV2 + type: "object" + description: string +} +const [matchPackageProperties, setMatchPackageProperties] = + deferred() +const matchPackagePropertyObject: Parser = + object({ + value: matchPackageProperties, + type: literal("object"), description: string, - value: string, - copyable: boolean, - qr: boolean, - masked: boolean, - }, - ["copyable", "description", "qr", "masked"], + }) + +const matchPackagePropertyString: Parser = + object( + { + type: literal("string"), + description: string, + value: string, + copyable: boolean, + qr: boolean, + masked: boolean, + }, + ["copyable", "description", "qr", "masked"], + ) +setMatchPackageProperties( + dictionary([ + string, + anyOf(matchPackagePropertyObject, matchPackagePropertyString), + ]), ) const matchProperties = object({ version: literal(2), - data: any, + data: matchPackageProperties, }) type ExportUi = { - value: string - title: string - description?: string | undefined - masked?: boolean | undefined - copyable?: boolean | undefined - qr?: boolean | undefined + values: { [key: string]: any } + expose: { [key: string]: T.ExposeUiPathsAll } } -function propertiesToExportUi(properties: unknown): ExportUi[] { - if (!object.test(properties)) return [] - const paths: ExportUi[] = [] - for (const key in properties) { - const value: unknown = (properties as any)[key] - if (matchPackagePropertyObject.test(value)) { - paths.push(...propertiesToExportUi(value)) +function propertiesToExportUi( + properties: PackagePropertiesV2, + previousPath = "", +): ExportUi { + const exportUi: ExportUi = { + values: {}, + expose: {}, + } + for (const [key, value] of Object.entries(properties)) { + const path = `${previousPath}/${key}` + if (value.type === "object") { + const { values, expose } = propertiesToExportUi(value.value, path) + exportUi.values[key] = values + exportUi.expose[key] = { + type: "object", + value: expose, + description: value.description, + } continue } - if (!matchPackagePropertyString.test(value)) continue - paths.push({ - value: value.value, - title: key, - description: value.description, - masked: value.masked, - copyable: value.copyable, - qr: value.qr, - }) + exportUi.values[key] = value.value + exportUi.expose[key] = { + type: "string", + path, + description: value.description ?? null, + masked: value.masked ?? false, + copyable: value.copyable ?? null, + qr: value.qr ?? null, + } } - return paths + return exportUi } export class SystemForEmbassy implements System { @@ -455,14 +494,9 @@ export class SystemForEmbassy implements System { const exposeUis = propertiesToExportUi(properties.data) await effects.store.set({ path: "/properties", - value: exposeUis.map((x) => x.value), - }) - await effects.exposeUi({ - paths: exposeUis.map((x, i) => ({ - ...x, - path: `/properties/${i}`, - })) as any[], + value: exposeUis.values, }) + await effects.exposeUi(exposeUis.expose) } else if (setConfigValue.type === "script") { const moduleCode = this.moduleCode const method = moduleCode.properties @@ -479,14 +513,9 @@ export class SystemForEmbassy implements System { const exposeUis = propertiesToExportUi(properties.data) await effects.store.set({ path: "/properties", - value: exposeUis.map((x) => x.value), - }) - await effects.exposeUi({ - paths: exposeUis.map((x, i) => ({ - ...x, - path: `/properties/${i}`, - })) as any[], + value: exposeUis.values, }) + await effects.exposeUi(exposeUis.expose) } } private async health( @@ -991,7 +1020,6 @@ async function updateConfig( ) { if (!dictionary([string, unknown]).test(spec)) return if (!dictionary([string, unknown]).test(mutConfigValue)) return - const utils = util.createUtils(effects) for (const key in spec) { const specValue = spec[key] @@ -1020,8 +1048,8 @@ async function updateConfig( if (matchPointerPackage.test(specValue)) { if (specValue.target === "tor-key") throw new Error("This service uses an unsupported target TorKey") - const filled = await utils.serviceInterface - .get({ + const filled = await util + .getServiceInterface(effects, { packageId: specValue["package-id"], id: specValue.interface, }) diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts index 65a827103..4c176b2f7 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts @@ -3,23 +3,18 @@ import * as oet from "./oldEmbassyTypes" import { Volume } from "../../../Models/Volume" import * as child_process from "child_process" import { promisify } from "util" -import { util, Utils } from "@start9labs/start-sdk" -import { Manifest } from "./matchManifest" +import { Daemons, startSdk, T } from "@start9labs/start-sdk" import { HostSystemStartOs } from "../../HostSystemStartOs" import "isomorphic-fetch" - -const { createUtils } = util +import { Manifest } from "./matchManifest" const execFile = promisify(child_process.execFile) export class PolyfillEffects implements oet.Effects { - private utils: Utils constructor( readonly effects: HostSystemStartOs, private manifest: Manifest, - ) { - this.utils = createUtils(effects as any) - } + ) {} async writeFile(input: { path: string volumeId: string @@ -99,9 +94,14 @@ export class PolyfillEffects implements oet.Effects { args?: string[] | undefined timeoutMillis?: number | undefined }): Promise> { - return this.utils - .runCommand(this.manifest.main.image, [command, ...(args || [])], {}) - .then((x) => ({ + return startSdk + .runCommand( + this.effects, + this.manifest.main.image, + [command, ...(args || [])], + {}, + ) + .then((x: any) => ({ stderr: x.stderr.toString(), stdout: x.stdout.toString(), })) diff --git a/sdk/lib/StartSdk.ts b/sdk/lib/StartSdk.ts index 42a6b8409..315dacecf 100644 --- a/sdk/lib/StartSdk.ts +++ b/sdk/lib/StartSdk.ts @@ -205,16 +205,7 @@ export class StartSdk { mounts?: { path: string; options: MountOptions }[] }, ): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> => { - const commands = splitCommand(command) - const overlay = await Overlay.of(effects, imageId) - try { - for (let mount of options.mounts || []) { - await overlay.mount(mount.options, mount.path) - } - return await overlay.exec(commands) - } finally { - await overlay.destroy() - } + return runCommand(effects, imageId, command, options) }, createDynamicAction: < @@ -654,3 +645,23 @@ export class StartSdk { } } } + +export async function runCommand( + effects: Effects, + imageId: Manifest["images"][number], + command: string | [string, ...string[]], + options: CommandOptions & { + mounts?: { path: string; options: MountOptions }[] + }, +): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> { + const commands = splitCommand(command) + const overlay = await Overlay.of(effects, imageId) + try { + for (let mount of options.mounts || []) { + await overlay.mount(mount.options, mount.path) + } + return await overlay.exec(commands) + } finally { + await overlay.destroy() + } +} diff --git a/sdk/lib/index.ts b/sdk/lib/index.ts index 2b22e2c83..dbdc2ce7d 100644 --- a/sdk/lib/index.ts +++ b/sdk/lib/index.ts @@ -10,6 +10,7 @@ export * as config from "./config" export * as configBuilder from "./config/builder" export * as configTypes from "./config/configTypes" export * as dependencyConfig from "./dependencyConfig" +export * as daemons from "./mainFn/Daemons" export * as health from "./health" export * as healthFns from "./health/checkFns" export * as inits from "./inits" @@ -17,8 +18,10 @@ export * as mainFn from "./mainFn" export * as manifest from "./manifest" export * as toml from "@iarna/toml" export * as types from "./types" +export * as T from "./types" export * as util from "./util" export * as yaml from "yaml" +export * as startSdk from "./StartSdk" export * as matches from "ts-matches" export * as YAML from "yaml" diff --git a/sdk/lib/mainFn/Daemons.ts b/sdk/lib/mainFn/Daemons.ts index 300b61b83..540ca3d37 100644 --- a/sdk/lib/mainFn/Daemons.ts +++ b/sdk/lib/mainFn/Daemons.ts @@ -43,7 +43,7 @@ type Daemon< type ErrorDuplicateId = `The id '${Id}' is already used` -const runDaemon = +export const runDaemon = () => async ( effects: Effects, diff --git a/sdk/lib/types.ts b/sdk/lib/types.ts index 6d6b2d7d7..2da7a6fcc 100644 --- a/sdk/lib/types.ts +++ b/sdk/lib/types.ts @@ -7,6 +7,8 @@ import { BindOptions, Scheme } from "./interfaces/Host" import { Daemons } from "./mainFn/Daemons" import { UrlString } from "./util/getServiceInterface" +export { SDKManifest } from "./manifest/ManifestTypes" + export type ExportedAction = (options: { effects: Effects input?: Record diff --git a/sdk/lib/util/index.ts b/sdk/lib/util/index.ts index b3cab7183..8cafa2cf6 100644 --- a/sdk/lib/util/index.ts +++ b/sdk/lib/util/index.ts @@ -9,6 +9,8 @@ import "./Overlay" import "./once" import { SDKManifest } from "../manifest/ManifestTypes" +export { GetServiceInterface, getServiceInterface } from "./getServiceInterface" +export { getServiceInterfaces } from "./getServiceInterfaces" // prettier-ignore export type FlattenIntersection = T extends ArrayLike ? T : diff --git a/sdk/lib/util/splitCommand.ts b/sdk/lib/util/splitCommand.ts index bf55a74c3..69f00a5a7 100644 --- a/sdk/lib/util/splitCommand.ts +++ b/sdk/lib/util/splitCommand.ts @@ -1,8 +1,8 @@ import { arrayOf, string } from "ts-matches" import { ValidIfNoStupidEscape } from "../types" -export const splitCommand = ( - command: ValidIfNoStupidEscape | [string, ...string[]], +export const splitCommand = ( + command: string | [string, ...string[]], ): string[] => { if (arrayOf(string).test(command)) return command return String(command)