mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
* fixes for main * refactor package initialization * fixes from testing * more fixes * beta.21 * do not use instanceof * closes #2921 * beta22 * allow disabling kiosk * migration * fix /etc/shadow * actionRequest -> task * beta.23
178 lines
5.2 KiB
TypeScript
178 lines
5.2 KiB
TypeScript
import * as fs from "fs/promises"
|
|
import * as cp from "child_process"
|
|
import { SubContainer, types as T } from "@start9labs/start-sdk"
|
|
import { promisify } from "util"
|
|
import { DockerProcedure, VolumeId } from "../../../Models/DockerProcedure"
|
|
import { Volume } from "./matchVolume"
|
|
import {
|
|
CommandOptions,
|
|
ExecOptions,
|
|
SubContainerOwned,
|
|
} from "@start9labs/start-sdk/package/lib/util/SubContainer"
|
|
import { Mounts } from "@start9labs/start-sdk/package/lib/mainFn/Mounts"
|
|
import { Manifest } from "@start9labs/start-sdk/base/lib/osBindings"
|
|
import { BackupEffects } from "@start9labs/start-sdk/package/lib/backup/Backups"
|
|
import { Drop } from "@start9labs/start-sdk/package/lib/util"
|
|
import { SDKManifest } from "@start9labs/start-sdk/base/lib/types"
|
|
export const exec = promisify(cp.exec)
|
|
export const execFile = promisify(cp.execFile)
|
|
|
|
export class DockerProcedureContainer extends Drop {
|
|
private constructor(
|
|
private readonly subcontainer: SubContainer<SDKManifest>,
|
|
) {
|
|
super()
|
|
}
|
|
|
|
static async of(
|
|
effects: T.Effects,
|
|
packageId: string,
|
|
data: DockerProcedure,
|
|
volumes: { [id: VolumeId]: Volume },
|
|
name: string,
|
|
options: { subcontainer?: SubContainer<SDKManifest> } = {},
|
|
) {
|
|
const subcontainer =
|
|
options?.subcontainer ??
|
|
(await DockerProcedureContainer.createSubContainer(
|
|
effects,
|
|
packageId,
|
|
data,
|
|
volumes,
|
|
name,
|
|
))
|
|
return new DockerProcedureContainer(subcontainer)
|
|
}
|
|
static async createSubContainer(
|
|
effects: T.Effects,
|
|
packageId: string,
|
|
data: DockerProcedure,
|
|
volumes: { [id: VolumeId]: Volume },
|
|
name: string,
|
|
) {
|
|
const subcontainer = await SubContainerOwned.of(
|
|
effects as BackupEffects,
|
|
{ imageId: data.image },
|
|
null,
|
|
name,
|
|
)
|
|
|
|
if (data.mounts) {
|
|
const mounts = data.mounts
|
|
for (const mount in mounts) {
|
|
const path = mounts[mount].startsWith("/")
|
|
? `${subcontainer.rootfs}${mounts[mount]}`
|
|
: `${subcontainer.rootfs}/${mounts[mount]}`
|
|
await fs.mkdir(path, { recursive: true })
|
|
const volumeMount = volumes[mount]
|
|
if (volumeMount.type === "data") {
|
|
await subcontainer.mount(
|
|
Mounts.of().mountVolume({
|
|
volumeId: mount,
|
|
subpath: null,
|
|
mountpoint: mounts[mount],
|
|
readonly: false,
|
|
}),
|
|
)
|
|
} else if (volumeMount.type === "assets") {
|
|
await subcontainer.mount(
|
|
Mounts.of().mountAssets({
|
|
subpath: mount,
|
|
mountpoint: mounts[mount],
|
|
}),
|
|
)
|
|
} else if (volumeMount.type === "certificate") {
|
|
const hostnames = [
|
|
`${packageId}.embassy`,
|
|
...new Set(
|
|
Object.values(
|
|
(
|
|
await effects.getHostInfo({
|
|
hostId: volumeMount["interface-id"],
|
|
})
|
|
)?.hostnameInfo || {},
|
|
)
|
|
.flatMap((h) => h)
|
|
.flatMap((h) => (h.kind === "onion" ? [h.hostname.value] : [])),
|
|
).values(),
|
|
]
|
|
const certChain = await effects.getSslCertificate({
|
|
hostnames,
|
|
})
|
|
const key = await effects.getSslKey({
|
|
hostnames,
|
|
})
|
|
await fs.writeFile(
|
|
`${path}/${volumeMount["interface-id"]}.cert.pem`,
|
|
certChain.join("\n"),
|
|
)
|
|
await fs.writeFile(
|
|
`${path}/${volumeMount["interface-id"]}.key.pem`,
|
|
key,
|
|
)
|
|
} else if (volumeMount.type === "pointer") {
|
|
await effects.mount({
|
|
location: path,
|
|
target: {
|
|
packageId: volumeMount["package-id"],
|
|
subpath: volumeMount.path,
|
|
readonly: volumeMount.readonly,
|
|
volumeId: volumeMount["volume-id"],
|
|
},
|
|
})
|
|
} else if (volumeMount.type === "backup") {
|
|
await subcontainer.mount(
|
|
Mounts.of().mountBackups({
|
|
subpath: null,
|
|
mountpoint: mounts[mount],
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
return subcontainer
|
|
}
|
|
|
|
async exec(
|
|
commands: string[],
|
|
options?: CommandOptions & ExecOptions,
|
|
timeoutMs?: number | null,
|
|
) {
|
|
try {
|
|
return await this.subcontainer.exec(commands, options, timeoutMs)
|
|
} finally {
|
|
await this.subcontainer.destroy?.()
|
|
}
|
|
}
|
|
|
|
async execFail(
|
|
commands: string[],
|
|
timeoutMs: number | null,
|
|
options?: CommandOptions & ExecOptions,
|
|
) {
|
|
try {
|
|
const res = await this.subcontainer.exec(commands, options, timeoutMs)
|
|
if (res.exitCode !== 0) {
|
|
const codeOrSignal =
|
|
res.exitCode !== null
|
|
? `code ${res.exitCode}`
|
|
: `signal ${res.exitSignal}`
|
|
throw new Error(
|
|
`Process exited with ${codeOrSignal}: ${res.stderr.toString()}`,
|
|
)
|
|
}
|
|
return res
|
|
} finally {
|
|
await this.subcontainer.destroy?.()
|
|
}
|
|
}
|
|
|
|
// async spawn(commands: string[]): Promise<cp.ChildProcess> {
|
|
// return await this.subcontainer.spawn(commands)
|
|
// }
|
|
|
|
onDrop(): void {
|
|
this.subcontainer.destroy?.()
|
|
}
|
|
}
|