mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
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:
@@ -31,274 +31,273 @@ 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">(
|
||||||
}
|
method: K,
|
||||||
|
params: Record<string, unknown>,
|
||||||
constructor(readonly callbackHolder: CallbackHolder) {}
|
) => {
|
||||||
id = 0
|
const id = hostSystemId++
|
||||||
rpcRound<K extends keyof Effects | "getStore" | "setStore">(
|
const client = net.createConnection({ path: SOCKET_PATH }, () => {
|
||||||
method: K,
|
client.write(
|
||||||
params: Record<string, unknown>,
|
JSON.stringify({
|
||||||
) {
|
id,
|
||||||
const id = this.id++
|
method,
|
||||||
const client = net.createConnection({ path: SOCKET_PATH }, () => {
|
params: { ...params, procedureId: procedureId },
|
||||||
client.write(
|
}) + "\n",
|
||||||
JSON.stringify({
|
)
|
||||||
id,
|
})
|
||||||
method,
|
let bufs: Buffer[] = []
|
||||||
params: { ...params, procedureId: this.procedureId },
|
return new Promise((resolve, reject) => {
|
||||||
}) + "\n",
|
client.on("data", (data) => {
|
||||||
)
|
try {
|
||||||
})
|
bufs.push(data)
|
||||||
let bufs: Buffer[] = []
|
if (data.reduce((acc, x) => acc || x == 10, false)) {
|
||||||
return new Promise((resolve, reject) => {
|
const res: unknown = JSON.parse(
|
||||||
client.on("data", (data) => {
|
Buffer.concat(bufs).toString().split("\n")[0],
|
||||||
try {
|
)
|
||||||
bufs.push(data)
|
if (testRpcError(res)) {
|
||||||
if (data.reduce((acc, x) => acc || x == 10, false)) {
|
let message = res.error.message
|
||||||
const res: unknown = JSON.parse(
|
console.error({ method, params, hostSystemStartOs: true })
|
||||||
Buffer.concat(bufs).toString().split("\n")[0],
|
if (string.test(res.error.data)) {
|
||||||
)
|
message += ": " + res.error.data
|
||||||
if (testRpcError(res)) {
|
console.error(res.error.data)
|
||||||
let message = res.error.message
|
} else {
|
||||||
console.error({ method, params, hostSystemStartOs: true })
|
if (res.error.data?.details) {
|
||||||
if (string.test(res.error.data)) {
|
message += ": " + res.error.data.details
|
||||||
message += ": " + res.error.data
|
console.error(res.error.data.details)
|
||||||
console.error(res.error.data)
|
}
|
||||||
|
if (res.error.data?.debug) {
|
||||||
|
message += "\n" + res.error.data.debug
|
||||||
|
console.error("Debug: " + res.error.data.debug)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reject(new Error(`${message}@${method}`))
|
||||||
|
} else if (testRpcResult(res)) {
|
||||||
|
resolve(res.result)
|
||||||
} else {
|
} else {
|
||||||
if (res.error.data?.details) {
|
reject(new Error(`malformed response ${JSON.stringify(res)}`))
|
||||||
message += ": " + res.error.data.details
|
|
||||||
console.error(res.error.data.details)
|
|
||||||
}
|
|
||||||
if (res.error.data?.debug) {
|
|
||||||
message += "\n" + res.error.data.debug
|
|
||||||
console.error("Debug: " + res.error.data.debug)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
reject(new Error(`${message}@${method}`))
|
|
||||||
} else if (testRpcResult(res)) {
|
|
||||||
resolve(res.result)
|
|
||||||
} else {
|
|
||||||
reject(new Error(`malformed response ${JSON.stringify(res)}`))
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
client.end()
|
||||||
|
})
|
||||||
|
client.on("error", (error) => {
|
||||||
reject(error)
|
reject(error)
|
||||||
}
|
})
|
||||||
client.end()
|
|
||||||
})
|
})
|
||||||
client.on("error", (error) => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
bind(...[options]: Parameters<T.Effects["bind"]>) {
|
|
||||||
return this.rpcRound("bind", {
|
|
||||||
...options,
|
|
||||||
stack: new Error().stack,
|
|
||||||
}) as ReturnType<T.Effects["bind"]>
|
|
||||||
}
|
|
||||||
clearBindings(...[]: Parameters<T.Effects["clearBindings"]>) {
|
|
||||||
return this.rpcRound("clearBindings", {}) as ReturnType<
|
|
||||||
T.Effects["clearBindings"]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
clearServiceInterfaces(
|
|
||||||
...[]: Parameters<T.Effects["clearServiceInterfaces"]>
|
|
||||||
) {
|
|
||||||
return this.rpcRound("clearServiceInterfaces", {}) as ReturnType<
|
|
||||||
T.Effects["clearServiceInterfaces"]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
createOverlayedImage(options: {
|
|
||||||
imageId: string
|
|
||||||
}): Promise<[string, string]> {
|
|
||||||
return this.rpcRound("createOverlayedImage", options) as ReturnType<
|
|
||||||
T.Effects["createOverlayedImage"]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
destroyOverlayedImage(options: { guid: string }): Promise<void> {
|
|
||||||
return this.rpcRound("destroyOverlayedImage", options) as ReturnType<
|
|
||||||
T.Effects["destroyOverlayedImage"]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
executeAction(...[options]: Parameters<T.Effects["executeAction"]>) {
|
|
||||||
return this.rpcRound("executeAction", options) as ReturnType<
|
|
||||||
T.Effects["executeAction"]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
exists(...[packageId]: Parameters<T.Effects["exists"]>) {
|
|
||||||
return this.rpcRound("exists", packageId) as ReturnType<T.Effects["exists"]>
|
|
||||||
}
|
|
||||||
exportAction(...[options]: Parameters<T.Effects["exportAction"]>) {
|
|
||||||
return this.rpcRound("exportAction", options) as ReturnType<
|
|
||||||
T.Effects["exportAction"]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
exportServiceInterface: Effects["exportServiceInterface"] = (
|
|
||||||
...[options]: Parameters<Effects["exportServiceInterface"]>
|
|
||||||
) => {
|
|
||||||
return this.rpcRound("exportServiceInterface", options) as ReturnType<
|
|
||||||
T.Effects["exportServiceInterface"]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
exposeForDependents(
|
|
||||||
...[options]: Parameters<T.Effects["exposeForDependents"]>
|
|
||||||
) {
|
|
||||||
return this.rpcRound("exposeForDependents", options) as ReturnType<
|
|
||||||
T.Effects["exposeForDependents"]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
getConfigured(...[]: Parameters<T.Effects["getConfigured"]>) {
|
|
||||||
return this.rpcRound("getConfigured", {}) as ReturnType<
|
|
||||||
T.Effects["getConfigured"]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
getContainerIp(...[]: Parameters<T.Effects["getContainerIp"]>) {
|
|
||||||
return this.rpcRound("getContainerIp", {}) as ReturnType<
|
|
||||||
T.Effects["getContainerIp"]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
getHostInfo: Effects["getHostInfo"] = (...[allOptions]: any[]) => {
|
|
||||||
const options = {
|
|
||||||
...allOptions,
|
|
||||||
callback: this.callbackHolder.addCallback(allOptions.callback),
|
|
||||||
}
|
}
|
||||||
return this.rpcRound("getHostInfo", options) as ReturnType<
|
const self: Effects = {
|
||||||
T.Effects["getHostInfo"]
|
bind(...[options]: Parameters<T.Effects["bind"]>) {
|
||||||
> as any
|
return rpcRound("bind", {
|
||||||
}
|
...options,
|
||||||
getServiceInterface(
|
stack: new Error().stack,
|
||||||
...[options]: Parameters<T.Effects["getServiceInterface"]>
|
}) as ReturnType<T.Effects["bind"]>
|
||||||
) {
|
},
|
||||||
return this.rpcRound("getServiceInterface", {
|
clearBindings(...[]: Parameters<T.Effects["clearBindings"]>) {
|
||||||
...options,
|
return rpcRound("clearBindings", {}) as ReturnType<
|
||||||
callback: this.callbackHolder.addCallback(options.callback),
|
T.Effects["clearBindings"]
|
||||||
}) as ReturnType<T.Effects["getServiceInterface"]>
|
>
|
||||||
}
|
},
|
||||||
|
clearServiceInterfaces(
|
||||||
|
...[]: Parameters<T.Effects["clearServiceInterfaces"]>
|
||||||
|
) {
|
||||||
|
return rpcRound("clearServiceInterfaces", {}) as ReturnType<
|
||||||
|
T.Effects["clearServiceInterfaces"]
|
||||||
|
>
|
||||||
|
},
|
||||||
|
createOverlayedImage(options: {
|
||||||
|
imageId: string
|
||||||
|
}): Promise<[string, string]> {
|
||||||
|
return rpcRound("createOverlayedImage", options) as ReturnType<
|
||||||
|
T.Effects["createOverlayedImage"]
|
||||||
|
>
|
||||||
|
},
|
||||||
|
destroyOverlayedImage(options: { guid: string }): Promise<void> {
|
||||||
|
return rpcRound("destroyOverlayedImage", options) as ReturnType<
|
||||||
|
T.Effects["destroyOverlayedImage"]
|
||||||
|
>
|
||||||
|
},
|
||||||
|
executeAction(...[options]: Parameters<T.Effects["executeAction"]>) {
|
||||||
|
return rpcRound("executeAction", options) as ReturnType<
|
||||||
|
T.Effects["executeAction"]
|
||||||
|
>
|
||||||
|
},
|
||||||
|
exists(...[packageId]: Parameters<T.Effects["exists"]>) {
|
||||||
|
return rpcRound("exists", packageId) as ReturnType<T.Effects["exists"]>
|
||||||
|
},
|
||||||
|
exportAction(...[options]: Parameters<T.Effects["exportAction"]>) {
|
||||||
|
return rpcRound("exportAction", options) as ReturnType<
|
||||||
|
T.Effects["exportAction"]
|
||||||
|
>
|
||||||
|
},
|
||||||
|
exportServiceInterface: ((
|
||||||
|
...[options]: Parameters<Effects["exportServiceInterface"]>
|
||||||
|
) => {
|
||||||
|
return rpcRound("exportServiceInterface", options) as ReturnType<
|
||||||
|
T.Effects["exportServiceInterface"]
|
||||||
|
>
|
||||||
|
}) as Effects["exportServiceInterface"],
|
||||||
|
exposeForDependents(
|
||||||
|
...[options]: Parameters<T.Effects["exposeForDependents"]>
|
||||||
|
) {
|
||||||
|
return rpcRound("exposeForDependents", options) as ReturnType<
|
||||||
|
T.Effects["exposeForDependents"]
|
||||||
|
>
|
||||||
|
},
|
||||||
|
getConfigured(...[]: Parameters<T.Effects["getConfigured"]>) {
|
||||||
|
return rpcRound("getConfigured", {}) as ReturnType<
|
||||||
|
T.Effects["getConfigured"]
|
||||||
|
>
|
||||||
|
},
|
||||||
|
getContainerIp(...[]: Parameters<T.Effects["getContainerIp"]>) {
|
||||||
|
return rpcRound("getContainerIp", {}) as ReturnType<
|
||||||
|
T.Effects["getContainerIp"]
|
||||||
|
>
|
||||||
|
},
|
||||||
|
getHostInfo: ((...[allOptions]: any[]) => {
|
||||||
|
const options = {
|
||||||
|
...allOptions,
|
||||||
|
callback: callbackHolder.addCallback(allOptions.callback),
|
||||||
|
}
|
||||||
|
return rpcRound("getHostInfo", options) as ReturnType<
|
||||||
|
T.Effects["getHostInfo"]
|
||||||
|
> as any
|
||||||
|
}) as Effects["getHostInfo"],
|
||||||
|
getServiceInterface(
|
||||||
|
...[options]: Parameters<T.Effects["getServiceInterface"]>
|
||||||
|
) {
|
||||||
|
return rpcRound("getServiceInterface", {
|
||||||
|
...options,
|
||||||
|
callback: callbackHolder.addCallback(options.callback),
|
||||||
|
}) 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],
|
||||||
T.Effects["getSslCertificate"]
|
) {
|
||||||
>
|
return rpcRound("getSslCertificate", options) as ReturnType<
|
||||||
}
|
T.Effects["getSslCertificate"]
|
||||||
getSslKey(options: Parameters<T.Effects["getSslKey"]>[0]) {
|
>
|
||||||
return this.rpcRound("getSslKey", options) as ReturnType<
|
},
|
||||||
T.Effects["getSslKey"]
|
getSslKey(options: Parameters<T.Effects["getSslKey"]>[0]) {
|
||||||
>
|
return rpcRound("getSslKey", options) as ReturnType<
|
||||||
}
|
T.Effects["getSslKey"]
|
||||||
getSystemSmtp(...[options]: Parameters<T.Effects["getSystemSmtp"]>) {
|
>
|
||||||
return this.rpcRound("getSystemSmtp", {
|
},
|
||||||
...options,
|
getSystemSmtp(...[options]: Parameters<T.Effects["getSystemSmtp"]>) {
|
||||||
callback: this.callbackHolder.addCallback(options.callback),
|
return rpcRound("getSystemSmtp", {
|
||||||
}) as ReturnType<T.Effects["getSystemSmtp"]>
|
...options,
|
||||||
}
|
callback: callbackHolder.addCallback(options.callback),
|
||||||
listServiceInterfaces(
|
}) as ReturnType<T.Effects["getSystemSmtp"]>
|
||||||
...[options]: Parameters<T.Effects["listServiceInterfaces"]>
|
},
|
||||||
) {
|
listServiceInterfaces(
|
||||||
return this.rpcRound("listServiceInterfaces", {
|
...[options]: Parameters<T.Effects["listServiceInterfaces"]>
|
||||||
...options,
|
) {
|
||||||
callback: this.callbackHolder.addCallback(options.callback),
|
return rpcRound("listServiceInterfaces", {
|
||||||
}) as ReturnType<T.Effects["listServiceInterfaces"]>
|
...options,
|
||||||
}
|
callback: callbackHolder.addCallback(options.callback),
|
||||||
mount(...[options]: Parameters<T.Effects["mount"]>) {
|
}) as ReturnType<T.Effects["listServiceInterfaces"]>
|
||||||
return this.rpcRound("mount", options) as ReturnType<T.Effects["mount"]>
|
},
|
||||||
}
|
mount(...[options]: Parameters<T.Effects["mount"]>) {
|
||||||
removeAction(...[options]: Parameters<T.Effects["removeAction"]>) {
|
return rpcRound("mount", options) as ReturnType<T.Effects["mount"]>
|
||||||
return this.rpcRound("removeAction", options) as ReturnType<
|
},
|
||||||
T.Effects["removeAction"]
|
removeAction(...[options]: Parameters<T.Effects["removeAction"]>) {
|
||||||
>
|
return rpcRound("removeAction", options) as ReturnType<
|
||||||
}
|
T.Effects["removeAction"]
|
||||||
removeAddress(...[options]: Parameters<T.Effects["removeAddress"]>) {
|
>
|
||||||
return this.rpcRound("removeAddress", options) as ReturnType<
|
},
|
||||||
T.Effects["removeAddress"]
|
removeAddress(...[options]: Parameters<T.Effects["removeAddress"]>) {
|
||||||
>
|
return rpcRound("removeAddress", options) as ReturnType<
|
||||||
}
|
T.Effects["removeAddress"]
|
||||||
restart(...[]: Parameters<T.Effects["restart"]>) {
|
>
|
||||||
return this.rpcRound("restart", {}) as ReturnType<T.Effects["restart"]>
|
},
|
||||||
}
|
restart(...[]: Parameters<T.Effects["restart"]>) {
|
||||||
running(...[packageId]: Parameters<T.Effects["running"]>) {
|
return rpcRound("restart", {}) as ReturnType<T.Effects["restart"]>
|
||||||
return this.rpcRound("running", { packageId }) as ReturnType<
|
},
|
||||||
T.Effects["running"]
|
running(...[packageId]: Parameters<T.Effects["running"]>) {
|
||||||
>
|
return rpcRound("running", { packageId }) as ReturnType<
|
||||||
}
|
T.Effects["running"]
|
||||||
// runRsync(...[options]: Parameters<T.Effects[""]>) {
|
>
|
||||||
//
|
},
|
||||||
// return this.rpcRound('executeAction', options) as ReturnType<T.Effects["executeAction"]>
|
// runRsync(...[options]: Parameters<T.Effects[""]>) {
|
||||||
//
|
//
|
||||||
// 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"]>) {
|
// return rpcRound('executeAction', options) as ReturnType<T.Effects["executeAction"]>
|
||||||
return this.rpcRound("setConfigured", { configured }) as ReturnType<
|
// }
|
||||||
T.Effects["setConfigured"]
|
setConfigured(...[configured]: Parameters<T.Effects["setConfigured"]>) {
|
||||||
>
|
return rpcRound("setConfigured", { configured }) as ReturnType<
|
||||||
}
|
T.Effects["setConfigured"]
|
||||||
setDependencies(
|
>
|
||||||
dependencies: Parameters<T.Effects["setDependencies"]>[0],
|
},
|
||||||
): ReturnType<T.Effects["setDependencies"]> {
|
setDependencies(
|
||||||
return this.rpcRound("setDependencies", dependencies) as ReturnType<
|
dependencies: Parameters<T.Effects["setDependencies"]>[0],
|
||||||
T.Effects["setDependencies"]
|
): ReturnType<T.Effects["setDependencies"]> {
|
||||||
>
|
return rpcRound("setDependencies", dependencies) as ReturnType<
|
||||||
}
|
T.Effects["setDependencies"]
|
||||||
checkDependencies(
|
>
|
||||||
options: Parameters<T.Effects["checkDependencies"]>[0],
|
},
|
||||||
): ReturnType<T.Effects["checkDependencies"]> {
|
checkDependencies(
|
||||||
return this.rpcRound("checkDependencies", options) as ReturnType<
|
options: Parameters<T.Effects["checkDependencies"]>[0],
|
||||||
T.Effects["checkDependencies"]
|
): ReturnType<T.Effects["checkDependencies"]> {
|
||||||
>
|
return rpcRound("checkDependencies", options) as ReturnType<
|
||||||
}
|
T.Effects["checkDependencies"]
|
||||||
getDependencies(): ReturnType<T.Effects["getDependencies"]> {
|
>
|
||||||
return this.rpcRound("getDependencies", {}) as ReturnType<
|
},
|
||||||
T.Effects["getDependencies"]
|
getDependencies(): ReturnType<T.Effects["getDependencies"]> {
|
||||||
>
|
return rpcRound("getDependencies", {}) as ReturnType<
|
||||||
}
|
T.Effects["getDependencies"]
|
||||||
setHealth(...[options]: Parameters<T.Effects["setHealth"]>) {
|
>
|
||||||
return this.rpcRound("setHealth", options) as ReturnType<
|
},
|
||||||
T.Effects["setHealth"]
|
setHealth(...[options]: Parameters<T.Effects["setHealth"]>) {
|
||||||
>
|
return rpcRound("setHealth", options) as ReturnType<
|
||||||
}
|
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"]>) {
|
||||||
|
return rpcRound("stopped", { packageId }) as ReturnType<
|
||||||
|
T.Effects["stopped"]
|
||||||
|
>
|
||||||
|
},
|
||||||
|
store: {
|
||||||
|
get: async (options: any) =>
|
||||||
|
rpcRound("getStore", {
|
||||||
|
...options,
|
||||||
|
callback: callbackHolder.addCallback(options.callback),
|
||||||
|
}) as any,
|
||||||
|
set: async (options: any) =>
|
||||||
|
rpcRound("setStore", options) as ReturnType<
|
||||||
|
T.Effects["store"]["set"]
|
||||||
|
>,
|
||||||
|
} as T.Effects["store"],
|
||||||
|
}
|
||||||
|
return self
|
||||||
}
|
}
|
||||||
stopped(...[packageId]: Parameters<T.Effects["stopped"]>) {
|
|
||||||
return this.rpcRound("stopped", { packageId }) as ReturnType<
|
|
||||||
T.Effects["stopped"]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
store: T.Effects["store"] = {
|
|
||||||
get: async (options: any) =>
|
|
||||||
this.rpcRound("getStore", {
|
|
||||||
...options,
|
|
||||||
callback: this.callbackHolder.addCallback(options.callback),
|
|
||||||
}) as any,
|
|
||||||
set: async (options: any) =>
|
|
||||||
this.rpcRound("setStore", options) as ReturnType<
|
|
||||||
T.Effects["store"]["set"]
|
|
||||||
>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -4,390 +4,175 @@ 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 = {
|
||||||
async writeFile(input: {
|
effects,
|
||||||
path: string
|
manifest,
|
||||||
volumeId: string
|
async writeFile(input: {
|
||||||
toWrite: string
|
path: string
|
||||||
}): Promise<void> {
|
volumeId: string
|
||||||
await fs.writeFile(
|
toWrite: string
|
||||||
new Volume(input.volumeId, input.path).path,
|
}): Promise<void> {
|
||||||
input.toWrite,
|
await fs.writeFile(
|
||||||
)
|
new Volume(input.volumeId, input.path).path,
|
||||||
}
|
input.toWrite,
|
||||||
async readFile(input: { volumeId: string; path: string }): Promise<string> {
|
)
|
||||||
return (
|
},
|
||||||
await fs.readFile(new Volume(input.volumeId, input.path).path)
|
async readFile(input: { volumeId: string; path: string }): Promise<string> {
|
||||||
).toString()
|
return (
|
||||||
}
|
|
||||||
async metadata(input: {
|
|
||||||
volumeId: string
|
|
||||||
path: string
|
|
||||||
}): Promise<oet.Metadata> {
|
|
||||||
const stats = await fs.stat(new Volume(input.volumeId, input.path).path)
|
|
||||||
return {
|
|
||||||
fileType: stats.isFile() ? "file" : "directory",
|
|
||||||
gid: stats.gid,
|
|
||||||
uid: stats.uid,
|
|
||||||
mode: stats.mode,
|
|
||||||
isDir: stats.isDirectory(),
|
|
||||||
isFile: stats.isFile(),
|
|
||||||
isSymlink: stats.isSymbolicLink(),
|
|
||||||
len: stats.size,
|
|
||||||
readonly: (stats.mode & 0o200) > 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async createDir(input: { volumeId: string; path: string }): Promise<string> {
|
|
||||||
const path = new Volume(input.volumeId, input.path).path
|
|
||||||
await fs.mkdir(path, { recursive: true })
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
async readDir(input: { volumeId: string; path: string }): Promise<string[]> {
|
|
||||||
return fs.readdir(new Volume(input.volumeId, input.path).path)
|
|
||||||
}
|
|
||||||
async removeDir(input: { volumeId: string; path: string }): Promise<string> {
|
|
||||||
const path = new Volume(input.volumeId, input.path).path
|
|
||||||
await fs.rmdir(new Volume(input.volumeId, input.path).path, {
|
|
||||||
recursive: true,
|
|
||||||
})
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
removeFile(input: { volumeId: string; path: string }): Promise<void> {
|
|
||||||
return fs.rm(new Volume(input.volumeId, input.path).path)
|
|
||||||
}
|
|
||||||
async writeJsonFile(input: {
|
|
||||||
volumeId: string
|
|
||||||
path: string
|
|
||||||
toWrite: Record<string, unknown>
|
|
||||||
}): Promise<void> {
|
|
||||||
await fs.writeFile(
|
|
||||||
new Volume(input.volumeId, input.path).path,
|
|
||||||
JSON.stringify(input.toWrite),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
async readJsonFile(input: {
|
|
||||||
volumeId: string
|
|
||||||
path: string
|
|
||||||
}): Promise<Record<string, unknown>> {
|
|
||||||
return JSON.parse(
|
|
||||||
(
|
|
||||||
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: {
|
||||||
runCommand({
|
volumeId: string
|
||||||
command,
|
path: string
|
||||||
args,
|
}): Promise<oet.Metadata> {
|
||||||
timeoutMillis,
|
const stats = await fs.stat(new Volume(input.volumeId, input.path).path)
|
||||||
}: {
|
return {
|
||||||
command: string
|
fileType: stats.isFile() ? "file" : "directory",
|
||||||
args?: string[] | undefined
|
gid: stats.gid,
|
||||||
timeoutMillis?: number | undefined
|
uid: stats.uid,
|
||||||
}): Promise<oet.ResultType<string>> {
|
mode: stats.mode,
|
||||||
return startSdk
|
isDir: stats.isDirectory(),
|
||||||
.runCommand(
|
isFile: stats.isFile(),
|
||||||
this.effects,
|
isSymlink: stats.isSymbolicLink(),
|
||||||
{ id: this.manifest.main.image },
|
len: stats.size,
|
||||||
[command, ...(args || [])],
|
readonly: (stats.mode & 0o200) > 0,
|
||||||
{},
|
}
|
||||||
|
},
|
||||||
|
async createDir(input: {
|
||||||
|
volumeId: string
|
||||||
|
path: string
|
||||||
|
}): Promise<string> {
|
||||||
|
const path = new Volume(input.volumeId, input.path).path
|
||||||
|
await fs.mkdir(path, { recursive: true })
|
||||||
|
return path
|
||||||
|
},
|
||||||
|
async readDir(input: {
|
||||||
|
volumeId: string
|
||||||
|
path: string
|
||||||
|
}): Promise<string[]> {
|
||||||
|
return fs.readdir(new Volume(input.volumeId, input.path).path)
|
||||||
|
},
|
||||||
|
async removeDir(input: {
|
||||||
|
volumeId: string
|
||||||
|
path: string
|
||||||
|
}): Promise<string> {
|
||||||
|
const path = new Volume(input.volumeId, input.path).path
|
||||||
|
await fs.rmdir(new Volume(input.volumeId, input.path).path, {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
|
return path
|
||||||
|
},
|
||||||
|
removeFile(input: { volumeId: string; path: string }): Promise<void> {
|
||||||
|
return fs.rm(new Volume(input.volumeId, input.path).path)
|
||||||
|
},
|
||||||
|
async writeJsonFile(input: {
|
||||||
|
volumeId: string
|
||||||
|
path: string
|
||||||
|
toWrite: Record<string, unknown>
|
||||||
|
}): Promise<void> {
|
||||||
|
await fs.writeFile(
|
||||||
|
new Volume(input.volumeId, input.path).path,
|
||||||
|
JSON.stringify(input.toWrite),
|
||||||
)
|
)
|
||||||
.then((x: any) => ({
|
},
|
||||||
stderr: x.stderr.toString(),
|
async readJsonFile(input: {
|
||||||
stdout: x.stdout.toString(),
|
volumeId: string
|
||||||
}))
|
path: string
|
||||||
.then((x: any) =>
|
}): Promise<Record<string, unknown>> {
|
||||||
!!x.stderr ? { error: x.stderr } : { result: x.stdout },
|
return JSON.parse(
|
||||||
|
(
|
||||||
|
await fs.readFile(new Volume(input.volumeId, input.path).path)
|
||||||
|
).toString(),
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
runDaemon(input: { command: string; args?: string[] | undefined }): {
|
runCommand({
|
||||||
wait(): Promise<oet.ResultType<string>>
|
command,
|
||||||
term(): Promise<void>
|
args,
|
||||||
} {
|
timeoutMillis,
|
||||||
const dockerProcedureContainer = DockerProcedureContainer.of(
|
}: {
|
||||||
this.effects,
|
command: string
|
||||||
this.manifest.main,
|
args?: string[] | undefined
|
||||||
this.manifest.volumes,
|
timeoutMillis?: number | undefined
|
||||||
)
|
}): Promise<oet.ResultType<string>> {
|
||||||
const daemon = dockerProcedureContainer.then((dockerProcedureContainer) =>
|
return startSdk
|
||||||
daemons.runCommand()(
|
.runCommand(
|
||||||
this.effects,
|
effects,
|
||||||
{ id: this.manifest.main.image },
|
{ id: manifest.main.image },
|
||||||
[input.command, ...(input.args || [])],
|
[command, ...(args || [])],
|
||||||
{
|
{},
|
||||||
overlay: dockerProcedureContainer.overlay,
|
)
|
||||||
},
|
.then((x: any) => ({
|
||||||
),
|
stderr: x.stderr.toString(),
|
||||||
)
|
stdout: x.stdout.toString(),
|
||||||
return {
|
}))
|
||||||
wait: () =>
|
.then((x: any) =>
|
||||||
daemon.then((daemon) =>
|
!!x.stderr ? { error: x.stderr } : { result: x.stdout },
|
||||||
daemon.wait().then(() => {
|
)
|
||||||
return { result: "" }
|
},
|
||||||
}),
|
runDaemon(input: { command: string; args?: string[] | undefined }): {
|
||||||
|
wait(): Promise<oet.ResultType<string>>
|
||||||
|
term(): Promise<void>
|
||||||
|
} {
|
||||||
|
const dockerProcedureContainer = DockerProcedureContainer.of(
|
||||||
|
effects,
|
||||||
|
manifest.main,
|
||||||
|
manifest.volumes,
|
||||||
|
)
|
||||||
|
const daemon = dockerProcedureContainer.then((dockerProcedureContainer) =>
|
||||||
|
daemons.runCommand()(
|
||||||
|
effects,
|
||||||
|
{ id: manifest.main.image },
|
||||||
|
[input.command, ...(input.args || [])],
|
||||||
|
{
|
||||||
|
overlay: dockerProcedureContainer.overlay,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
term: () => daemon.then((daemon) => daemon.term()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async chown(input: {
|
|
||||||
volumeId: string
|
|
||||||
path: string
|
|
||||||
uid: string
|
|
||||||
}): Promise<null> {
|
|
||||||
await startSdk
|
|
||||||
.runCommand(
|
|
||||||
this.effects,
|
|
||||||
{ id: this.manifest.main.image },
|
|
||||||
["chown", "--recursive", input.uid, `/drive/${input.path}`],
|
|
||||||
{
|
|
||||||
mounts: [
|
|
||||||
{
|
|
||||||
path: "/drive",
|
|
||||||
options: {
|
|
||||||
type: "volume",
|
|
||||||
id: input.volumeId,
|
|
||||||
subpath: null,
|
|
||||||
readonly: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.then((x: any) => ({
|
return {
|
||||||
stderr: x.stderr.toString(),
|
wait: () =>
|
||||||
stdout: x.stdout.toString(),
|
daemon.then((daemon) =>
|
||||||
}))
|
daemon.wait().then(() => {
|
||||||
.then((x: any) => {
|
return { result: "" }
|
||||||
if (!!x.stderr) {
|
}),
|
||||||
throw new Error(x.stderr)
|
),
|
||||||
}
|
term: () => daemon.then((daemon) => daemon.term()),
|
||||||
})
|
}
|
||||||
return null
|
},
|
||||||
}
|
async chown(input: {
|
||||||
async chmod(input: {
|
volumeId: string
|
||||||
volumeId: string
|
path: string
|
||||||
path: string
|
uid: string
|
||||||
mode: string
|
}): Promise<null> {
|
||||||
}): Promise<null> {
|
await startSdk
|
||||||
await startSdk
|
.runCommand(
|
||||||
.runCommand(
|
effects,
|
||||||
this.effects,
|
{ id: manifest.main.image },
|
||||||
{ id: this.manifest.main.image },
|
["chown", "--recursive", input.uid, `/drive/${input.path}`],
|
||||||
["chmod", "--recursive", input.mode, `/drive/${input.path}`],
|
{
|
||||||
{
|
mounts: [
|
||||||
mounts: [
|
{
|
||||||
{
|
path: "/drive",
|
||||||
path: "/drive",
|
options: {
|
||||||
options: {
|
type: "volume",
|
||||||
type: "volume",
|
id: input.volumeId,
|
||||||
id: input.volumeId,
|
subpath: null,
|
||||||
subpath: null,
|
readonly: false,
|
||||||
readonly: false,
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
},
|
)
|
||||||
)
|
|
||||||
.then((x: any) => ({
|
|
||||||
stderr: x.stderr.toString(),
|
|
||||||
stdout: x.stdout.toString(),
|
|
||||||
}))
|
|
||||||
.then((x: any) => {
|
|
||||||
if (!!x.stderr) {
|
|
||||||
throw new Error(x.stderr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
sleep(timeMs: number): Promise<null> {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, timeMs))
|
|
||||||
}
|
|
||||||
trace(whatToPrint: string): void {
|
|
||||||
console.trace(whatToPrint)
|
|
||||||
}
|
|
||||||
warn(whatToPrint: string): void {
|
|
||||||
console.warn(whatToPrint)
|
|
||||||
}
|
|
||||||
error(whatToPrint: string): void {
|
|
||||||
console.error(whatToPrint)
|
|
||||||
}
|
|
||||||
debug(whatToPrint: string): void {
|
|
||||||
console.debug(whatToPrint)
|
|
||||||
}
|
|
||||||
info(whatToPrint: string): void {
|
|
||||||
console.log(false)
|
|
||||||
}
|
|
||||||
is_sandboxed(): boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
exists(input: { volumeId: string; path: string }): Promise<boolean> {
|
|
||||||
return this.metadata(input)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false)
|
|
||||||
}
|
|
||||||
async fetch(
|
|
||||||
url: string,
|
|
||||||
options?:
|
|
||||||
| {
|
|
||||||
method?:
|
|
||||||
| "GET"
|
|
||||||
| "POST"
|
|
||||||
| "PUT"
|
|
||||||
| "DELETE"
|
|
||||||
| "HEAD"
|
|
||||||
| "PATCH"
|
|
||||||
| undefined
|
|
||||||
headers?: Record<string, string> | undefined
|
|
||||||
body?: string | undefined
|
|
||||||
}
|
|
||||||
| undefined,
|
|
||||||
): Promise<{
|
|
||||||
method: string
|
|
||||||
ok: boolean
|
|
||||||
status: number
|
|
||||||
headers: Record<string, string>
|
|
||||||
body?: string | null | undefined
|
|
||||||
text(): Promise<string>
|
|
||||||
json(): Promise<unknown>
|
|
||||||
}> {
|
|
||||||
const fetched = await fetch(url, options)
|
|
||||||
return {
|
|
||||||
method: fetched.type,
|
|
||||||
ok: fetched.ok,
|
|
||||||
status: fetched.status,
|
|
||||||
headers: Object.fromEntries(fetched.headers.entries()),
|
|
||||||
body: await fetched.text(),
|
|
||||||
text: () => fetched.text(),
|
|
||||||
json: () => fetched.json(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runRsync(rsyncOptions: {
|
|
||||||
srcVolume: string
|
|
||||||
dstVolume: string
|
|
||||||
srcPath: string
|
|
||||||
dstPath: string
|
|
||||||
options: oet.BackupOptions
|
|
||||||
}): {
|
|
||||||
id: () => Promise<string>
|
|
||||||
wait: () => Promise<null>
|
|
||||||
progress: () => Promise<number>
|
|
||||||
} {
|
|
||||||
let secondRun: ReturnType<typeof this._runRsync> | undefined
|
|
||||||
let firstRun = this._runRsync(rsyncOptions)
|
|
||||||
let waitValue = firstRun.wait().then((x) => {
|
|
||||||
secondRun = this._runRsync(rsyncOptions)
|
|
||||||
return secondRun.wait()
|
|
||||||
})
|
|
||||||
const id = async () => {
|
|
||||||
return secondRun?.id?.() ?? firstRun.id()
|
|
||||||
}
|
|
||||||
const wait = () => waitValue
|
|
||||||
const progress = async () => {
|
|
||||||
const secondProgress = secondRun?.progress?.()
|
|
||||||
if (secondProgress) {
|
|
||||||
return (await secondProgress) / 2.0 + 0.5
|
|
||||||
}
|
|
||||||
return (await firstRun.progress()) / 2.0
|
|
||||||
}
|
|
||||||
return { id, wait, progress }
|
|
||||||
}
|
|
||||||
_runRsync(rsyncOptions: {
|
|
||||||
srcVolume: string
|
|
||||||
dstVolume: string
|
|
||||||
srcPath: string
|
|
||||||
dstPath: string
|
|
||||||
options: oet.BackupOptions
|
|
||||||
}): {
|
|
||||||
id: () => Promise<string>
|
|
||||||
wait: () => Promise<null>
|
|
||||||
progress: () => Promise<number>
|
|
||||||
} {
|
|
||||||
const { srcVolume, dstVolume, srcPath, dstPath, options } = rsyncOptions
|
|
||||||
const command = "rsync"
|
|
||||||
const args: string[] = []
|
|
||||||
if (options.delete) {
|
|
||||||
args.push("--delete")
|
|
||||||
}
|
|
||||||
if (options.force) {
|
|
||||||
args.push("--force")
|
|
||||||
}
|
|
||||||
if (options.ignoreExisting) {
|
|
||||||
args.push("--ignore-existing")
|
|
||||||
}
|
|
||||||
for (const exclude of options.exclude) {
|
|
||||||
args.push(`--exclude=${exclude}`)
|
|
||||||
}
|
|
||||||
args.push("-actAXH")
|
|
||||||
args.push("--info=progress2")
|
|
||||||
args.push("--no-inc-recursive")
|
|
||||||
args.push(new Volume(srcVolume, srcPath).path)
|
|
||||||
args.push(new Volume(dstVolume, dstPath).path)
|
|
||||||
const spawned = child_process.spawn(command, args, { detached: true })
|
|
||||||
let percentage = 0.0
|
|
||||||
spawned.stdout.on("data", (data: unknown) => {
|
|
||||||
const lines = String(data).replace("\r", "\n").split("\n")
|
|
||||||
for (const line of lines) {
|
|
||||||
const parsed = /$([0-9.]+)%/.exec(line)?.[1]
|
|
||||||
if (!parsed) continue
|
|
||||||
percentage = Number.parseFloat(parsed)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
spawned.stderr.on("data", (data: unknown) => {
|
|
||||||
console.error(String(data))
|
|
||||||
})
|
|
||||||
|
|
||||||
const id = async () => {
|
|
||||||
const pid = spawned.pid
|
|
||||||
if (pid === undefined) {
|
|
||||||
throw new Error("rsync process has no pid")
|
|
||||||
}
|
|
||||||
return String(pid)
|
|
||||||
}
|
|
||||||
const waitPromise = new Promise<null>((resolve, reject) => {
|
|
||||||
spawned.on("exit", (code: any) => {
|
|
||||||
if (code === 0) {
|
|
||||||
resolve(null)
|
|
||||||
} else {
|
|
||||||
reject(new Error(`rsync exited with code ${code}`))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
const wait = () => waitPromise
|
|
||||||
const progress = () => Promise.resolve(percentage)
|
|
||||||
return { id, wait, progress }
|
|
||||||
}
|
|
||||||
async diskUsage(
|
|
||||||
options?: { volumeId: string; path: string } | undefined,
|
|
||||||
): Promise<{ used: number; total: number }> {
|
|
||||||
const output = await execFile("df", ["--block-size=1", "-P", "/"])
|
|
||||||
.then((x: any) => ({
|
|
||||||
stderr: x.stderr.toString(),
|
|
||||||
stdout: x.stdout.toString(),
|
|
||||||
}))
|
|
||||||
.then((x: any) => {
|
|
||||||
if (!!x.stderr) {
|
|
||||||
throw new Error(x.stderr)
|
|
||||||
}
|
|
||||||
return parseDfOutput(x.stdout)
|
|
||||||
})
|
|
||||||
if (!!options) {
|
|
||||||
const used = await execFile("du", [
|
|
||||||
"-s",
|
|
||||||
"--block-size=1",
|
|
||||||
"-P",
|
|
||||||
new Volume(options.volumeId, options.path).path,
|
|
||||||
])
|
|
||||||
.then((x: any) => ({
|
.then((x: any) => ({
|
||||||
stderr: x.stderr.toString(),
|
stderr: x.stderr.toString(),
|
||||||
stdout: x.stdout.toString(),
|
stdout: x.stdout.toString(),
|
||||||
@@ -396,15 +181,244 @@ export class PolyfillEffects implements oet.Effects {
|
|||||||
if (!!x.stderr) {
|
if (!!x.stderr) {
|
||||||
throw new Error(x.stderr)
|
throw new Error(x.stderr)
|
||||||
}
|
}
|
||||||
return Number.parseInt(x.stdout.split(/\s+/)[0])
|
|
||||||
})
|
})
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
async chmod(input: {
|
||||||
|
volumeId: string
|
||||||
|
path: string
|
||||||
|
mode: string
|
||||||
|
}): Promise<null> {
|
||||||
|
await startSdk
|
||||||
|
.runCommand(
|
||||||
|
effects,
|
||||||
|
{ id: manifest.main.image },
|
||||||
|
["chmod", "--recursive", input.mode, `/drive/${input.path}`],
|
||||||
|
{
|
||||||
|
mounts: [
|
||||||
|
{
|
||||||
|
path: "/drive",
|
||||||
|
options: {
|
||||||
|
type: "volume",
|
||||||
|
id: input.volumeId,
|
||||||
|
subpath: null,
|
||||||
|
readonly: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then((x: any) => ({
|
||||||
|
stderr: x.stderr.toString(),
|
||||||
|
stdout: x.stdout.toString(),
|
||||||
|
}))
|
||||||
|
.then((x: any) => {
|
||||||
|
if (!!x.stderr) {
|
||||||
|
throw new Error(x.stderr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
sleep(timeMs: number): Promise<null> {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, timeMs))
|
||||||
|
},
|
||||||
|
trace(whatToPrint: string): void {
|
||||||
|
console.trace(whatToPrint)
|
||||||
|
},
|
||||||
|
warn(whatToPrint: string): void {
|
||||||
|
console.warn(whatToPrint)
|
||||||
|
},
|
||||||
|
error(whatToPrint: string): void {
|
||||||
|
console.error(whatToPrint)
|
||||||
|
},
|
||||||
|
debug(whatToPrint: string): void {
|
||||||
|
console.debug(whatToPrint)
|
||||||
|
},
|
||||||
|
info(whatToPrint: string): void {
|
||||||
|
console.log(false)
|
||||||
|
},
|
||||||
|
is_sandboxed(): boolean {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
exists(input: { volumeId: string; path: string }): Promise<boolean> {
|
||||||
|
return self
|
||||||
|
.metadata(input)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false)
|
||||||
|
},
|
||||||
|
async fetch(
|
||||||
|
url: string,
|
||||||
|
options?:
|
||||||
|
| {
|
||||||
|
method?:
|
||||||
|
| "GET"
|
||||||
|
| "POST"
|
||||||
|
| "PUT"
|
||||||
|
| "DELETE"
|
||||||
|
| "HEAD"
|
||||||
|
| "PATCH"
|
||||||
|
| undefined
|
||||||
|
headers?: Record<string, string> | undefined
|
||||||
|
body?: string | undefined
|
||||||
|
}
|
||||||
|
| undefined,
|
||||||
|
): Promise<{
|
||||||
|
method: string
|
||||||
|
ok: boolean
|
||||||
|
status: number
|
||||||
|
headers: Record<string, string>
|
||||||
|
body?: string | null | undefined
|
||||||
|
text(): Promise<string>
|
||||||
|
json(): Promise<unknown>
|
||||||
|
}> {
|
||||||
|
const fetched = await fetch(url, options)
|
||||||
return {
|
return {
|
||||||
...output,
|
method: fetched.type,
|
||||||
used,
|
ok: fetched.ok,
|
||||||
|
status: fetched.status,
|
||||||
|
headers: Object.fromEntries(fetched.headers.entries()),
|
||||||
|
body: await fetched.text(),
|
||||||
|
text: () => fetched.text(),
|
||||||
|
json: () => fetched.json(),
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
return output
|
|
||||||
|
runRsync(rsyncOptions: {
|
||||||
|
srcVolume: string
|
||||||
|
dstVolume: string
|
||||||
|
srcPath: string
|
||||||
|
dstPath: string
|
||||||
|
options: oet.BackupOptions
|
||||||
|
}): {
|
||||||
|
id: () => Promise<string>
|
||||||
|
wait: () => Promise<null>
|
||||||
|
progress: () => Promise<number>
|
||||||
|
} {
|
||||||
|
let secondRun: ReturnType<typeof self._runRsync> | undefined
|
||||||
|
let firstRun = self._runRsync(rsyncOptions)
|
||||||
|
let waitValue = firstRun.wait().then((x) => {
|
||||||
|
secondRun = self._runRsync(rsyncOptions)
|
||||||
|
return secondRun.wait()
|
||||||
|
})
|
||||||
|
const id = async () => {
|
||||||
|
return secondRun?.id?.() ?? firstRun.id()
|
||||||
|
}
|
||||||
|
const wait = () => waitValue
|
||||||
|
const progress = async () => {
|
||||||
|
const secondProgress = secondRun?.progress?.()
|
||||||
|
if (secondProgress) {
|
||||||
|
return (await secondProgress) / 2.0 + 0.5
|
||||||
|
}
|
||||||
|
return (await firstRun.progress()) / 2.0
|
||||||
|
}
|
||||||
|
return { id, wait, progress }
|
||||||
|
},
|
||||||
|
_runRsync(rsyncOptions: {
|
||||||
|
srcVolume: string
|
||||||
|
dstVolume: string
|
||||||
|
srcPath: string
|
||||||
|
dstPath: string
|
||||||
|
options: oet.BackupOptions
|
||||||
|
}): {
|
||||||
|
id: () => Promise<string>
|
||||||
|
wait: () => Promise<null>
|
||||||
|
progress: () => Promise<number>
|
||||||
|
} {
|
||||||
|
const { srcVolume, dstVolume, srcPath, dstPath, options } = rsyncOptions
|
||||||
|
const command = "rsync"
|
||||||
|
const args: string[] = []
|
||||||
|
if (options.delete) {
|
||||||
|
args.push("--delete")
|
||||||
|
}
|
||||||
|
if (options.force) {
|
||||||
|
args.push("--force")
|
||||||
|
}
|
||||||
|
if (options.ignoreExisting) {
|
||||||
|
args.push("--ignore-existing")
|
||||||
|
}
|
||||||
|
for (const exclude of options.exclude) {
|
||||||
|
args.push(`--exclude=${exclude}`)
|
||||||
|
}
|
||||||
|
args.push("-actAXH")
|
||||||
|
args.push("--info=progress2")
|
||||||
|
args.push("--no-inc-recursive")
|
||||||
|
args.push(new Volume(srcVolume, srcPath).path)
|
||||||
|
args.push(new Volume(dstVolume, dstPath).path)
|
||||||
|
const spawned = child_process.spawn(command, args, { detached: true })
|
||||||
|
let percentage = 0.0
|
||||||
|
spawned.stdout.on("data", (data: unknown) => {
|
||||||
|
const lines = String(data).replace("\r", "\n").split("\n")
|
||||||
|
for (const line of lines) {
|
||||||
|
const parsed = /$([0-9.]+)%/.exec(line)?.[1]
|
||||||
|
if (!parsed) continue
|
||||||
|
percentage = Number.parseFloat(parsed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
spawned.stderr.on("data", (data: unknown) => {
|
||||||
|
console.error(String(data))
|
||||||
|
})
|
||||||
|
|
||||||
|
const id = async () => {
|
||||||
|
const pid = spawned.pid
|
||||||
|
if (pid === undefined) {
|
||||||
|
throw new Error("rsync process has no pid")
|
||||||
|
}
|
||||||
|
return String(pid)
|
||||||
|
}
|
||||||
|
const waitPromise = new Promise<null>((resolve, reject) => {
|
||||||
|
spawned.on("exit", (code: any) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(null)
|
||||||
|
} else {
|
||||||
|
reject(new Error(`rsync exited with code ${code}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const wait = () => waitPromise
|
||||||
|
const progress = () => Promise.resolve(percentage)
|
||||||
|
return { id, wait, progress }
|
||||||
|
},
|
||||||
|
async diskUsage(
|
||||||
|
options?: { volumeId: string; path: string } | undefined,
|
||||||
|
): Promise<{ used: number; total: number }> {
|
||||||
|
const output = await execFile("df", ["--block-size=1", "-P", "/"])
|
||||||
|
.then((x: any) => ({
|
||||||
|
stderr: x.stderr.toString(),
|
||||||
|
stdout: x.stdout.toString(),
|
||||||
|
}))
|
||||||
|
.then((x: any) => {
|
||||||
|
if (!!x.stderr) {
|
||||||
|
throw new Error(x.stderr)
|
||||||
|
}
|
||||||
|
return parseDfOutput(x.stdout)
|
||||||
|
})
|
||||||
|
if (!!options) {
|
||||||
|
const used = await execFile("du", [
|
||||||
|
"-s",
|
||||||
|
"--block-size=1",
|
||||||
|
"-P",
|
||||||
|
new Volume(options.volumeId, options.path).path,
|
||||||
|
])
|
||||||
|
.then((x: any) => ({
|
||||||
|
stderr: x.stderr.toString(),
|
||||||
|
stdout: x.stdout.toString(),
|
||||||
|
}))
|
||||||
|
.then((x: any) => {
|
||||||
|
if (!!x.stderr) {
|
||||||
|
throw new Error(x.stderr)
|
||||||
|
}
|
||||||
|
return Number.parseInt(x.stdout.split(/\s+/)[0])
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
...output,
|
||||||
|
used,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDfOutput(output: string): { used: number; total: number } {
|
function parseDfOutput(output: string): { used: number; total: number } {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user