mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
chore: Update the types and get the container-runtime working
This commit is contained in:
@@ -28,9 +28,15 @@ export class DockerProcedureContainer {
|
|||||||
await fs.mkdir(path, { recursive: true })
|
await fs.mkdir(path, { recursive: true })
|
||||||
const volumeMount = volumes[mount]
|
const volumeMount = volumes[mount]
|
||||||
if (volumeMount.type === "data") {
|
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") {
|
} 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") {
|
} else if (volumeMount.type === "certificate") {
|
||||||
volumeMount
|
volumeMount
|
||||||
const certChain = await effects.getSslCertificate({
|
const certChain = await effects.getSslCertificate({
|
||||||
@@ -56,7 +62,7 @@ export class DockerProcedureContainer {
|
|||||||
location: path,
|
location: path,
|
||||||
target: {
|
target: {
|
||||||
packageId: volumeMount["package-id"],
|
packageId: volumeMount["package-id"],
|
||||||
path: volumeMount.path,
|
subpath: volumeMount.path,
|
||||||
readonly: volumeMount.readonly,
|
readonly: volumeMount.readonly,
|
||||||
volumeId: volumeMount["volume-id"],
|
volumeId: volumeMount["volume-id"],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { PolyfillEffects } from "./polyfillEffects"
|
|||||||
import { DockerProcedureContainer } from "./DockerProcedureContainer"
|
import { DockerProcedureContainer } from "./DockerProcedureContainer"
|
||||||
import { SystemForEmbassy } from "."
|
import { SystemForEmbassy } from "."
|
||||||
import { HostSystemStartOs } from "../../HostSystemStartOs"
|
import { HostSystemStartOs } from "../../HostSystemStartOs"
|
||||||
import { util, Daemons, types as T } from "@start9labs/start-sdk"
|
import { Daemons, T, daemons } from "@start9labs/start-sdk"
|
||||||
import { exec } from "child_process"
|
|
||||||
|
|
||||||
const EMBASSY_HEALTH_INTERVAL = 15 * 1000
|
const EMBASSY_HEALTH_INTERVAL = 15 * 1000
|
||||||
const EMBASSY_PROPERTIES_LOOP = 30 * 1000
|
const EMBASSY_PROPERTIES_LOOP = 30 * 1000
|
||||||
@@ -39,7 +38,6 @@ export class MainLoop {
|
|||||||
|
|
||||||
private async constructMainEvent() {
|
private async constructMainEvent() {
|
||||||
const { system, effects } = this
|
const { system, effects } = this
|
||||||
const utils = util.createUtils(effects)
|
|
||||||
const currentCommand: [string, ...string[]] = [
|
const currentCommand: [string, ...string[]] = [
|
||||||
system.manifest.main.entrypoint,
|
system.manifest.main.entrypoint,
|
||||||
...system.manifest.main.args,
|
...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,
|
this.system.manifest.main.image,
|
||||||
currentCommand,
|
currentCommand,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 * as fs from "fs/promises"
|
||||||
|
|
||||||
import { PolyfillEffects } from "./polyfillEffects"
|
import { PolyfillEffects } from "./polyfillEffects"
|
||||||
@@ -22,6 +22,9 @@ import {
|
|||||||
any,
|
any,
|
||||||
tuple,
|
tuple,
|
||||||
number,
|
number,
|
||||||
|
anyOf,
|
||||||
|
deferred,
|
||||||
|
Parser,
|
||||||
} from "ts-matches"
|
} from "ts-matches"
|
||||||
import { HostSystemStartOs } from "../../HostSystemStartOs"
|
import { HostSystemStartOs } from "../../HostSystemStartOs"
|
||||||
import { JsonPath, unNestPath } from "../../../Models/JsonPath"
|
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_JS_LOCATION = "/usr/lib/startos/package/embassy.js"
|
||||||
const EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig"
|
const EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig"
|
||||||
|
|
||||||
const matchPackagePropertyObject = object({
|
export type PackagePropertiesV2 = {
|
||||||
value: any,
|
[name: string]: PackagePropertyObject | PackagePropertyString
|
||||||
type: literal("object"),
|
}
|
||||||
description: string,
|
export type PackagePropertyString = {
|
||||||
})
|
type: "string"
|
||||||
|
description?: string
|
||||||
const matchPackagePropertyString = object(
|
value: string
|
||||||
{
|
/** Let's the ui make this copyable button */
|
||||||
type: literal("string"),
|
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<PackagePropertiesV2>()
|
||||||
|
const matchPackagePropertyObject: Parser<unknown, PackagePropertyObject> =
|
||||||
|
object({
|
||||||
|
value: matchPackageProperties,
|
||||||
|
type: literal("object"),
|
||||||
description: string,
|
description: string,
|
||||||
value: string,
|
})
|
||||||
copyable: boolean,
|
|
||||||
qr: boolean,
|
const matchPackagePropertyString: Parser<unknown, PackagePropertyString> =
|
||||||
masked: boolean,
|
object(
|
||||||
},
|
{
|
||||||
["copyable", "description", "qr", "masked"],
|
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({
|
const matchProperties = object({
|
||||||
version: literal(2),
|
version: literal(2),
|
||||||
data: any,
|
data: matchPackageProperties,
|
||||||
})
|
})
|
||||||
|
|
||||||
type ExportUi = {
|
type ExportUi = {
|
||||||
value: string
|
values: { [key: string]: any }
|
||||||
title: string
|
expose: { [key: string]: T.ExposeUiPathsAll }
|
||||||
description?: string | undefined
|
|
||||||
masked?: boolean | undefined
|
|
||||||
copyable?: boolean | undefined
|
|
||||||
qr?: boolean | undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function propertiesToExportUi(properties: unknown): ExportUi[] {
|
function propertiesToExportUi(
|
||||||
if (!object.test(properties)) return []
|
properties: PackagePropertiesV2,
|
||||||
const paths: ExportUi[] = []
|
previousPath = "",
|
||||||
for (const key in properties) {
|
): ExportUi {
|
||||||
const value: unknown = (properties as any)[key]
|
const exportUi: ExportUi = {
|
||||||
if (matchPackagePropertyObject.test(value)) {
|
values: {},
|
||||||
paths.push(...propertiesToExportUi(value))
|
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
|
continue
|
||||||
}
|
}
|
||||||
if (!matchPackagePropertyString.test(value)) continue
|
exportUi.values[key] = value.value
|
||||||
paths.push({
|
exportUi.expose[key] = {
|
||||||
value: value.value,
|
type: "string",
|
||||||
title: key,
|
path,
|
||||||
description: value.description,
|
description: value.description ?? null,
|
||||||
masked: value.masked,
|
masked: value.masked ?? false,
|
||||||
copyable: value.copyable,
|
copyable: value.copyable ?? null,
|
||||||
qr: value.qr,
|
qr: value.qr ?? null,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
return paths
|
return exportUi
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SystemForEmbassy implements System {
|
export class SystemForEmbassy implements System {
|
||||||
@@ -455,14 +494,9 @@ export class SystemForEmbassy implements System {
|
|||||||
const exposeUis = propertiesToExportUi(properties.data)
|
const exposeUis = propertiesToExportUi(properties.data)
|
||||||
await effects.store.set<any, any>({
|
await effects.store.set<any, any>({
|
||||||
path: "/properties",
|
path: "/properties",
|
||||||
value: exposeUis.map((x) => x.value),
|
value: exposeUis.values,
|
||||||
})
|
|
||||||
await effects.exposeUi({
|
|
||||||
paths: exposeUis.map((x, i) => ({
|
|
||||||
...x,
|
|
||||||
path: `/properties/${i}`,
|
|
||||||
})) as any[],
|
|
||||||
})
|
})
|
||||||
|
await effects.exposeUi(exposeUis.expose)
|
||||||
} else if (setConfigValue.type === "script") {
|
} else if (setConfigValue.type === "script") {
|
||||||
const moduleCode = this.moduleCode
|
const moduleCode = this.moduleCode
|
||||||
const method = moduleCode.properties
|
const method = moduleCode.properties
|
||||||
@@ -479,14 +513,9 @@ export class SystemForEmbassy implements System {
|
|||||||
const exposeUis = propertiesToExportUi(properties.data)
|
const exposeUis = propertiesToExportUi(properties.data)
|
||||||
await effects.store.set<any, any>({
|
await effects.store.set<any, any>({
|
||||||
path: "/properties",
|
path: "/properties",
|
||||||
value: exposeUis.map((x) => x.value),
|
value: exposeUis.values,
|
||||||
})
|
|
||||||
await effects.exposeUi({
|
|
||||||
paths: exposeUis.map((x, i) => ({
|
|
||||||
...x,
|
|
||||||
path: `/properties/${i}`,
|
|
||||||
})) as any[],
|
|
||||||
})
|
})
|
||||||
|
await effects.exposeUi(exposeUis.expose)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private async health(
|
private async health(
|
||||||
@@ -991,7 +1020,6 @@ async function updateConfig(
|
|||||||
) {
|
) {
|
||||||
if (!dictionary([string, unknown]).test(spec)) return
|
if (!dictionary([string, unknown]).test(spec)) return
|
||||||
if (!dictionary([string, unknown]).test(mutConfigValue)) return
|
if (!dictionary([string, unknown]).test(mutConfigValue)) return
|
||||||
const utils = util.createUtils(effects)
|
|
||||||
for (const key in spec) {
|
for (const key in spec) {
|
||||||
const specValue = spec[key]
|
const specValue = spec[key]
|
||||||
|
|
||||||
@@ -1020,8 +1048,8 @@ async function updateConfig(
|
|||||||
if (matchPointerPackage.test(specValue)) {
|
if (matchPointerPackage.test(specValue)) {
|
||||||
if (specValue.target === "tor-key")
|
if (specValue.target === "tor-key")
|
||||||
throw new Error("This service uses an unsupported target TorKey")
|
throw new Error("This service uses an unsupported target TorKey")
|
||||||
const filled = await utils.serviceInterface
|
const filled = await util
|
||||||
.get({
|
.getServiceInterface(effects, {
|
||||||
packageId: specValue["package-id"],
|
packageId: specValue["package-id"],
|
||||||
id: specValue.interface,
|
id: specValue.interface,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,23 +3,18 @@ import * as oet from "./oldEmbassyTypes"
|
|||||||
import { Volume } from "../../../Models/Volume"
|
import { Volume } from "../../../Models/Volume"
|
||||||
import * as child_process from "child_process"
|
import * as child_process from "child_process"
|
||||||
import { promisify } from "util"
|
import { promisify } from "util"
|
||||||
import { util, Utils } from "@start9labs/start-sdk"
|
import { Daemons, startSdk, T } from "@start9labs/start-sdk"
|
||||||
import { Manifest } from "./matchManifest"
|
|
||||||
import { HostSystemStartOs } from "../../HostSystemStartOs"
|
import { HostSystemStartOs } from "../../HostSystemStartOs"
|
||||||
import "isomorphic-fetch"
|
import "isomorphic-fetch"
|
||||||
|
import { Manifest } from "./matchManifest"
|
||||||
const { createUtils } = util
|
|
||||||
|
|
||||||
const execFile = promisify(child_process.execFile)
|
const execFile = promisify(child_process.execFile)
|
||||||
|
|
||||||
export class PolyfillEffects implements oet.Effects {
|
export class PolyfillEffects implements oet.Effects {
|
||||||
private utils: Utils<any, any>
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly effects: HostSystemStartOs,
|
readonly effects: HostSystemStartOs,
|
||||||
private manifest: Manifest,
|
private manifest: Manifest,
|
||||||
) {
|
) {}
|
||||||
this.utils = createUtils(effects as any)
|
|
||||||
}
|
|
||||||
async writeFile(input: {
|
async writeFile(input: {
|
||||||
path: string
|
path: string
|
||||||
volumeId: string
|
volumeId: string
|
||||||
@@ -99,9 +94,14 @@ export class PolyfillEffects implements oet.Effects {
|
|||||||
args?: string[] | undefined
|
args?: string[] | undefined
|
||||||
timeoutMillis?: number | undefined
|
timeoutMillis?: number | undefined
|
||||||
}): Promise<oet.ResultType<string>> {
|
}): Promise<oet.ResultType<string>> {
|
||||||
return this.utils
|
return startSdk
|
||||||
.runCommand(this.manifest.main.image, [command, ...(args || [])], {})
|
.runCommand(
|
||||||
.then((x) => ({
|
this.effects,
|
||||||
|
this.manifest.main.image,
|
||||||
|
[command, ...(args || [])],
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
.then((x: any) => ({
|
||||||
stderr: x.stderr.toString(),
|
stderr: x.stderr.toString(),
|
||||||
stdout: x.stdout.toString(),
|
stdout: x.stdout.toString(),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -205,16 +205,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
mounts?: { path: string; options: MountOptions }[]
|
mounts?: { path: string; options: MountOptions }[]
|
||||||
},
|
},
|
||||||
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> => {
|
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> => {
|
||||||
const commands = splitCommand(command)
|
return runCommand<Manifest>(effects, imageId, command, options)
|
||||||
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()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createDynamicAction: <
|
createDynamicAction: <
|
||||||
@@ -654,3 +645,23 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function runCommand<Manifest extends SDKManifest>(
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export * as config from "./config"
|
|||||||
export * as configBuilder from "./config/builder"
|
export * as configBuilder from "./config/builder"
|
||||||
export * as configTypes from "./config/configTypes"
|
export * as configTypes from "./config/configTypes"
|
||||||
export * as dependencyConfig from "./dependencyConfig"
|
export * as dependencyConfig from "./dependencyConfig"
|
||||||
|
export * as daemons from "./mainFn/Daemons"
|
||||||
export * as health from "./health"
|
export * as health from "./health"
|
||||||
export * as healthFns from "./health/checkFns"
|
export * as healthFns from "./health/checkFns"
|
||||||
export * as inits from "./inits"
|
export * as inits from "./inits"
|
||||||
@@ -17,8 +18,10 @@ export * as mainFn from "./mainFn"
|
|||||||
export * as manifest from "./manifest"
|
export * as manifest from "./manifest"
|
||||||
export * as toml from "@iarna/toml"
|
export * as toml from "@iarna/toml"
|
||||||
export * as types from "./types"
|
export * as types from "./types"
|
||||||
|
export * as T from "./types"
|
||||||
export * as util from "./util"
|
export * as util from "./util"
|
||||||
export * as yaml from "yaml"
|
export * as yaml from "yaml"
|
||||||
|
export * as startSdk from "./StartSdk"
|
||||||
|
|
||||||
export * as matches from "ts-matches"
|
export * as matches from "ts-matches"
|
||||||
export * as YAML from "yaml"
|
export * as YAML from "yaml"
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ type Daemon<
|
|||||||
|
|
||||||
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`
|
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`
|
||||||
|
|
||||||
const runDaemon =
|
export const runDaemon =
|
||||||
<Manifest extends SDKManifest>() =>
|
<Manifest extends SDKManifest>() =>
|
||||||
async <A extends string>(
|
async <A extends string>(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { BindOptions, Scheme } from "./interfaces/Host"
|
|||||||
import { Daemons } from "./mainFn/Daemons"
|
import { Daemons } from "./mainFn/Daemons"
|
||||||
import { UrlString } from "./util/getServiceInterface"
|
import { UrlString } from "./util/getServiceInterface"
|
||||||
|
|
||||||
|
export { SDKManifest } from "./manifest/ManifestTypes"
|
||||||
|
|
||||||
export type ExportedAction = (options: {
|
export type ExportedAction = (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
input?: Record<string, unknown>
|
input?: Record<string, unknown>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import "./Overlay"
|
|||||||
import "./once"
|
import "./once"
|
||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
|
|
||||||
|
export { GetServiceInterface, getServiceInterface } from "./getServiceInterface"
|
||||||
|
export { getServiceInterfaces } from "./getServiceInterfaces"
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export type FlattenIntersection<T> =
|
export type FlattenIntersection<T> =
|
||||||
T extends ArrayLike<any> ? T :
|
T extends ArrayLike<any> ? T :
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { arrayOf, string } from "ts-matches"
|
import { arrayOf, string } from "ts-matches"
|
||||||
import { ValidIfNoStupidEscape } from "../types"
|
import { ValidIfNoStupidEscape } from "../types"
|
||||||
|
|
||||||
export const splitCommand = <A>(
|
export const splitCommand = (
|
||||||
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
command: string | [string, ...string[]],
|
||||||
): string[] => {
|
): string[] => {
|
||||||
if (arrayOf(string).test(command)) return command
|
if (arrayOf(string).test(command)) return command
|
||||||
return String(command)
|
return String(command)
|
||||||
|
|||||||
Reference in New Issue
Block a user