Chore/refactoring effects (#2644)

* fix mac build

* wip

* chore: Update the effects to get rid of bad pattern

* chore: Some small changes

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Jade
2024-06-14 14:16:12 -06:00
committed by GitHub
parent 3f380fa0da
commit bb514d6216
10 changed files with 704 additions and 693 deletions

View File

@@ -31,26 +31,21 @@ type RpcError = typeof matchRpcError._TYPE
const SOCKET_PATH = "/media/startos/rpc/host.sock" const SOCKET_PATH = "/media/startos/rpc/host.sock"
const MAIN = "/main" as const const MAIN = "/main" as const
export class HostSystemStartOs implements Effects { let hostSystemId = 0
procedureId: string | null = null export const hostSystemStartOs =
(callbackHolder: CallbackHolder) =>
static of(callbackHolder: CallbackHolder) { (procedureId: null | string): Effects => {
return new HostSystemStartOs(callbackHolder) const rpcRound = <K extends keyof Effects | "getStore" | "setStore">(
}
constructor(readonly callbackHolder: CallbackHolder) {}
id = 0
rpcRound<K extends keyof Effects | "getStore" | "setStore">(
method: K, method: K,
params: Record<string, unknown>, params: Record<string, unknown>,
) { ) => {
const id = this.id++ const id = hostSystemId++
const client = net.createConnection({ path: SOCKET_PATH }, () => { const client = net.createConnection({ path: SOCKET_PATH }, () => {
client.write( client.write(
JSON.stringify({ JSON.stringify({
id, id,
method, method,
params: { ...params, procedureId: this.procedureId }, params: { ...params, procedureId: procedureId },
}) + "\n", }) + "\n",
) )
}) })
@@ -96,209 +91,213 @@ export class HostSystemStartOs implements Effects {
}) })
}) })
} }
const self: Effects = {
bind(...[options]: Parameters<T.Effects["bind"]>) { bind(...[options]: Parameters<T.Effects["bind"]>) {
return this.rpcRound("bind", { return rpcRound("bind", {
...options, ...options,
stack: new Error().stack, stack: new Error().stack,
}) as ReturnType<T.Effects["bind"]> }) as ReturnType<T.Effects["bind"]>
} },
clearBindings(...[]: Parameters<T.Effects["clearBindings"]>) { clearBindings(...[]: Parameters<T.Effects["clearBindings"]>) {
return this.rpcRound("clearBindings", {}) as ReturnType< return rpcRound("clearBindings", {}) as ReturnType<
T.Effects["clearBindings"] T.Effects["clearBindings"]
> >
} },
clearServiceInterfaces( clearServiceInterfaces(
...[]: Parameters<T.Effects["clearServiceInterfaces"]> ...[]: Parameters<T.Effects["clearServiceInterfaces"]>
) { ) {
return this.rpcRound("clearServiceInterfaces", {}) as ReturnType< return rpcRound("clearServiceInterfaces", {}) as ReturnType<
T.Effects["clearServiceInterfaces"] T.Effects["clearServiceInterfaces"]
> >
} },
createOverlayedImage(options: { createOverlayedImage(options: {
imageId: string imageId: string
}): Promise<[string, string]> { }): Promise<[string, string]> {
return this.rpcRound("createOverlayedImage", options) as ReturnType< return rpcRound("createOverlayedImage", options) as ReturnType<
T.Effects["createOverlayedImage"] T.Effects["createOverlayedImage"]
> >
} },
destroyOverlayedImage(options: { guid: string }): Promise<void> { destroyOverlayedImage(options: { guid: string }): Promise<void> {
return this.rpcRound("destroyOverlayedImage", options) as ReturnType< return rpcRound("destroyOverlayedImage", options) as ReturnType<
T.Effects["destroyOverlayedImage"] T.Effects["destroyOverlayedImage"]
> >
} },
executeAction(...[options]: Parameters<T.Effects["executeAction"]>) { executeAction(...[options]: Parameters<T.Effects["executeAction"]>) {
return this.rpcRound("executeAction", options) as ReturnType< return rpcRound("executeAction", options) as ReturnType<
T.Effects["executeAction"] T.Effects["executeAction"]
> >
} },
exists(...[packageId]: Parameters<T.Effects["exists"]>) { exists(...[packageId]: Parameters<T.Effects["exists"]>) {
return this.rpcRound("exists", packageId) as ReturnType<T.Effects["exists"]> return rpcRound("exists", packageId) as ReturnType<T.Effects["exists"]>
} },
exportAction(...[options]: Parameters<T.Effects["exportAction"]>) { exportAction(...[options]: Parameters<T.Effects["exportAction"]>) {
return this.rpcRound("exportAction", options) as ReturnType< return rpcRound("exportAction", options) as ReturnType<
T.Effects["exportAction"] T.Effects["exportAction"]
> >
} },
exportServiceInterface: Effects["exportServiceInterface"] = ( exportServiceInterface: ((
...[options]: Parameters<Effects["exportServiceInterface"]> ...[options]: Parameters<Effects["exportServiceInterface"]>
) => { ) => {
return this.rpcRound("exportServiceInterface", options) as ReturnType< return rpcRound("exportServiceInterface", options) as ReturnType<
T.Effects["exportServiceInterface"] T.Effects["exportServiceInterface"]
> >
} }) as Effects["exportServiceInterface"],
exposeForDependents( exposeForDependents(
...[options]: Parameters<T.Effects["exposeForDependents"]> ...[options]: Parameters<T.Effects["exposeForDependents"]>
) { ) {
return this.rpcRound("exposeForDependents", options) as ReturnType< return rpcRound("exposeForDependents", options) as ReturnType<
T.Effects["exposeForDependents"] T.Effects["exposeForDependents"]
> >
} },
getConfigured(...[]: Parameters<T.Effects["getConfigured"]>) { getConfigured(...[]: Parameters<T.Effects["getConfigured"]>) {
return this.rpcRound("getConfigured", {}) as ReturnType< return rpcRound("getConfigured", {}) as ReturnType<
T.Effects["getConfigured"] T.Effects["getConfigured"]
> >
} },
getContainerIp(...[]: Parameters<T.Effects["getContainerIp"]>) { getContainerIp(...[]: Parameters<T.Effects["getContainerIp"]>) {
return this.rpcRound("getContainerIp", {}) as ReturnType< return rpcRound("getContainerIp", {}) as ReturnType<
T.Effects["getContainerIp"] T.Effects["getContainerIp"]
> >
} },
getHostInfo: Effects["getHostInfo"] = (...[allOptions]: any[]) => { getHostInfo: ((...[allOptions]: any[]) => {
const options = { const options = {
...allOptions, ...allOptions,
callback: this.callbackHolder.addCallback(allOptions.callback), callback: callbackHolder.addCallback(allOptions.callback),
} }
return this.rpcRound("getHostInfo", options) as ReturnType< return rpcRound("getHostInfo", options) as ReturnType<
T.Effects["getHostInfo"] T.Effects["getHostInfo"]
> as any > as any
} }) as Effects["getHostInfo"],
getServiceInterface( getServiceInterface(
...[options]: Parameters<T.Effects["getServiceInterface"]> ...[options]: Parameters<T.Effects["getServiceInterface"]>
) { ) {
return this.rpcRound("getServiceInterface", { return rpcRound("getServiceInterface", {
...options, ...options,
callback: this.callbackHolder.addCallback(options.callback), callback: callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["getServiceInterface"]> }) as ReturnType<T.Effects["getServiceInterface"]>
} },
getPrimaryUrl(...[options]: Parameters<T.Effects["getPrimaryUrl"]>) { getPrimaryUrl(...[options]: Parameters<T.Effects["getPrimaryUrl"]>) {
return this.rpcRound("getPrimaryUrl", { return rpcRound("getPrimaryUrl", {
...options, ...options,
callback: this.callbackHolder.addCallback(options.callback), callback: callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["getPrimaryUrl"]> }) as ReturnType<T.Effects["getPrimaryUrl"]>
} },
getServicePortForward( getServicePortForward(
...[options]: Parameters<T.Effects["getServicePortForward"]> ...[options]: Parameters<T.Effects["getServicePortForward"]>
) { ) {
return this.rpcRound("getServicePortForward", options) as ReturnType< return rpcRound("getServicePortForward", options) as ReturnType<
T.Effects["getServicePortForward"] T.Effects["getServicePortForward"]
> >
} },
getSslCertificate(options: Parameters<T.Effects["getSslCertificate"]>[0]) { getSslCertificate(
return this.rpcRound("getSslCertificate", options) as ReturnType< options: Parameters<T.Effects["getSslCertificate"]>[0],
) {
return rpcRound("getSslCertificate", options) as ReturnType<
T.Effects["getSslCertificate"] T.Effects["getSslCertificate"]
> >
} },
getSslKey(options: Parameters<T.Effects["getSslKey"]>[0]) { getSslKey(options: Parameters<T.Effects["getSslKey"]>[0]) {
return this.rpcRound("getSslKey", options) as ReturnType< return rpcRound("getSslKey", options) as ReturnType<
T.Effects["getSslKey"] T.Effects["getSslKey"]
> >
} },
getSystemSmtp(...[options]: Parameters<T.Effects["getSystemSmtp"]>) { getSystemSmtp(...[options]: Parameters<T.Effects["getSystemSmtp"]>) {
return this.rpcRound("getSystemSmtp", { return rpcRound("getSystemSmtp", {
...options, ...options,
callback: this.callbackHolder.addCallback(options.callback), callback: callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["getSystemSmtp"]> }) as ReturnType<T.Effects["getSystemSmtp"]>
} },
listServiceInterfaces( listServiceInterfaces(
...[options]: Parameters<T.Effects["listServiceInterfaces"]> ...[options]: Parameters<T.Effects["listServiceInterfaces"]>
) { ) {
return this.rpcRound("listServiceInterfaces", { return rpcRound("listServiceInterfaces", {
...options, ...options,
callback: this.callbackHolder.addCallback(options.callback), callback: callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["listServiceInterfaces"]> }) as ReturnType<T.Effects["listServiceInterfaces"]>
} },
mount(...[options]: Parameters<T.Effects["mount"]>) { mount(...[options]: Parameters<T.Effects["mount"]>) {
return this.rpcRound("mount", options) as ReturnType<T.Effects["mount"]> return rpcRound("mount", options) as ReturnType<T.Effects["mount"]>
} },
removeAction(...[options]: Parameters<T.Effects["removeAction"]>) { removeAction(...[options]: Parameters<T.Effects["removeAction"]>) {
return this.rpcRound("removeAction", options) as ReturnType< return rpcRound("removeAction", options) as ReturnType<
T.Effects["removeAction"] T.Effects["removeAction"]
> >
} },
removeAddress(...[options]: Parameters<T.Effects["removeAddress"]>) { removeAddress(...[options]: Parameters<T.Effects["removeAddress"]>) {
return this.rpcRound("removeAddress", options) as ReturnType< return rpcRound("removeAddress", options) as ReturnType<
T.Effects["removeAddress"] T.Effects["removeAddress"]
> >
} },
restart(...[]: Parameters<T.Effects["restart"]>) { restart(...[]: Parameters<T.Effects["restart"]>) {
return this.rpcRound("restart", {}) as ReturnType<T.Effects["restart"]> return rpcRound("restart", {}) as ReturnType<T.Effects["restart"]>
} },
running(...[packageId]: Parameters<T.Effects["running"]>) { running(...[packageId]: Parameters<T.Effects["running"]>) {
return this.rpcRound("running", { packageId }) as ReturnType< return rpcRound("running", { packageId }) as ReturnType<
T.Effects["running"] T.Effects["running"]
> >
} },
// runRsync(...[options]: Parameters<T.Effects[""]>) { // runRsync(...[options]: Parameters<T.Effects[""]>) {
// //
// return this.rpcRound('executeAction', options) as ReturnType<T.Effects["executeAction"]> // return rpcRound('executeAction', options) as ReturnType<T.Effects["executeAction"]>
// //
// return this.rpcRound('executeAction', options) as ReturnType<T.Effects["executeAction"]> // return rpcRound('executeAction', options) as ReturnType<T.Effects["executeAction"]>
// } // }
setConfigured(...[configured]: Parameters<T.Effects["setConfigured"]>) { setConfigured(...[configured]: Parameters<T.Effects["setConfigured"]>) {
return this.rpcRound("setConfigured", { configured }) as ReturnType< return rpcRound("setConfigured", { configured }) as ReturnType<
T.Effects["setConfigured"] T.Effects["setConfigured"]
> >
} },
setDependencies( setDependencies(
dependencies: Parameters<T.Effects["setDependencies"]>[0], dependencies: Parameters<T.Effects["setDependencies"]>[0],
): ReturnType<T.Effects["setDependencies"]> { ): ReturnType<T.Effects["setDependencies"]> {
return this.rpcRound("setDependencies", dependencies) as ReturnType< return rpcRound("setDependencies", dependencies) as ReturnType<
T.Effects["setDependencies"] T.Effects["setDependencies"]
> >
} },
checkDependencies( checkDependencies(
options: Parameters<T.Effects["checkDependencies"]>[0], options: Parameters<T.Effects["checkDependencies"]>[0],
): ReturnType<T.Effects["checkDependencies"]> { ): ReturnType<T.Effects["checkDependencies"]> {
return this.rpcRound("checkDependencies", options) as ReturnType< return rpcRound("checkDependencies", options) as ReturnType<
T.Effects["checkDependencies"] T.Effects["checkDependencies"]
> >
} },
getDependencies(): ReturnType<T.Effects["getDependencies"]> { getDependencies(): ReturnType<T.Effects["getDependencies"]> {
return this.rpcRound("getDependencies", {}) as ReturnType< return rpcRound("getDependencies", {}) as ReturnType<
T.Effects["getDependencies"] T.Effects["getDependencies"]
> >
} },
setHealth(...[options]: Parameters<T.Effects["setHealth"]>) { setHealth(...[options]: Parameters<T.Effects["setHealth"]>) {
return this.rpcRound("setHealth", options) as ReturnType< return rpcRound("setHealth", options) as ReturnType<
T.Effects["setHealth"] T.Effects["setHealth"]
> >
} },
setMainStatus(o: { status: "running" | "stopped" }): Promise<void> { setMainStatus(o: { status: "running" | "stopped" }): Promise<void> {
return this.rpcRound("setMainStatus", o) as ReturnType< return rpcRound("setMainStatus", o) as ReturnType<
T.Effects["setHealth"] T.Effects["setHealth"]
> >
} },
shutdown(...[]: Parameters<T.Effects["shutdown"]>) { shutdown(...[]: Parameters<T.Effects["shutdown"]>) {
return this.rpcRound("shutdown", {}) as ReturnType<T.Effects["shutdown"]> return rpcRound("shutdown", {}) as ReturnType<T.Effects["shutdown"]>
} },
stopped(...[packageId]: Parameters<T.Effects["stopped"]>) { stopped(...[packageId]: Parameters<T.Effects["stopped"]>) {
return this.rpcRound("stopped", { packageId }) as ReturnType< return rpcRound("stopped", { packageId }) as ReturnType<
T.Effects["stopped"] T.Effects["stopped"]
> >
} },
store: T.Effects["store"] = { store: {
get: async (options: any) => get: async (options: any) =>
this.rpcRound("getStore", { rpcRound("getStore", {
...options, ...options,
callback: this.callbackHolder.addCallback(options.callback), callback: callbackHolder.addCallback(options.callback),
}) as any, }) as any,
set: async (options: any) => set: async (options: any) =>
this.rpcRound("setStore", options) as ReturnType< rpcRound("setStore", options) as ReturnType<
T.Effects["store"]["set"] T.Effects["store"]["set"]
>, >,
} as T.Effects["store"],
} }
return self
} }

View File

@@ -247,7 +247,7 @@ export class RpcListener {
})), })),
) )
.when(exitType, async ({ id }) => { .when(exitType, async ({ id }) => {
if (this._system) await this._system.exit(this.effects) if (this._system) await this._system.exit(this.effects(null))
delete this._system delete this._system
delete this._effects delete this._effects

View File

@@ -1,9 +1,10 @@
import { PolyfillEffects } from "./polyfillEffects" 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 { Daemons, T, daemons } from "@start9labs/start-sdk" import { Daemons, T, daemons } from "@start9labs/start-sdk"
import { Daemon } from "@start9labs/start-sdk/cjs/lib/mainFn/Daemon" import { Daemon } from "@start9labs/start-sdk/cjs/lib/mainFn/Daemon"
import { Effects } from "../../../Models/Effects"
const EMBASSY_HEALTH_INTERVAL = 15 * 1000 const EMBASSY_HEALTH_INTERVAL = 15 * 1000
const EMBASSY_PROPERTIES_LOOP = 30 * 1000 const EMBASSY_PROPERTIES_LOOP = 30 * 1000
@@ -27,7 +28,7 @@ export class MainLoop {
| undefined | undefined
constructor( constructor(
readonly system: SystemForEmbassy, readonly system: SystemForEmbassy,
readonly effects: HostSystemStartOs, readonly effects: Effects,
) { ) {
this.healthLoops = this.constructHealthLoops() this.healthLoops = this.constructHealthLoops()
this.mainEvent = this.constructMainEvent() this.mainEvent = this.constructMainEvent()
@@ -65,7 +66,7 @@ export class MainLoop {
} }
} }
private async setupInterfaces(effects: HostSystemStartOs) { private async setupInterfaces(effects: T.Effects) {
for (const interfaceId in this.system.manifest.interfaces) { for (const interfaceId in this.system.manifest.interfaces) {
const iface = this.system.manifest.interfaces[interfaceId] const iface = this.system.manifest.interfaces[interfaceId]
const internalPorts = new Set<number>() const internalPorts = new Set<number>()
@@ -211,7 +212,7 @@ export class MainLoop {
} }
const result = await method( const result = await method(
new PolyfillEffects(effects, this.system.manifest), polyfillEffects(effects, this.system.manifest),
timeChanged, timeChanged,
) )

View File

@@ -1,7 +1,7 @@
import { types as T, utils, EmVer } from "@start9labs/start-sdk" import { types as T, utils, 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"
import { Duration, duration, fromDuration } from "../../../Models/Duration" import { Duration, duration, fromDuration } from "../../../Models/Duration"
import { System } from "../../../Interfaces/System" import { System } from "../../../Interfaces/System"
import { matchManifest, Manifest, Procedure } from "./matchManifest" import { matchManifest, Manifest, Procedure } from "./matchManifest"
@@ -27,7 +27,7 @@ import {
Parser, Parser,
array, array,
} 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"
import { RpcResult, matchRpcResult } from "../../RpcListener" import { RpcResult, matchRpcResult } from "../../RpcListener"
import { CT } from "@start9labs/start-sdk" import { CT } from "@start9labs/start-sdk"
@@ -41,6 +41,7 @@ import {
MultiHost, MultiHost,
} from "@start9labs/start-sdk/cjs/lib/interfaces/Host" } from "@start9labs/start-sdk/cjs/lib/interfaces/Host"
import { ServiceInterfaceBuilder } from "@start9labs/start-sdk/cjs/lib/interfaces/ServiceInterfaceBuilder" import { ServiceInterfaceBuilder } from "@start9labs/start-sdk/cjs/lib/interfaces/ServiceInterfaceBuilder"
import { Effects } from "../../../Models/Effects"
type Optional<A> = A | undefined | null type Optional<A> = A | undefined | null
function todo(): never { function todo(): never {
@@ -197,7 +198,7 @@ export class SystemForEmbassy implements System {
readonly moduleCode: Partial<U.ExpectedExports>, readonly moduleCode: Partial<U.ExpectedExports>,
) {} ) {}
async execute( async execute(
effects: HostSystemStartOs, effectCreator: ReturnType<typeof hostSystemStartOs>,
options: { options: {
id: string id: string
procedure: JsonPath procedure: JsonPath
@@ -205,8 +206,7 @@ export class SystemForEmbassy implements System {
timeout?: number | undefined timeout?: number | undefined
}, },
): Promise<RpcResult> { ): Promise<RpcResult> {
effects = Object.create(effects) const effects = effectCreator(options.id)
effects.procedureId = options.id
return this._execute(effects, options) return this._execute(effects, options)
.then((x) => .then((x) =>
matches(x) matches(x)
@@ -261,12 +261,12 @@ export class SystemForEmbassy implements System {
} }
}) })
} }
async exit(effects: HostSystemStartOs): Promise<void> { async exit(): Promise<void> {
if (this.currentRunning) await this.currentRunning.clean() if (this.currentRunning) await this.currentRunning.clean()
delete this.currentRunning delete this.currentRunning
} }
async _execute( async _execute(
effects: HostSystemStartOs, effects: Effects,
options: { options: {
procedure: JsonPath procedure: JsonPath
input: unknown input: unknown
@@ -340,7 +340,7 @@ export class SystemForEmbassy implements System {
throw new Error(`Could not find the path for ${options.procedure}`) throw new Error(`Could not find the path for ${options.procedure}`)
} }
private async init( private async init(
effects: HostSystemStartOs, effects: Effects,
previousVersion: Optional<string>, previousVersion: Optional<string>,
timeoutMs: number | null, timeoutMs: number | null,
): Promise<void> { ): Promise<void> {
@@ -350,7 +350,7 @@ export class SystemForEmbassy implements System {
await this.exportActions(effects) await this.exportActions(effects)
await this.exportNetwork(effects) await this.exportNetwork(effects)
} }
async exportNetwork(effects: HostSystemStartOs) { async exportNetwork(effects: Effects) {
for (const [id, interfaceValue] of Object.entries( for (const [id, interfaceValue] of Object.entries(
this.manifest.interfaces, this.manifest.interfaces,
)) { )) {
@@ -428,7 +428,7 @@ export class SystemForEmbassy implements System {
) )
} }
} }
async exportActions(effects: HostSystemStartOs) { async exportActions(effects: Effects) {
const manifest = this.manifest const manifest = this.manifest
if (!manifest.actions) return if (!manifest.actions) return
for (const [actionId, action] of Object.entries(manifest.actions)) { for (const [actionId, action] of Object.entries(manifest.actions)) {
@@ -457,7 +457,7 @@ export class SystemForEmbassy implements System {
} }
} }
private async uninit( private async uninit(
effects: HostSystemStartOs, effects: Effects,
nextVersion: Optional<string>, nextVersion: Optional<string>,
timeoutMs: number | null, timeoutMs: number | null,
): Promise<void> { ): Promise<void> {
@@ -465,7 +465,7 @@ export class SystemForEmbassy implements System {
await effects.setMainStatus({ status: "stopped" }) await effects.setMainStatus({ status: "stopped" })
} }
private async mainStart( private async mainStart(
effects: HostSystemStartOs, effects: Effects,
timeoutMs: number | null, timeoutMs: number | null,
): Promise<void> { ): Promise<void> {
if (!!this.currentRunning) return if (!!this.currentRunning) return
@@ -473,7 +473,7 @@ export class SystemForEmbassy implements System {
this.currentRunning = new MainLoop(this, effects) this.currentRunning = new MainLoop(this, effects)
} }
private async mainStop( private async mainStop(
effects: HostSystemStartOs, effects: Effects,
timeoutMs: number | null, timeoutMs: number | null,
): Promise<Duration> { ): Promise<Duration> {
const { currentRunning } = this const { currentRunning } = this
@@ -491,7 +491,7 @@ export class SystemForEmbassy implements System {
return durationValue return durationValue
} }
private async createBackup( private async createBackup(
effects: HostSystemStartOs, effects: Effects,
timeoutMs: number | null, timeoutMs: number | null,
): Promise<void> { ): Promise<void> {
const backup = this.manifest.backup.create const backup = this.manifest.backup.create
@@ -503,13 +503,11 @@ export class SystemForEmbassy implements System {
await container.execFail([backup.entrypoint, ...backup.args], timeoutMs) await container.execFail([backup.entrypoint, ...backup.args], timeoutMs)
} else { } else {
const moduleCode = await this.moduleCode const moduleCode = await this.moduleCode
await moduleCode.createBackup?.( await moduleCode.createBackup?.(polyfillEffects(effects, this.manifest))
new PolyfillEffects(effects, this.manifest),
)
} }
} }
private async restoreBackup( private async restoreBackup(
effects: HostSystemStartOs, effects: Effects,
timeoutMs: number | null, timeoutMs: number | null,
): Promise<void> { ): Promise<void> {
const restoreBackup = this.manifest.backup.restore const restoreBackup = this.manifest.backup.restore
@@ -528,19 +526,17 @@ export class SystemForEmbassy implements System {
) )
} else { } else {
const moduleCode = await this.moduleCode const moduleCode = await this.moduleCode
await moduleCode.restoreBackup?.( await moduleCode.restoreBackup?.(polyfillEffects(effects, this.manifest))
new PolyfillEffects(effects, this.manifest),
)
} }
} }
private async getConfig( private async getConfig(
effects: HostSystemStartOs, effects: Effects,
timeoutMs: number | null, timeoutMs: number | null,
): Promise<T.ConfigRes> { ): Promise<T.ConfigRes> {
return this.getConfigUncleaned(effects, timeoutMs).then(removePointers) return this.getConfigUncleaned(effects, timeoutMs).then(removePointers)
} }
private async getConfigUncleaned( private async getConfigUncleaned(
effects: HostSystemStartOs, effects: Effects,
timeoutMs: number | null, timeoutMs: number | null,
): Promise<T.ConfigRes> { ): Promise<T.ConfigRes> {
const config = this.manifest.config?.get const config = this.manifest.config?.get
@@ -564,7 +560,7 @@ export class SystemForEmbassy implements System {
const moduleCode = await this.moduleCode const moduleCode = await this.moduleCode
const method = moduleCode.getConfig const method = moduleCode.getConfig
if (!method) throw new Error("Expecting that the method getConfig exists") if (!method) throw new Error("Expecting that the method getConfig exists")
return (await method(new PolyfillEffects(effects, this.manifest)).then( return (await method(polyfillEffects(effects, this.manifest)).then(
(x) => { (x) => {
if ("result" in x) return x.result if ("result" in x) return x.result
if ("error" in x) throw new Error("Error getting config: " + x.error) if ("error" in x) throw new Error("Error getting config: " + x.error)
@@ -574,7 +570,7 @@ export class SystemForEmbassy implements System {
} }
} }
private async setConfig( private async setConfig(
effects: HostSystemStartOs, effects: Effects,
newConfigWithoutPointers: unknown, newConfigWithoutPointers: unknown,
timeoutMs: number | null, timeoutMs: number | null,
): Promise<void> { ): Promise<void> {
@@ -617,7 +613,7 @@ export class SystemForEmbassy implements System {
const answer = matchSetResult.unsafeCast( const answer = matchSetResult.unsafeCast(
await method( await method(
new PolyfillEffects(effects, this.manifest), polyfillEffects(effects, this.manifest),
newConfig as U.Config, newConfig as U.Config,
).then((x): T.SetResult => { ).then((x): T.SetResult => {
if ("result" in x) if ("result" in x)
@@ -636,7 +632,7 @@ export class SystemForEmbassy implements System {
} }
} }
private async setConfigSetConfig( private async setConfigSetConfig(
effects: HostSystemStartOs, effects: Effects,
dependsOn: { [x: string]: readonly string[] }, dependsOn: { [x: string]: readonly string[] },
) { ) {
await effects.setDependencies({ await effects.setDependencies({
@@ -660,7 +656,7 @@ export class SystemForEmbassy implements System {
} }
private async migration( private async migration(
effects: HostSystemStartOs, effects: Effects,
fromVersion: string, fromVersion: string,
timeoutMs: number | null, timeoutMs: number | null,
): Promise<T.MigrationRes> { ): Promise<T.MigrationRes> {
@@ -713,7 +709,7 @@ export class SystemForEmbassy implements System {
if (!method) if (!method)
throw new Error("Expecting that the method migration exists") throw new Error("Expecting that the method migration exists")
return (await method( return (await method(
new PolyfillEffects(effects, this.manifest), polyfillEffects(effects, this.manifest),
fromVersion as string, fromVersion as string,
).then((x) => { ).then((x) => {
if ("result" in x) return x.result if ("result" in x) return x.result
@@ -725,7 +721,7 @@ export class SystemForEmbassy implements System {
return { configured: true } return { configured: true }
} }
private async properties( private async properties(
effects: HostSystemStartOs, effects: Effects,
timeoutMs: number | null, timeoutMs: number | null,
): Promise<ReturnType<T.ExpectedExports.properties>> { ): Promise<ReturnType<T.ExpectedExports.properties>> {
// TODO BLU-J set the properties ever so often // TODO BLU-J set the properties ever so often
@@ -754,7 +750,7 @@ export class SystemForEmbassy implements System {
if (!method) if (!method)
throw new Error("Expecting that the method properties exists") throw new Error("Expecting that the method properties exists")
const properties = matchProperties.unsafeCast( const properties = matchProperties.unsafeCast(
await method(new PolyfillEffects(effects, this.manifest)).then((x) => { await method(polyfillEffects(effects, this.manifest)).then((x) => {
if ("result" in x) return x.result if ("result" in x) return x.result
if ("error" in x) throw new Error("Error getting config: " + x.error) if ("error" in x) throw new Error("Error getting config: " + x.error)
throw new Error("Error getting config: " + x["error-code"][1]) throw new Error("Error getting config: " + x["error-code"][1])
@@ -765,7 +761,7 @@ export class SystemForEmbassy implements System {
throw new Error(`Unknown type in the fetch properties: ${setConfigValue}`) throw new Error(`Unknown type in the fetch properties: ${setConfigValue}`)
} }
private async action( private async action(
effects: HostSystemStartOs, effects: Effects,
actionId: string, actionId: string,
formData: unknown, formData: unknown,
timeoutMs: number | null, timeoutMs: number | null,
@@ -795,7 +791,7 @@ export class SystemForEmbassy implements System {
const method = moduleCode.action?.[actionId] const method = moduleCode.action?.[actionId]
if (!method) throw new Error("Expecting that the method action exists") if (!method) throw new Error("Expecting that the method action exists")
return (await method( return (await method(
new PolyfillEffects(effects, this.manifest), polyfillEffects(effects, this.manifest),
formData as any, formData as any,
).then((x) => { ).then((x) => {
if ("result" in x) return x.result if ("result" in x) return x.result
@@ -805,7 +801,7 @@ export class SystemForEmbassy implements System {
} }
} }
private async dependenciesCheck( private async dependenciesCheck(
effects: HostSystemStartOs, effects: Effects,
id: string, id: string,
oldConfig: unknown, oldConfig: unknown,
timeoutMs: number | null, timeoutMs: number | null,
@@ -838,7 +834,7 @@ export class SystemForEmbassy implements System {
`Expecting that the method dependency check ${id} exists`, `Expecting that the method dependency check ${id} exists`,
) )
return (await method( return (await method(
new PolyfillEffects(effects, this.manifest), polyfillEffects(effects, this.manifest),
oldConfig as any, oldConfig as any,
).then((x) => { ).then((x) => {
if ("result" in x) return x.result if ("result" in x) return x.result
@@ -850,7 +846,7 @@ export class SystemForEmbassy implements System {
} }
} }
private async dependenciesAutoconfig( private async dependenciesAutoconfig(
effects: HostSystemStartOs, effects: Effects,
id: string, id: string,
oldConfig: unknown, oldConfig: unknown,
timeoutMs: number | null, timeoutMs: number | null,
@@ -863,7 +859,7 @@ export class SystemForEmbassy implements System {
`Expecting that the method dependency autoConfigure ${id} exists`, `Expecting that the method dependency autoConfigure ${id} exists`,
) )
return (await method( return (await method(
new PolyfillEffects(effects, this.manifest), polyfillEffects(effects, this.manifest),
oldConfig as any, oldConfig as any,
).then((x) => { ).then((x) => {
if ("result" in x) return x.result if ("result" in x) return x.result
@@ -961,7 +957,7 @@ function cleanConfigFromPointers<C, S>(
} }
async function updateConfig( async function updateConfig(
effects: HostSystemStartOs, effects: Effects,
manifest: Manifest, manifest: Manifest,
spec: unknown, spec: unknown,
mutConfigValue: unknown, mutConfigValue: unknown,

View File

@@ -4,17 +4,19 @@ 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 { daemons, startSdk, T } from "@start9labs/start-sdk" import { daemons, startSdk, T } from "@start9labs/start-sdk"
import { HostSystemStartOs } from "../../HostSystemStartOs"
import "isomorphic-fetch" import "isomorphic-fetch"
import { Manifest } from "./matchManifest" import { Manifest } from "./matchManifest"
import { DockerProcedureContainer } from "./DockerProcedureContainer" import { DockerProcedureContainer } from "./DockerProcedureContainer"
import * as cp from "child_process" import * as cp from "child_process"
import { Effects } from "../../../Models/Effects"
export const execFile = promisify(cp.execFile) export const execFile = promisify(cp.execFile)
export class PolyfillEffects implements oet.Effects { export const polyfillEffects = (
constructor( effects: Effects,
readonly effects: HostSystemStartOs, manifest: Manifest,
private manifest: Manifest, ): oet.Effects => {
) {} const self = {
effects,
manifest,
async writeFile(input: { async writeFile(input: {
path: string path: string
volumeId: string volumeId: string
@@ -24,12 +26,12 @@ export class PolyfillEffects implements oet.Effects {
new Volume(input.volumeId, input.path).path, new Volume(input.volumeId, input.path).path,
input.toWrite, input.toWrite,
) )
} },
async readFile(input: { volumeId: string; path: string }): Promise<string> { async readFile(input: { volumeId: string; path: string }): Promise<string> {
return ( return (
await fs.readFile(new Volume(input.volumeId, input.path).path) await fs.readFile(new Volume(input.volumeId, input.path).path)
).toString() ).toString()
} },
async metadata(input: { async metadata(input: {
volumeId: string volumeId: string
path: string path: string
@@ -46,25 +48,34 @@ export class PolyfillEffects implements oet.Effects {
len: stats.size, len: stats.size,
readonly: (stats.mode & 0o200) > 0, readonly: (stats.mode & 0o200) > 0,
} }
} },
async createDir(input: { volumeId: string; path: string }): Promise<string> { async createDir(input: {
volumeId: string
path: string
}): Promise<string> {
const path = new Volume(input.volumeId, input.path).path const path = new Volume(input.volumeId, input.path).path
await fs.mkdir(path, { recursive: true }) await fs.mkdir(path, { recursive: true })
return path return path
} },
async readDir(input: { volumeId: string; path: string }): Promise<string[]> { async readDir(input: {
volumeId: string
path: string
}): Promise<string[]> {
return fs.readdir(new Volume(input.volumeId, input.path).path) return fs.readdir(new Volume(input.volumeId, input.path).path)
} },
async removeDir(input: { volumeId: string; path: string }): Promise<string> { async removeDir(input: {
volumeId: string
path: string
}): Promise<string> {
const path = new Volume(input.volumeId, input.path).path const path = new Volume(input.volumeId, input.path).path
await fs.rmdir(new Volume(input.volumeId, input.path).path, { await fs.rmdir(new Volume(input.volumeId, input.path).path, {
recursive: true, recursive: true,
}) })
return path return path
} },
removeFile(input: { volumeId: string; path: string }): Promise<void> { removeFile(input: { volumeId: string; path: string }): Promise<void> {
return fs.rm(new Volume(input.volumeId, input.path).path) return fs.rm(new Volume(input.volumeId, input.path).path)
} },
async writeJsonFile(input: { async writeJsonFile(input: {
volumeId: string volumeId: string
path: string path: string
@@ -74,7 +85,7 @@ export class PolyfillEffects implements oet.Effects {
new Volume(input.volumeId, input.path).path, new Volume(input.volumeId, input.path).path,
JSON.stringify(input.toWrite), JSON.stringify(input.toWrite),
) )
} },
async readJsonFile(input: { async readJsonFile(input: {
volumeId: string volumeId: string
path: string path: string
@@ -84,7 +95,7 @@ export class PolyfillEffects implements oet.Effects {
await fs.readFile(new Volume(input.volumeId, input.path).path) await fs.readFile(new Volume(input.volumeId, input.path).path)
).toString(), ).toString(),
) )
} },
runCommand({ runCommand({
command, command,
args, args,
@@ -96,8 +107,8 @@ export class PolyfillEffects implements oet.Effects {
}): Promise<oet.ResultType<string>> { }): Promise<oet.ResultType<string>> {
return startSdk return startSdk
.runCommand( .runCommand(
this.effects, effects,
{ id: this.manifest.main.image }, { id: manifest.main.image },
[command, ...(args || [])], [command, ...(args || [])],
{}, {},
) )
@@ -108,20 +119,20 @@ export class PolyfillEffects implements oet.Effects {
.then((x: any) => .then((x: any) =>
!!x.stderr ? { error: x.stderr } : { result: x.stdout }, !!x.stderr ? { error: x.stderr } : { result: x.stdout },
) )
} },
runDaemon(input: { command: string; args?: string[] | undefined }): { runDaemon(input: { command: string; args?: string[] | undefined }): {
wait(): Promise<oet.ResultType<string>> wait(): Promise<oet.ResultType<string>>
term(): Promise<void> term(): Promise<void>
} { } {
const dockerProcedureContainer = DockerProcedureContainer.of( const dockerProcedureContainer = DockerProcedureContainer.of(
this.effects, effects,
this.manifest.main, manifest.main,
this.manifest.volumes, manifest.volumes,
) )
const daemon = dockerProcedureContainer.then((dockerProcedureContainer) => const daemon = dockerProcedureContainer.then((dockerProcedureContainer) =>
daemons.runCommand()( daemons.runCommand()(
this.effects, effects,
{ id: this.manifest.main.image }, { id: manifest.main.image },
[input.command, ...(input.args || [])], [input.command, ...(input.args || [])],
{ {
overlay: dockerProcedureContainer.overlay, overlay: dockerProcedureContainer.overlay,
@@ -137,7 +148,7 @@ export class PolyfillEffects implements oet.Effects {
), ),
term: () => daemon.then((daemon) => daemon.term()), term: () => daemon.then((daemon) => daemon.term()),
} }
} },
async chown(input: { async chown(input: {
volumeId: string volumeId: string
path: string path: string
@@ -145,8 +156,8 @@ export class PolyfillEffects implements oet.Effects {
}): Promise<null> { }): Promise<null> {
await startSdk await startSdk
.runCommand( .runCommand(
this.effects, effects,
{ id: this.manifest.main.image }, { id: manifest.main.image },
["chown", "--recursive", input.uid, `/drive/${input.path}`], ["chown", "--recursive", input.uid, `/drive/${input.path}`],
{ {
mounts: [ mounts: [
@@ -172,7 +183,7 @@ export class PolyfillEffects implements oet.Effects {
} }
}) })
return null return null
} },
async chmod(input: { async chmod(input: {
volumeId: string volumeId: string
path: string path: string
@@ -180,8 +191,8 @@ export class PolyfillEffects implements oet.Effects {
}): Promise<null> { }): Promise<null> {
await startSdk await startSdk
.runCommand( .runCommand(
this.effects, effects,
{ id: this.manifest.main.image }, { id: manifest.main.image },
["chmod", "--recursive", input.mode, `/drive/${input.path}`], ["chmod", "--recursive", input.mode, `/drive/${input.path}`],
{ {
mounts: [ mounts: [
@@ -207,33 +218,34 @@ export class PolyfillEffects implements oet.Effects {
} }
}) })
return null return null
} },
sleep(timeMs: number): Promise<null> { sleep(timeMs: number): Promise<null> {
return new Promise((resolve) => setTimeout(resolve, timeMs)) return new Promise((resolve) => setTimeout(resolve, timeMs))
} },
trace(whatToPrint: string): void { trace(whatToPrint: string): void {
console.trace(whatToPrint) console.trace(whatToPrint)
} },
warn(whatToPrint: string): void { warn(whatToPrint: string): void {
console.warn(whatToPrint) console.warn(whatToPrint)
} },
error(whatToPrint: string): void { error(whatToPrint: string): void {
console.error(whatToPrint) console.error(whatToPrint)
} },
debug(whatToPrint: string): void { debug(whatToPrint: string): void {
console.debug(whatToPrint) console.debug(whatToPrint)
} },
info(whatToPrint: string): void { info(whatToPrint: string): void {
console.log(false) console.log(false)
} },
is_sandboxed(): boolean { is_sandboxed(): boolean {
return false return false
} },
exists(input: { volumeId: string; path: string }): Promise<boolean> { exists(input: { volumeId: string; path: string }): Promise<boolean> {
return this.metadata(input) return self
.metadata(input)
.then(() => true) .then(() => true)
.catch(() => false) .catch(() => false)
} },
async fetch( async fetch(
url: string, url: string,
options?: options?:
@@ -269,7 +281,7 @@ export class PolyfillEffects implements oet.Effects {
text: () => fetched.text(), text: () => fetched.text(),
json: () => fetched.json(), json: () => fetched.json(),
} }
} },
runRsync(rsyncOptions: { runRsync(rsyncOptions: {
srcVolume: string srcVolume: string
@@ -282,10 +294,10 @@ export class PolyfillEffects implements oet.Effects {
wait: () => Promise<null> wait: () => Promise<null>
progress: () => Promise<number> progress: () => Promise<number>
} { } {
let secondRun: ReturnType<typeof this._runRsync> | undefined let secondRun: ReturnType<typeof self._runRsync> | undefined
let firstRun = this._runRsync(rsyncOptions) let firstRun = self._runRsync(rsyncOptions)
let waitValue = firstRun.wait().then((x) => { let waitValue = firstRun.wait().then((x) => {
secondRun = this._runRsync(rsyncOptions) secondRun = self._runRsync(rsyncOptions)
return secondRun.wait() return secondRun.wait()
}) })
const id = async () => { const id = async () => {
@@ -300,7 +312,7 @@ export class PolyfillEffects implements oet.Effects {
return (await firstRun.progress()) / 2.0 return (await firstRun.progress()) / 2.0
} }
return { id, wait, progress } return { id, wait, progress }
} },
_runRsync(rsyncOptions: { _runRsync(rsyncOptions: {
srcVolume: string srcVolume: string
dstVolume: string dstVolume: string
@@ -366,7 +378,7 @@ export class PolyfillEffects implements oet.Effects {
const wait = () => waitPromise const wait = () => waitPromise
const progress = () => Promise.resolve(percentage) const progress = () => Promise.resolve(percentage)
return { id, wait, progress } return { id, wait, progress }
} },
async diskUsage( async diskUsage(
options?: { volumeId: string; path: string } | undefined, options?: { volumeId: string; path: string } | undefined,
): Promise<{ used: number; total: number }> { ): Promise<{ used: number; total: number }> {
@@ -404,7 +416,9 @@ export class PolyfillEffects implements oet.Effects {
} }
} }
return output return output
},
} }
return self
} }
function parseDfOutput(output: string): { used: number; total: number } { function parseDfOutput(output: string): { used: number; total: number } {

View File

@@ -1,7 +1,7 @@
import { ExecuteResult, System } from "../../Interfaces/System" import { ExecuteResult, System } from "../../Interfaces/System"
import { unNestPath } from "../../Models/JsonPath" import { unNestPath } from "../../Models/JsonPath"
import matches, { any, number, object, string, tuple } from "ts-matches" import matches, { any, number, object, string, tuple } from "ts-matches"
import { HostSystemStartOs } from "../HostSystemStartOs" import { hostSystemStartOs } from "../HostSystemStartOs"
import { Effects } from "../../Models/Effects" import { Effects } from "../../Models/Effects"
import { RpcResult, matchRpcResult } from "../RpcListener" import { RpcResult, matchRpcResult } from "../RpcListener"
import { duration } from "../../Models/Duration" import { duration } from "../../Models/Duration"
@@ -15,7 +15,7 @@ export class SystemForStartOs implements System {
} }
constructor(readonly abi: T.ABI) {} constructor(readonly abi: T.ABI) {}
async execute( async execute(
effects: HostSystemStartOs, effectCreator: ReturnType<typeof hostSystemStartOs>,
options: { options: {
id: string id: string
procedure: procedure:
@@ -36,8 +36,7 @@ export class SystemForStartOs implements System {
timeout?: number | undefined timeout?: number | undefined
}, },
): Promise<RpcResult> { ): Promise<RpcResult> {
effects = Object.create(effects) const effects = effectCreator(options.id)
effects.procedureId = options.id
return this._execute(effects, options) return this._execute(effects, options)
.then((x) => .then((x) =>
matches(x) matches(x)

View File

@@ -2,6 +2,7 @@ import { types as T } from "@start9labs/start-sdk"
import { CallbackHolder } from "../Models/CallbackHolder" import { CallbackHolder } from "../Models/CallbackHolder"
import { Effects } from "../Models/Effects" import { Effects } from "../Models/Effects"
export type HostSystem = Effects export type HostSystem = Effects
export type GetHostSystem = (callbackHolder: CallbackHolder) => HostSystem export type GetHostSystem = (
callbackHolder: CallbackHolder,
) => (procedureId: null | string) => Effects

View File

@@ -1,7 +1,7 @@
import { types as T } from "@start9labs/start-sdk" import { types as T } from "@start9labs/start-sdk"
import { JsonPath } from "../Models/JsonPath" import { JsonPath } from "../Models/JsonPath"
import { HostSystemStartOs } from "../Adapters/HostSystemStartOs"
import { RpcResult } from "../Adapters/RpcListener" import { RpcResult } from "../Adapters/RpcListener"
import { hostSystemStartOs } from "../Adapters/HostSystemStartOs"
export type ExecuteResult = export type ExecuteResult =
| { ok: unknown } | { ok: unknown }
| { err: { code: number; message: string } } | { err: { code: number; message: string } }
@@ -12,7 +12,7 @@ export interface System {
// stop(effects: Effects, options: { timeout: number, signal?: number }): Promise<void> // stop(effects: Effects, options: { timeout: number, signal?: number }): Promise<void>
execute( execute(
effects: T.Effects, effectCreator: ReturnType<typeof hostSystemStartOs>,
options: { options: {
id: string id: string
procedure: JsonPath procedure: JsonPath

View File

@@ -1,12 +1,12 @@
import { RpcListener } from "./Adapters/RpcListener" import { RpcListener } from "./Adapters/RpcListener"
import { SystemForEmbassy } from "./Adapters/Systems/SystemForEmbassy" import { SystemForEmbassy } from "./Adapters/Systems/SystemForEmbassy"
import { HostSystemStartOs } from "./Adapters/HostSystemStartOs" import { hostSystemStartOs } from "./Adapters/HostSystemStartOs"
import { AllGetDependencies } from "./Interfaces/AllGetDependencies" import { AllGetDependencies } from "./Interfaces/AllGetDependencies"
import { getSystem } from "./Adapters/Systems" import { getSystem } from "./Adapters/Systems"
const getDependencies: AllGetDependencies = { const getDependencies: AllGetDependencies = {
system: getSystem, system: getSystem,
hostSystem: () => HostSystemStartOs.of, hostSystem: () => hostSystemStartOs,
} }
new RpcListener(getDependencies) new RpcListener(getDependencies)

View File

@@ -16,7 +16,8 @@ mkdir -p ./firmware/$PLATFORM
cd ./firmware/$PLATFORM cd ./firmware/$PLATFORM
mapfile -t firmwares <<< "$(jq -c ".[] | select(.platform[] | contains(\"$PLATFORM\"))" ../../build/lib/firmware.json)" firmwares=()
while IFS= read -r line; do firmwares+=("$line"); done < <(jq -c ".[] | select(.platform[] | contains(\"$PLATFORM\"))" ../../build/lib/firmware.json)
for firmware in "${firmwares[@]}"; do for firmware in "${firmwares[@]}"; do
if [ -n "$firmware" ]; then if [ -n "$firmware" ]; then
id=$(echo "$firmware" | jq --raw-output '.id') id=$(echo "$firmware" | jq --raw-output '.id')