Files
start-os/container-runtime/src/Adapters/Systems/SystemForStartOs.ts
Aiden McClelland 3f380fa0da feature: pack s9pk (#2642)
* TODO: images

* wip

* pack s9pk images

* include path in packsource error

* debug info

* add cmd as context to invoke

* filehelper bugfix

* fix file helper

* fix exposeForDependents

* misc fixes

* force image removal

* fix filtering

* fix deadlock

* fix api

* chore: Up the version of the package.json

* always allow concurrency within same call stack

* Update core/startos/src/s9pk/merkle_archive/expected.rs

Co-authored-by: Jade <2364004+Blu-J@users.noreply.github.com>

---------

Co-authored-by: J H <dragondef@gmail.com>
Co-authored-by: Jade <2364004+Blu-J@users.noreply.github.com>
2024-06-12 17:46:59 +00:00

194 lines
6.2 KiB
TypeScript

import { ExecuteResult, System } from "../../Interfaces/System"
import { unNestPath } from "../../Models/JsonPath"
import matches, { any, number, object, string, tuple } from "ts-matches"
import { HostSystemStartOs } from "../HostSystemStartOs"
import { Effects } from "../../Models/Effects"
import { RpcResult, matchRpcResult } from "../RpcListener"
import { duration } from "../../Models/Duration"
import { T } from "@start9labs/start-sdk"
import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk"
export const STARTOS_JS_LOCATION = "/usr/lib/startos/package/index.js"
export class SystemForStartOs implements System {
private onTerm: (() => Promise<void>) | undefined
static of() {
return new SystemForStartOs(require(STARTOS_JS_LOCATION))
}
constructor(readonly abi: T.ABI) {}
async execute(
effects: HostSystemStartOs,
options: {
id: string
procedure:
| "/init"
| "/uninit"
| "/main/start"
| "/main/stop"
| "/config/set"
| "/config/get"
| "/backup/create"
| "/backup/restore"
| "/actions/metadata"
| `/actions/${string}/get`
| `/actions/${string}/run`
| `/dependencies/${string}/query`
| `/dependencies/${string}/update`
input: unknown
timeout?: number | undefined
},
): Promise<RpcResult> {
effects = Object.create(effects)
effects.procedureId = options.id
return this._execute(effects, options)
.then((x) =>
matches(x)
.when(
object({
result: any,
}),
(x) => x,
)
.when(
object({
error: string,
}),
(x) => ({
error: {
code: 0,
message: x.error,
},
}),
)
.when(
object({
"error-code": tuple(number, string),
}),
({ "error-code": [code, message] }) => ({
error: {
code,
message,
},
}),
)
.defaultTo({ result: x }),
)
.catch((error: unknown) => {
if (error instanceof Error)
return {
error: {
code: 0,
message: error.name,
data: {
details: error.message,
debug: `${error?.cause ?? "[noCause]"}:${error?.stack ?? "[noStack]"}`,
},
},
}
if (matchRpcResult.test(error)) return error
return {
error: {
code: 0,
message: String(error),
},
}
})
}
async _execute(
effects: Effects,
options: {
procedure:
| "/init"
| "/uninit"
| "/main/start"
| "/main/stop"
| "/config/set"
| "/config/get"
| "/backup/create"
| "/backup/restore"
| "/actions/metadata"
| `/actions/${string}/get`
| `/actions/${string}/run`
| `/dependencies/${string}/query`
| `/dependencies/${string}/update`
input: unknown
timeout?: number | undefined
},
): Promise<unknown> {
switch (options.procedure) {
case "/init": {
const previousVersion =
string.optional().unsafeCast(options.input) || null
return this.abi.init({ effects, previousVersion })
}
case "/uninit": {
const nextVersion = string.optional().unsafeCast(options.input) || null
return this.abi.uninit({ effects, nextVersion })
}
case "/main/start": {
const started = async (onTerm: () => Promise<void>) => {
await effects.setMainStatus({ status: "running" })
if (this.onTerm) await this.onTerm()
this.onTerm = onTerm
}
const daemons = await (
await this.abi.main({
effects: { ...effects, _type: "main" },
started,
})
).build()
this.onTerm = daemons.term
}
case "/main/stop": {
await effects.setMainStatus({ status: "stopped" })
if (this.onTerm) await this.onTerm()
delete this.onTerm
return duration(30, "s")
}
case "/config/set": {
const input = options.input as any // TODO
return this.abi.setConfig({ effects, input })
}
case "/config/get": {
return this.abi.getConfig({ effects })
}
case "/backup/create":
case "/backup/restore":
throw new Error("this should be called with the init/unit")
case "/actions/metadata": {
return this.abi.actionsMetadata({ effects })
}
default:
const procedures = unNestPath(options.procedure)
const id = procedures[2]
switch (true) {
case procedures[1] === "actions" && procedures[3] === "get": {
const action = (await this.abi.actions({ effects }))[id]
if (!action) throw new Error(`Action ${id} not found`)
return action.getConfig({ effects })
}
case procedures[1] === "actions" && procedures[3] === "run": {
const action = (await this.abi.actions({ effects }))[id]
if (!action) throw new Error(`Action ${id} not found`)
return action.run({ effects, input: options.input as any }) // TODO
}
case procedures[1] === "dependencies" && procedures[3] === "query": {
const dependencyConfig = this.abi.dependencyConfig[id]
if (!dependencyConfig)
throw new Error(`dependencyConfig ${id} not found`)
const localConfig = options.input
return dependencyConfig.query({ effects })
}
case procedures[1] === "dependencies" && procedures[3] === "update": {
const dependencyConfig = this.abi.dependencyConfig[id]
if (!dependencyConfig)
throw new Error(`dependencyConfig ${id} not found`)
return dependencyConfig.update(options.input as any) // TODO
}
}
}
throw new Error(`Method ${options.procedure} not implemented.`)
}
async exit(effects: Effects): Promise<void> {
return void null
}
}