mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 22:39:46 +00:00
Fix/overlay destroy (#2707)
* feature: Make all errors in console.error be including an error for that stack tract * feature: Make all errors in console.error be including an error for that stack tract * fix: Add the tinisubreaper for the subreapers to know they are not the reaper * fix: overlay always destroyed * chore: Move the style of destroy to just private
This commit is contained in:
17
container-runtime/package-lock.json
generated
17
container-runtime/package-lock.json
generated
@@ -20,6 +20,7 @@
|
|||||||
"node-fetch": "^3.1.0",
|
"node-fetch": "^3.1.0",
|
||||||
"ts-matches": "^5.5.1",
|
"ts-matches": "^5.5.1",
|
||||||
"tslib": "^2.5.3",
|
"tslib": "^2.5.3",
|
||||||
|
"tslog": "^4.9.3",
|
||||||
"typescript": "^5.1.3",
|
"typescript": "^5.1.3",
|
||||||
"yaml": "^2.3.1"
|
"yaml": "^2.3.1"
|
||||||
},
|
},
|
||||||
@@ -5627,6 +5628,17 @@
|
|||||||
"version": "2.6.3",
|
"version": "2.6.3",
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
|
"node_modules/tslog": {
|
||||||
|
"version": "4.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslog/-/tslog-4.9.3.tgz",
|
||||||
|
"integrity": "sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fullstack-build/tslog?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||||
@@ -9746,6 +9758,11 @@
|
|||||||
"tslib": {
|
"tslib": {
|
||||||
"version": "2.6.3"
|
"version": "2.6.3"
|
||||||
},
|
},
|
||||||
|
"tslog": {
|
||||||
|
"version": "4.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslog/-/tslog-4.9.3.tgz",
|
||||||
|
"integrity": "sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw=="
|
||||||
|
},
|
||||||
"type-check": {
|
"type-check": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { types as T } from "@start9labs/start-sdk"
|
import { types as T, utils } from "@start9labs/start-sdk"
|
||||||
import * as net from "net"
|
import * as net from "net"
|
||||||
import { object, string, number, literals, some, unknown } from "ts-matches"
|
import { object, string, number, literals, some, unknown } from "ts-matches"
|
||||||
import { Effects } from "../Models/Effects"
|
import { Effects } from "../Models/Effects"
|
||||||
@@ -65,7 +65,10 @@ const rpcRoundFor =
|
|||||||
)
|
)
|
||||||
if (testRpcError(res)) {
|
if (testRpcError(res)) {
|
||||||
let message = res.error.message
|
let message = res.error.message
|
||||||
console.error("Error in host RPC:", { method, params })
|
console.error(
|
||||||
|
"Error in host RPC:",
|
||||||
|
utils.asError({ method, params }),
|
||||||
|
)
|
||||||
if (string.test(res.error.data)) {
|
if (string.test(res.error.data)) {
|
||||||
message += ": " + res.error.data
|
message += ": " + res.error.data
|
||||||
console.error(`Details: ${res.error.data}`)
|
console.error(`Details: ${res.error.data}`)
|
||||||
|
|||||||
@@ -4,19 +4,35 @@ import { Overlay, types as T } from "@start9labs/start-sdk"
|
|||||||
import { promisify } from "util"
|
import { promisify } from "util"
|
||||||
import { DockerProcedure, VolumeId } from "../../../Models/DockerProcedure"
|
import { DockerProcedure, VolumeId } from "../../../Models/DockerProcedure"
|
||||||
import { Volume } from "./matchVolume"
|
import { Volume } from "./matchVolume"
|
||||||
|
import { ExecSpawnable } from "@start9labs/start-sdk/cjs/lib/util/Overlay"
|
||||||
export const exec = promisify(cp.exec)
|
export const exec = promisify(cp.exec)
|
||||||
export const execFile = promisify(cp.execFile)
|
export const execFile = promisify(cp.execFile)
|
||||||
|
|
||||||
export class DockerProcedureContainer {
|
export class DockerProcedureContainer {
|
||||||
private constructor(readonly overlay: Overlay) {}
|
private constructor(private readonly overlay: ExecSpawnable) {}
|
||||||
// static async readonlyOf(data: DockerProcedure) {
|
|
||||||
// return DockerProcedureContainer.of(data, ["-o", "ro"])
|
|
||||||
// }
|
|
||||||
static async of(
|
static async of(
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
packageId: string,
|
packageId: string,
|
||||||
data: DockerProcedure,
|
data: DockerProcedure,
|
||||||
volumes: { [id: VolumeId]: Volume },
|
volumes: { [id: VolumeId]: Volume },
|
||||||
|
options: { overlay?: ExecSpawnable } = {},
|
||||||
|
) {
|
||||||
|
const overlay =
|
||||||
|
options?.overlay ??
|
||||||
|
(await DockerProcedureContainer.createOverlay(
|
||||||
|
effects,
|
||||||
|
packageId,
|
||||||
|
data,
|
||||||
|
volumes,
|
||||||
|
))
|
||||||
|
return new DockerProcedureContainer(overlay)
|
||||||
|
}
|
||||||
|
static async createOverlay(
|
||||||
|
effects: T.Effects,
|
||||||
|
packageId: string,
|
||||||
|
data: DockerProcedure,
|
||||||
|
volumes: { [id: VolumeId]: Volume },
|
||||||
) {
|
) {
|
||||||
const overlay = await Overlay.of(effects, { id: data.image })
|
const overlay = await Overlay.of(effects, { id: data.image })
|
||||||
|
|
||||||
@@ -84,23 +100,18 @@ export class DockerProcedureContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return overlay
|
||||||
return new DockerProcedureContainer(overlay)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async exec(commands: string[], { destroy = true } = {}) {
|
async exec(commands: string[], {} = {}) {
|
||||||
try {
|
try {
|
||||||
return await this.overlay.exec(commands)
|
return await this.overlay.exec(commands)
|
||||||
} finally {
|
} finally {
|
||||||
if (destroy) await this.overlay.destroy()
|
await this.overlay.destroy?.()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async execFail(
|
async execFail(commands: string[], timeoutMs: number | null, {} = {}) {
|
||||||
commands: string[],
|
|
||||||
timeoutMs: number | null,
|
|
||||||
{ destroy = true } = {},
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const res = await this.overlay.exec(commands, {}, timeoutMs)
|
const res = await this.overlay.exec(commands, {}, timeoutMs)
|
||||||
if (res.exitCode !== 0) {
|
if (res.exitCode !== 0) {
|
||||||
@@ -114,7 +125,7 @@ export class DockerProcedureContainer {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
} finally {
|
} finally {
|
||||||
if (destroy) await this.overlay.destroy()
|
await this.overlay.destroy?.()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { T, utils } 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"
|
import { Effects } from "../../../Models/Effects"
|
||||||
import { off } from "node:process"
|
import { off } from "node:process"
|
||||||
|
import { CommandController } from "@start9labs/start-sdk/cjs/lib/mainFn/CommandController"
|
||||||
|
|
||||||
const EMBASSY_HEALTH_INTERVAL = 15 * 1000
|
const EMBASSY_HEALTH_INTERVAL = 15 * 1000
|
||||||
const EMBASSY_PROPERTIES_LOOP = 30 * 1000
|
const EMBASSY_PROPERTIES_LOOP = 30 * 1000
|
||||||
@@ -14,9 +15,8 @@ const EMBASSY_PROPERTIES_LOOP = 30 * 1000
|
|||||||
* Also, this has an ability to clean itself up too if need be.
|
* Also, this has an ability to clean itself up too if need be.
|
||||||
*/
|
*/
|
||||||
export class MainLoop {
|
export class MainLoop {
|
||||||
private _mainDockerContainer?: DockerProcedureContainer
|
get mainOverlay() {
|
||||||
get mainDockerContainer() {
|
return this.mainEvent?.daemon?.overlay
|
||||||
return this._mainDockerContainer
|
|
||||||
}
|
}
|
||||||
private healthLoops?: {
|
private healthLoops?: {
|
||||||
name: string
|
name: string
|
||||||
@@ -52,27 +52,33 @@ export class MainLoop {
|
|||||||
await this.setupInterfaces(effects)
|
await this.setupInterfaces(effects)
|
||||||
await effects.setMainStatus({ status: "running" })
|
await effects.setMainStatus({ status: "running" })
|
||||||
const jsMain = (this.system.moduleCode as any)?.jsMain
|
const jsMain = (this.system.moduleCode as any)?.jsMain
|
||||||
const dockerProcedureContainer = await DockerProcedureContainer.of(
|
|
||||||
effects,
|
|
||||||
this.system.manifest.id,
|
|
||||||
this.system.manifest.main,
|
|
||||||
this.system.manifest.volumes,
|
|
||||||
)
|
|
||||||
this._mainDockerContainer = dockerProcedureContainer
|
|
||||||
if (jsMain) {
|
if (jsMain) {
|
||||||
throw new Error("Unreachable")
|
throw new Error("Unreachable")
|
||||||
}
|
}
|
||||||
const daemon = await Daemon.of()(
|
const daemon = new Daemon(async () => {
|
||||||
this.effects,
|
const overlay = await DockerProcedureContainer.createOverlay(
|
||||||
{ id: this.system.manifest.main.image },
|
effects,
|
||||||
currentCommand,
|
this.system.manifest.id,
|
||||||
{
|
this.system.manifest.main,
|
||||||
overlay: dockerProcedureContainer.overlay,
|
this.system.manifest.volumes,
|
||||||
sigtermTimeout: utils.inMs(
|
)
|
||||||
this.system.manifest.main["sigterm-timeout"],
|
return CommandController.of()(
|
||||||
),
|
this.effects,
|
||||||
},
|
|
||||||
)
|
{ id: this.system.manifest.main.image },
|
||||||
|
currentCommand,
|
||||||
|
{
|
||||||
|
overlay,
|
||||||
|
env: {
|
||||||
|
TINI_SUBREAPER: "true",
|
||||||
|
},
|
||||||
|
sigtermTimeout: utils.inMs(
|
||||||
|
this.system.manifest.main["sigterm-timeout"],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
daemon.start()
|
daemon.start()
|
||||||
return {
|
return {
|
||||||
daemon,
|
daemon,
|
||||||
@@ -128,10 +134,11 @@ export class MainLoop {
|
|||||||
const main = await mainEvent
|
const main = await mainEvent
|
||||||
delete this.mainEvent
|
delete this.mainEvent
|
||||||
delete this.healthLoops
|
delete this.healthLoops
|
||||||
await main?.daemon.stop().catch((e) => console.error(e))
|
await main?.daemon
|
||||||
|
.stop()
|
||||||
|
.catch((e) => console.error(`Main loop error`, utils.asError(e)))
|
||||||
this.effects.setMainStatus({ status: "stopped" })
|
this.effects.setMainStatus({ status: "stopped" })
|
||||||
if (healthLoops) healthLoops.forEach((x) => clearInterval(x.interval))
|
if (healthLoops) healthLoops.forEach((x) => clearInterval(x.interval))
|
||||||
delete this._mainDockerContainer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructHealthLoops() {
|
private constructHealthLoops() {
|
||||||
@@ -144,24 +151,27 @@ export class MainLoop {
|
|||||||
const actionProcedure = value
|
const actionProcedure = value
|
||||||
const timeChanged = Date.now() - start
|
const timeChanged = Date.now() - start
|
||||||
if (actionProcedure.type === "docker") {
|
if (actionProcedure.type === "docker") {
|
||||||
|
const overlay = actionProcedure.inject
|
||||||
|
? this.mainOverlay
|
||||||
|
: undefined
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const container =
|
const container =
|
||||||
actionProcedure.inject && this._mainDockerContainer ?
|
|
||||||
this._mainDockerContainer :
|
|
||||||
await DockerProcedureContainer.of(
|
await DockerProcedureContainer.of(
|
||||||
effects,
|
effects,
|
||||||
manifest.id,
|
manifest.id,
|
||||||
actionProcedure,
|
actionProcedure,
|
||||||
manifest.volumes,
|
manifest.volumes,
|
||||||
|
{
|
||||||
|
overlay,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
const shouldDestroy = container !== this._mainDockerContainer
|
|
||||||
const executed = await container.exec(
|
const executed = await container.exec(
|
||||||
[
|
[
|
||||||
actionProcedure.entrypoint,
|
actionProcedure.entrypoint,
|
||||||
...actionProcedure.args,
|
...actionProcedure.args,
|
||||||
JSON.stringify(timeChanged),
|
JSON.stringify(timeChanged),
|
||||||
],
|
],
|
||||||
{ destroy: shouldDestroy },
|
{},
|
||||||
)
|
)
|
||||||
if (executed.exitCode === 0) {
|
if (executed.exitCode === 0) {
|
||||||
await effects.setHealth({
|
await effects.setHealth({
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export class SystemForEmbassy implements System {
|
|||||||
const moduleCode = await import(EMBASSY_JS_LOCATION)
|
const moduleCode = await import(EMBASSY_JS_LOCATION)
|
||||||
.catch((_) => require(EMBASSY_JS_LOCATION))
|
.catch((_) => require(EMBASSY_JS_LOCATION))
|
||||||
.catch(async (_) => {
|
.catch(async (_) => {
|
||||||
console.error("Could not load the js")
|
console.error(utils.asError("Could not load the js"))
|
||||||
console.error({
|
console.error({
|
||||||
exists: await fs.stat(EMBASSY_JS_LOCATION),
|
exists: await fs.stat(EMBASSY_JS_LOCATION),
|
||||||
})
|
})
|
||||||
@@ -798,17 +798,18 @@ export class SystemForEmbassy implements System {
|
|||||||
const actionProcedure = this.manifest.actions?.[actionId]?.implementation
|
const actionProcedure = this.manifest.actions?.[actionId]?.implementation
|
||||||
if (!actionProcedure) return { message: "Action not found", value: null }
|
if (!actionProcedure) return { message: "Action not found", value: null }
|
||||||
if (actionProcedure.type === "docker") {
|
if (actionProcedure.type === "docker") {
|
||||||
const container =
|
const overlay = actionProcedure.inject
|
||||||
actionProcedure.inject && this.currentRunning?.mainDockerContainer
|
? this.currentRunning?.mainOverlay
|
||||||
? this.currentRunning?.mainDockerContainer
|
: undefined
|
||||||
: await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
effects,
|
effects,
|
||||||
this.manifest.id,
|
this.manifest.id,
|
||||||
actionProcedure,
|
actionProcedure,
|
||||||
this.manifest.volumes,
|
this.manifest.volumes,
|
||||||
)
|
{
|
||||||
const shouldDestroy =
|
overlay,
|
||||||
container !== this.currentRunning?.mainDockerContainer
|
},
|
||||||
|
)
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
(
|
(
|
||||||
await container.execFail(
|
await container.execFail(
|
||||||
@@ -818,7 +819,6 @@ export class SystemForEmbassy implements System {
|
|||||||
JSON.stringify(formData),
|
JSON.stringify(formData),
|
||||||
],
|
],
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
{ destroy: shouldDestroy },
|
|
||||||
)
|
)
|
||||||
).stdout.toString(),
|
).stdout.toString(),
|
||||||
)
|
)
|
||||||
@@ -987,7 +987,10 @@ async function updateConfig(
|
|||||||
})
|
})
|
||||||
.once()
|
.once()
|
||||||
.catch((x) => {
|
.catch((x) => {
|
||||||
console.error("Could not get the service interface", x)
|
console.error(
|
||||||
|
"Could not get the service interface",
|
||||||
|
utils.asError(x),
|
||||||
|
)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
const catchFn = <X>(fn: () => X) => {
|
const catchFn = <X>(fn: () => X) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as oet from "./oldEmbassyTypes"
|
|||||||
import { Volume } from "../../../Models/Volume"
|
import { Volume } from "../../../Models/Volume"
|
||||||
import * as child_process from "child_process"
|
import * as child_process from "child_process"
|
||||||
import { promisify } from "util"
|
import { promisify } from "util"
|
||||||
import { daemons, startSdk, T } from "@start9labs/start-sdk"
|
import { daemons, startSdk, T, utils } from "@start9labs/start-sdk"
|
||||||
import "isomorphic-fetch"
|
import "isomorphic-fetch"
|
||||||
import { Manifest } from "./matchManifest"
|
import { Manifest } from "./matchManifest"
|
||||||
import { DockerProcedureContainer } from "./DockerProcedureContainer"
|
import { DockerProcedureContainer } from "./DockerProcedureContainer"
|
||||||
@@ -124,19 +124,19 @@ export const polyfillEffects = (
|
|||||||
wait(): Promise<oet.ResultType<string>>
|
wait(): Promise<oet.ResultType<string>>
|
||||||
term(): Promise<void>
|
term(): Promise<void>
|
||||||
} {
|
} {
|
||||||
const dockerProcedureContainer = DockerProcedureContainer.of(
|
const promiseOverlay = DockerProcedureContainer.createOverlay(
|
||||||
effects,
|
effects,
|
||||||
manifest.id,
|
manifest.id,
|
||||||
manifest.main,
|
manifest.main,
|
||||||
manifest.volumes,
|
manifest.volumes,
|
||||||
)
|
)
|
||||||
const daemon = dockerProcedureContainer.then((dockerProcedureContainer) =>
|
const daemon = promiseOverlay.then((overlay) =>
|
||||||
daemons.runCommand()(
|
daemons.runCommand()(
|
||||||
effects,
|
effects,
|
||||||
{ id: manifest.main.image },
|
{ id: manifest.main.image },
|
||||||
[input.command, ...(input.args || [])],
|
[input.command, ...(input.args || [])],
|
||||||
{
|
{
|
||||||
overlay: dockerProcedureContainer.overlay,
|
overlay,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -224,16 +224,16 @@ export const polyfillEffects = (
|
|||||||
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(utils.asError(whatToPrint))
|
||||||
},
|
},
|
||||||
warn(whatToPrint: string): void {
|
warn(whatToPrint: string): void {
|
||||||
console.warn(whatToPrint)
|
console.warn(utils.asError(whatToPrint))
|
||||||
},
|
},
|
||||||
error(whatToPrint: string): void {
|
error(whatToPrint: string): void {
|
||||||
console.error(whatToPrint)
|
console.error(utils.asError(whatToPrint))
|
||||||
},
|
},
|
||||||
debug(whatToPrint: string): void {
|
debug(whatToPrint: string): void {
|
||||||
console.debug(whatToPrint)
|
console.debug(utils.asError(whatToPrint))
|
||||||
},
|
},
|
||||||
info(whatToPrint: string): void {
|
info(whatToPrint: string): void {
|
||||||
console.log(false)
|
console.log(false)
|
||||||
@@ -357,7 +357,7 @@ export const polyfillEffects = (
|
|||||||
})
|
})
|
||||||
|
|
||||||
spawned.stderr.on("data", (data: unknown) => {
|
spawned.stderr.on("data", (data: unknown) => {
|
||||||
console.error(String(data))
|
console.error(`polyfill.runAsync`, utils.asError(data))
|
||||||
})
|
})
|
||||||
|
|
||||||
const id = async () => {
|
const id = async () => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import matches, { any, number, object, string, tuple } from "ts-matches"
|
|||||||
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"
|
||||||
import { T } from "@start9labs/start-sdk"
|
import { T, utils } from "@start9labs/start-sdk"
|
||||||
import { Volume } from "../../Models/Volume"
|
import { Volume } from "../../Models/Volume"
|
||||||
import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk"
|
import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk"
|
||||||
import { CallbackHolder } from "../../Models/CallbackHolder"
|
import { CallbackHolder } from "../../Models/CallbackHolder"
|
||||||
@@ -57,7 +57,9 @@ export class SystemForStartOs implements System {
|
|||||||
if (this.runningMain) {
|
if (this.runningMain) {
|
||||||
this.runningMain.callbacks
|
this.runningMain.callbacks
|
||||||
.callCallback(callback, args)
|
.callCallback(callback, args)
|
||||||
.catch((error) => console.error(`callback ${callback} failed`, error))
|
.catch((error) =>
|
||||||
|
console.error(`callback ${callback} failed`, utils.asError(error)),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
console.warn(`callback ${callback} ignored because system is not running`)
|
console.warn(`callback ${callback} ignored because system is not running`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub fn chroot<C: Context>(
|
|||||||
args,
|
args,
|
||||||
}: ChrootParams,
|
}: ChrootParams,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut cmd = std::process::Command::new(command);
|
let mut cmd: std::process::Command = std::process::Command::new(command);
|
||||||
if let Some(env) = env {
|
if let Some(env) = env {
|
||||||
for (k, v) in std::fs::read_to_string(env)?
|
for (k, v) in std::fs::read_to_string(env)?
|
||||||
.lines()
|
.lines()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as T from "../types"
|
|||||||
|
|
||||||
import * as child_process from "child_process"
|
import * as child_process from "child_process"
|
||||||
import { promises as fsPromises } from "fs"
|
import { promises as fsPromises } from "fs"
|
||||||
|
import { asError } from "../util"
|
||||||
|
|
||||||
export type BACKUP = "BACKUP"
|
export type BACKUP = "BACKUP"
|
||||||
export const DEFAULT_OPTIONS: T.BackupOptions = {
|
export const DEFAULT_OPTIONS: T.BackupOptions = {
|
||||||
@@ -183,7 +184,7 @@ async function runRsync(
|
|||||||
})
|
})
|
||||||
|
|
||||||
spawned.stderr.on("data", (data: unknown) => {
|
spawned.stderr.on("data", (data: unknown) => {
|
||||||
console.error(String(data))
|
console.error(`Backups.runAsync`, asError(data))
|
||||||
})
|
})
|
||||||
|
|
||||||
const id = async () => {
|
const id = async () => {
|
||||||
|
|||||||
@@ -57,7 +57,9 @@ export function setupConfig<
|
|||||||
return {
|
return {
|
||||||
setConfig: (async ({ effects, input }) => {
|
setConfig: (async ({ effects, input }) => {
|
||||||
if (!validator.test(input)) {
|
if (!validator.test(input)) {
|
||||||
await console.error(String(validator.errorMessage(input)))
|
await console.error(
|
||||||
|
new Error(validator.errorMessage(input)?.toString()),
|
||||||
|
)
|
||||||
return { error: "Set config type error for config" }
|
return { error: "Set config type error for config" }
|
||||||
}
|
}
|
||||||
await effects.clearBindings()
|
await effects.clearBindings()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { once } from "../util/once"
|
|||||||
import { Overlay } from "../util/Overlay"
|
import { Overlay } from "../util/Overlay"
|
||||||
import { object, unknown } from "ts-matches"
|
import { object, unknown } from "ts-matches"
|
||||||
import * as T from "../types"
|
import * as T from "../types"
|
||||||
|
import { asError } from "../util/asError"
|
||||||
|
|
||||||
export type HealthCheckParams = {
|
export type HealthCheckParams = {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
@@ -44,7 +45,7 @@ export function healthCheck(o: HealthCheckParams) {
|
|||||||
})
|
})
|
||||||
currentValue.lastResult = result
|
currentValue.lastResult = result
|
||||||
await triggerFirstSuccess().catch((err) => {
|
await triggerFirstSuccess().catch((err) => {
|
||||||
console.error(err)
|
console.error(asError(err))
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await o.effects.setHealth({
|
await o.effects.setHealth({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Effects } from "../../types"
|
import { Effects } from "../../types"
|
||||||
|
import { asError } from "../../util/asError"
|
||||||
import { HealthCheckResult } from "./HealthCheckResult"
|
import { HealthCheckResult } from "./HealthCheckResult"
|
||||||
import { timeoutPromise } from "./index"
|
import { timeoutPromise } from "./index"
|
||||||
import "isomorphic-fetch"
|
import "isomorphic-fetch"
|
||||||
@@ -29,7 +30,7 @@ export const checkWebUrl = async (
|
|||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.warn(`Error while fetching URL: ${url}`)
|
console.warn(`Error while fetching URL: ${url}`)
|
||||||
console.error(JSON.stringify(e))
|
console.error(JSON.stringify(e))
|
||||||
console.error(e.toString())
|
console.error(asError(e))
|
||||||
return { result: "failure" as const, message: errorMessage }
|
return { result: "failure" as const, message: errorMessage }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,20 @@ import { DEFAULT_SIGTERM_TIMEOUT } from "."
|
|||||||
import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../StartSdk"
|
import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../StartSdk"
|
||||||
|
|
||||||
import * as T from "../types"
|
import * as T from "../types"
|
||||||
import { MountOptions, Overlay } from "../util/Overlay"
|
import { asError } from "../util/asError"
|
||||||
|
import {
|
||||||
|
ExecSpawnable,
|
||||||
|
MountOptions,
|
||||||
|
NonDestroyableOverlay,
|
||||||
|
Overlay,
|
||||||
|
} from "../util/Overlay"
|
||||||
import { splitCommand } from "../util/splitCommand"
|
import { splitCommand } from "../util/splitCommand"
|
||||||
import { cpExecFile, cpExec } from "./Daemons"
|
import { cpExecFile, cpExec } from "./Daemons"
|
||||||
|
|
||||||
export class CommandController {
|
export class CommandController {
|
||||||
private constructor(
|
private constructor(
|
||||||
readonly runningAnswer: Promise<unknown>,
|
readonly runningAnswer: Promise<unknown>,
|
||||||
readonly overlay: Overlay,
|
private readonly overlay: ExecSpawnable,
|
||||||
readonly pid: number | undefined,
|
readonly pid: number | undefined,
|
||||||
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
||||||
) {}
|
) {}
|
||||||
@@ -25,7 +31,7 @@ export class CommandController {
|
|||||||
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
|
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
|
||||||
sigtermTimeout?: number
|
sigtermTimeout?: number
|
||||||
mounts?: { path: string; options: MountOptions }[]
|
mounts?: { path: string; options: MountOptions }[]
|
||||||
overlay?: Overlay
|
overlay?: ExecSpawnable
|
||||||
env?:
|
env?:
|
||||||
| {
|
| {
|
||||||
[variable: string]: string
|
[variable: string]: string
|
||||||
@@ -38,10 +44,15 @@ export class CommandController {
|
|||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const commands = splitCommand(command)
|
const commands = splitCommand(command)
|
||||||
const overlay = options.overlay || (await Overlay.of(effects, imageId))
|
const overlay =
|
||||||
for (let mount of options.mounts || []) {
|
options.overlay ||
|
||||||
await overlay.mount(mount.options, mount.path)
|
(await (async () => {
|
||||||
}
|
const overlay = await Overlay.of(effects, imageId)
|
||||||
|
for (let mount of options.mounts || []) {
|
||||||
|
await overlay.mount(mount.options, mount.path)
|
||||||
|
}
|
||||||
|
return overlay
|
||||||
|
})())
|
||||||
const childProcess = await overlay.spawn(commands, {
|
const childProcess = await overlay.spawn(commands, {
|
||||||
env: options.env,
|
env: options.env,
|
||||||
})
|
})
|
||||||
@@ -57,7 +68,7 @@ export class CommandController {
|
|||||||
"data",
|
"data",
|
||||||
options.onStderr ??
|
options.onStderr ??
|
||||||
((data: any) => {
|
((data: any) => {
|
||||||
console.error(data.toString())
|
console.error(asError(data))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,7 +85,10 @@ export class CommandController {
|
|||||||
return new CommandController(answer, overlay, pid, options.sigtermTimeout)
|
return new CommandController(answer, overlay, pid, options.sigtermTimeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async wait(timeout: number = NO_TIMEOUT) {
|
get nonDestroyableOverlay() {
|
||||||
|
return new NonDestroyableOverlay(this.overlay)
|
||||||
|
}
|
||||||
|
async wait({ timeout = NO_TIMEOUT } = {}) {
|
||||||
if (timeout > 0)
|
if (timeout > 0)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.term()
|
this.term()
|
||||||
@@ -87,7 +101,7 @@ export class CommandController {
|
|||||||
(_) => {},
|
(_) => {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
await this.overlay.destroy().catch((_) => {})
|
await this.overlay.destroy?.().catch((_) => {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
||||||
@@ -106,7 +120,7 @@ export class CommandController {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await this.overlay.destroy()
|
await this.overlay.destroy?.()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as T from "../types"
|
import * as T from "../types"
|
||||||
import { MountOptions, Overlay } from "../util/Overlay"
|
import { asError } from "../util/asError"
|
||||||
|
import { ExecSpawnable, MountOptions, Overlay } from "../util/Overlay"
|
||||||
import { CommandController } from "./CommandController"
|
import { CommandController } from "./CommandController"
|
||||||
|
|
||||||
const TIMEOUT_INCREMENT_MS = 1000
|
const TIMEOUT_INCREMENT_MS = 1000
|
||||||
@@ -12,7 +13,10 @@ const MAX_TIMEOUT_MS = 30000
|
|||||||
export class Daemon {
|
export class Daemon {
|
||||||
private commandController: CommandController | null = null
|
private commandController: CommandController | null = null
|
||||||
private shouldBeRunning = false
|
private shouldBeRunning = false
|
||||||
private constructor(private startCommand: () => Promise<CommandController>) {}
|
constructor(private startCommand: () => Promise<CommandController>) {}
|
||||||
|
get overlay(): undefined | ExecSpawnable {
|
||||||
|
return this.commandController?.nonDestroyableOverlay
|
||||||
|
}
|
||||||
static of<Manifest extends T.Manifest>() {
|
static of<Manifest extends T.Manifest>() {
|
||||||
return async <A extends string>(
|
return async <A extends string>(
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
@@ -41,7 +45,6 @@ export class Daemon {
|
|||||||
return new Daemon(startCommand)
|
return new Daemon(startCommand)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
if (this.commandController) {
|
if (this.commandController) {
|
||||||
return
|
return
|
||||||
@@ -57,7 +60,7 @@ export class Daemon {
|
|||||||
timeoutCounter = Math.max(MAX_TIMEOUT_MS, timeoutCounter)
|
timeoutCounter = Math.max(MAX_TIMEOUT_MS, timeoutCounter)
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error(err)
|
console.error(asError(err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
async term(termOptions?: {
|
async term(termOptions?: {
|
||||||
@@ -72,8 +75,8 @@ export class Daemon {
|
|||||||
}) {
|
}) {
|
||||||
this.shouldBeRunning = false
|
this.shouldBeRunning = false
|
||||||
await this.commandController
|
await this.commandController
|
||||||
?.term(termOptions)
|
?.term({ ...termOptions })
|
||||||
.catch((e) => console.error(e))
|
.catch((e) => console.error(asError(e)))
|
||||||
this.commandController = null
|
this.commandController = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Ready } from "./Daemons"
|
|||||||
import { Daemon } from "./Daemon"
|
import { Daemon } from "./Daemon"
|
||||||
import { Effects, SetHealth } from "../types"
|
import { Effects, SetHealth } from "../types"
|
||||||
import { DEFAULT_SIGTERM_TIMEOUT } from "."
|
import { DEFAULT_SIGTERM_TIMEOUT } from "."
|
||||||
|
import { asError } from "../util/asError"
|
||||||
|
|
||||||
const oncePromise = <T>() => {
|
const oncePromise = <T>() => {
|
||||||
let resolve: (value: T) => void
|
let resolve: (value: T) => void
|
||||||
@@ -21,13 +22,13 @@ const oncePromise = <T>() => {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export class HealthDaemon {
|
export class HealthDaemon {
|
||||||
#health: HealthCheckResult = { result: "starting", message: null }
|
private _health: HealthCheckResult = { result: "starting", message: null }
|
||||||
#healthWatchers: Array<() => unknown> = []
|
private healthWatchers: Array<() => unknown> = []
|
||||||
#running = false
|
private running = false
|
||||||
constructor(
|
constructor(
|
||||||
readonly daemon: Promise<Daemon>,
|
private readonly daemon: Promise<Daemon>,
|
||||||
readonly daemonIndex: number,
|
readonly daemonIndex: number,
|
||||||
readonly dependencies: HealthDaemon[],
|
private readonly dependencies: HealthDaemon[],
|
||||||
readonly id: string,
|
readonly id: string,
|
||||||
readonly ids: string[],
|
readonly ids: string[],
|
||||||
readonly ready: Ready,
|
readonly ready: Ready,
|
||||||
@@ -43,12 +44,12 @@ export class HealthDaemon {
|
|||||||
signal?: NodeJS.Signals | undefined
|
signal?: NodeJS.Signals | undefined
|
||||||
timeout?: number | undefined
|
timeout?: number | undefined
|
||||||
}) {
|
}) {
|
||||||
this.#healthWatchers = []
|
this.healthWatchers = []
|
||||||
this.#running = false
|
this.running = false
|
||||||
this.#healthCheckCleanup?.()
|
this.healthCheckCleanup?.()
|
||||||
|
|
||||||
await this.daemon.then((d) =>
|
await this.daemon.then((d) =>
|
||||||
d.stop({
|
d.term({
|
||||||
timeout: this.sigtermTimeout,
|
timeout: this.sigtermTimeout,
|
||||||
...termOptions,
|
...termOptions,
|
||||||
}),
|
}),
|
||||||
@@ -57,17 +58,17 @@ export class HealthDaemon {
|
|||||||
|
|
||||||
/** Want to add another notifier that the health might have changed */
|
/** Want to add another notifier that the health might have changed */
|
||||||
addWatcher(watcher: () => unknown) {
|
addWatcher(watcher: () => unknown) {
|
||||||
this.#healthWatchers.push(watcher)
|
this.healthWatchers.push(watcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
get health() {
|
get health() {
|
||||||
return Object.freeze(this.#health)
|
return Object.freeze(this._health)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async changeRunning(newStatus: boolean) {
|
private async changeRunning(newStatus: boolean) {
|
||||||
if (this.#running === newStatus) return
|
if (this.running === newStatus) return
|
||||||
|
|
||||||
this.#running = newStatus
|
this.running = newStatus
|
||||||
|
|
||||||
if (newStatus) {
|
if (newStatus) {
|
||||||
;(await this.daemon).start()
|
;(await this.daemon).start()
|
||||||
@@ -80,14 +81,14 @@ export class HealthDaemon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#healthCheckCleanup: (() => void) | null = null
|
private healthCheckCleanup: (() => void) | null = null
|
||||||
private turnOffHealthCheck() {
|
private turnOffHealthCheck() {
|
||||||
this.#healthCheckCleanup?.()
|
this.healthCheckCleanup?.()
|
||||||
}
|
}
|
||||||
private async setupHealthCheck() {
|
private async setupHealthCheck() {
|
||||||
if (this.#healthCheckCleanup) return
|
if (this.healthCheckCleanup) return
|
||||||
const trigger = (this.ready.trigger ?? defaultTrigger)(() => ({
|
const trigger = (this.ready.trigger ?? defaultTrigger)(() => ({
|
||||||
lastResult: this.#health.result,
|
lastResult: this._health.result,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const { promise: status, resolve: setStatus } = oncePromise<{
|
const { promise: status, resolve: setStatus } = oncePromise<{
|
||||||
@@ -102,7 +103,7 @@ export class HealthDaemon {
|
|||||||
const response: HealthCheckResult = await Promise.resolve(
|
const response: HealthCheckResult = await Promise.resolve(
|
||||||
this.ready.fn(),
|
this.ready.fn(),
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
console.error(err)
|
console.error(asError(err))
|
||||||
return {
|
return {
|
||||||
result: "failure",
|
result: "failure",
|
||||||
message: "message" in err ? err.message : String(err),
|
message: "message" in err ? err.message : String(err),
|
||||||
@@ -112,15 +113,15 @@ export class HealthDaemon {
|
|||||||
}
|
}
|
||||||
}).catch((err) => console.error(`Daemon ${this.id} failed: ${err}`))
|
}).catch((err) => console.error(`Daemon ${this.id} failed: ${err}`))
|
||||||
|
|
||||||
this.#healthCheckCleanup = () => {
|
this.healthCheckCleanup = () => {
|
||||||
setStatus({ done: true })
|
setStatus({ done: true })
|
||||||
this.#healthCheckCleanup = null
|
this.healthCheckCleanup = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setHealth(health: HealthCheckResult) {
|
private async setHealth(health: HealthCheckResult) {
|
||||||
this.#health = health
|
this._health = health
|
||||||
this.#healthWatchers.forEach((watcher) => watcher())
|
this.healthWatchers.forEach((watcher) => watcher())
|
||||||
const display = this.ready.display
|
const display = this.ready.display
|
||||||
const result = health.result
|
const result = health.result
|
||||||
if (!display) {
|
if (!display) {
|
||||||
@@ -134,7 +135,7 @@ export class HealthDaemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async updateStatus() {
|
private async updateStatus() {
|
||||||
const healths = this.dependencies.map((d) => d.#health)
|
const healths = this.dependencies.map((d) => d._health)
|
||||||
this.changeRunning(healths.every((x) => x.result === "success"))
|
this.changeRunning(healths.every((x) => x.result === "success"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { asError } from "../../util/asError"
|
||||||
|
|
||||||
const msb = 0x80
|
const msb = 0x80
|
||||||
const dropMsb = 0x7f
|
const dropMsb = 0x7f
|
||||||
const maxSize = Math.floor((8 * 8 + 7) / 7)
|
const maxSize = Math.floor((8 * 8 + 7) / 7)
|
||||||
@@ -38,7 +40,7 @@ export class VarIntProcessor {
|
|||||||
if (success) {
|
if (success) {
|
||||||
return result
|
return result
|
||||||
} else {
|
} else {
|
||||||
console.error(this.buf)
|
console.error(asError(this.buf))
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,40 @@ import { promisify } from "util"
|
|||||||
import { Buffer } from "node:buffer"
|
import { Buffer } from "node:buffer"
|
||||||
export const execFile = promisify(cp.execFile)
|
export const execFile = promisify(cp.execFile)
|
||||||
const WORKDIR = (imageId: string) => `/media/startos/images/${imageId}/`
|
const WORKDIR = (imageId: string) => `/media/startos/images/${imageId}/`
|
||||||
export class Overlay {
|
|
||||||
|
type ExecResults = {
|
||||||
|
exitCode: number | null
|
||||||
|
exitSignal: NodeJS.Signals | null
|
||||||
|
stdout: string | Buffer
|
||||||
|
stderr: string | Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the type that is going to describe what an overlay could do. The main point of the
|
||||||
|
* overlay is to have commands that run in a chrooted environment. This is useful for running
|
||||||
|
* commands in a containerized environment. But, I wanted the destroy to sometimes be doable, for example the
|
||||||
|
* case where the overlay isn't owned by the process, the overlay shouldn't be destroyed.
|
||||||
|
*/
|
||||||
|
export interface ExecSpawnable {
|
||||||
|
get destroy(): undefined | (() => Promise<void>)
|
||||||
|
exec(
|
||||||
|
command: string[],
|
||||||
|
options?: CommandOptions,
|
||||||
|
timeoutMs?: number | null,
|
||||||
|
): Promise<ExecResults>
|
||||||
|
spawn(
|
||||||
|
command: string[],
|
||||||
|
options?: CommandOptions,
|
||||||
|
): Promise<cp.ChildProcessWithoutNullStreams>
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Want to limit what we can do in a container, so we want to launch a container with a specific image and the mounts.
|
||||||
|
*
|
||||||
|
* Implements:
|
||||||
|
* @see {@link ExecSpawnable}
|
||||||
|
*/
|
||||||
|
export class Overlay implements ExecSpawnable {
|
||||||
|
private destroyed = false
|
||||||
private constructor(
|
private constructor(
|
||||||
readonly effects: T.Effects,
|
readonly effects: T.Effects,
|
||||||
readonly imageId: T.ImageId,
|
readonly imageId: T.ImageId,
|
||||||
@@ -39,23 +72,6 @@ export class Overlay {
|
|||||||
return new Overlay(effects, id, rootfs, guid)
|
return new Overlay(effects, id, rootfs, guid)
|
||||||
}
|
}
|
||||||
|
|
||||||
static async with<T>(
|
|
||||||
effects: T.Effects,
|
|
||||||
image: { id: T.ImageId; sharedRun?: boolean },
|
|
||||||
mounts: { options: MountOptions; path: string }[],
|
|
||||||
fn: (overlay: Overlay) => Promise<T>,
|
|
||||||
): Promise<T> {
|
|
||||||
const overlay = await Overlay.of(effects, image)
|
|
||||||
try {
|
|
||||||
for (let mount of mounts) {
|
|
||||||
await overlay.mount(mount.options, mount.path)
|
|
||||||
}
|
|
||||||
return await fn(overlay)
|
|
||||||
} finally {
|
|
||||||
await overlay.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async mount(options: MountOptions, path: string): Promise<Overlay> {
|
async mount(options: MountOptions, path: string): Promise<Overlay> {
|
||||||
path = path.startsWith("/")
|
path = path.startsWith("/")
|
||||||
? `${this.rootfs}${path}`
|
? `${this.rootfs}${path}`
|
||||||
@@ -70,7 +86,7 @@ export class Overlay {
|
|||||||
|
|
||||||
await fs.mkdir(from, { recursive: true })
|
await fs.mkdir(from, { recursive: true })
|
||||||
await fs.mkdir(path, { recursive: true })
|
await fs.mkdir(path, { recursive: true })
|
||||||
await await execFile("mount", ["--bind", from, path])
|
await execFile("mount", ["--bind", from, path])
|
||||||
} else if (options.type === "assets") {
|
} else if (options.type === "assets") {
|
||||||
const subpath = options.subpath
|
const subpath = options.subpath
|
||||||
? options.subpath.startsWith("/")
|
? options.subpath.startsWith("/")
|
||||||
@@ -101,10 +117,14 @@ export class Overlay {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroy() {
|
get destroy() {
|
||||||
const imageId = this.imageId
|
return async () => {
|
||||||
const guid = this.guid
|
if (this.destroyed) return
|
||||||
await this.effects.destroyOverlayedImage({ guid })
|
this.destroyed = true
|
||||||
|
const imageId = this.imageId
|
||||||
|
const guid = this.guid
|
||||||
|
await this.effects.destroyOverlayedImage({ guid })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async exec(
|
async exec(
|
||||||
@@ -218,6 +238,32 @@ export class Overlay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take an overlay but remove the ability to add the mounts and the destroy function.
|
||||||
|
* Lets other functions, like health checks, to not destroy the parents.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class NonDestroyableOverlay implements ExecSpawnable {
|
||||||
|
constructor(private overlay: ExecSpawnable) {}
|
||||||
|
get destroy() {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(
|
||||||
|
command: string[],
|
||||||
|
options?: CommandOptions,
|
||||||
|
timeoutMs?: number | null,
|
||||||
|
): Promise<ExecResults> {
|
||||||
|
return this.overlay.exec(command, options, timeoutMs)
|
||||||
|
}
|
||||||
|
spawn(
|
||||||
|
command: string[],
|
||||||
|
options?: CommandOptions,
|
||||||
|
): Promise<cp.ChildProcessWithoutNullStreams> {
|
||||||
|
return this.overlay.spawn(command, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type CommandOptions = {
|
export type CommandOptions = {
|
||||||
env?: { [variable: string]: string }
|
env?: { [variable: string]: string }
|
||||||
cwd?: string
|
cwd?: string
|
||||||
|
|||||||
6
sdk/lib/util/asError.ts
Normal file
6
sdk/lib/util/asError.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const asError = (e: unknown) => {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
return new Error(e as any)
|
||||||
|
}
|
||||||
|
return new Error(`${e}`)
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import "./Overlay"
|
|||||||
import "./once"
|
import "./once"
|
||||||
|
|
||||||
export { GetServiceInterface, getServiceInterface } from "./getServiceInterface"
|
export { GetServiceInterface, getServiceInterface } from "./getServiceInterface"
|
||||||
|
export { asError } from "./asError"
|
||||||
export { getServiceInterfaces } from "./getServiceInterfaces"
|
export { getServiceInterfaces } from "./getServiceInterfaces"
|
||||||
export { addressHostToUrl } from "./getServiceInterface"
|
export { addressHostToUrl } from "./getServiceInterface"
|
||||||
export { hostnameInfoToAddress } from "./Hostname"
|
export { hostnameInfoToAddress } from "./Hostname"
|
||||||
|
|||||||
Reference in New Issue
Block a user