mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
* fix typeo in patch db seed * show all registries in updates tab, fix required dependnecy display in marketplace, update browser tab title desc * always show pointer for version select * chore: fix comments * support html in action desc and marketplace long desc, only show qr in action res if qr is true * disable save if smtp creds not edited, show better smtp success message * dont dismiss login spinner until patchDB returns * feat: redesign of service dashboard and interface (#2946) * feat: redesign of service dashboard and interface * chore: comments * re-add setup complete * dibale launch UI when not running, re-style things, rename things * back to 1000 * fix clearnet docs link and require password retype in setup wiz * faster hint display * display dependency ID if title not available * fix migration * better init progress view * fix setup success page by providing VERSION and notifications page fixes * force uninstall from service error page, soft or hard * handle error state better * chore: fixed for install and setup wizards * chore: fix issues (#2949) * enable and disable kiosk mode * minor fixes * fix dependency mounts * dismissable tasks * provide replayId * default if health check success message is null * look for wifi interface too * dash for null user agent in sessions * add disk repair to diagnostic api --------- Co-authored-by: waterplea <alexander@inkin.ru> Co-authored-by: Aiden McClelland <me@drbonez.dev>
179 lines
5.2 KiB
TypeScript
179 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"],
|
|
filetype: "directory",
|
|
},
|
|
})
|
|
} 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?.()
|
|
}
|
|
}
|