mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
* bugfixes * update fe types * implement new registry types in marketplace and ui * fix marketplace types to have default params * add alt implementation toggle * merge cleanup * more cleanup and notes * fix build * cleanup sync with next/minor * add exver JS parser * parse ValidExVer to string * update types to interface * add VersionRange and comparative functions * Parse ExtendedVersion from string * add conjunction, disjunction, and inversion logic * consider flavor in satisfiedBy fn * consider prerelease for ordering * add compare fn for sorting * rename fns for consistency * refactoring * update compare fn to return null if flavors don't match * begin simplifying dependencies * under construction * wip * add dependency metadata to CurrentDependencyInfo * ditch inheritance for recursive VersionRange constructor. Recursive 'satisfiedBy' fn wip * preprocess manifest * misc fixes * use sdk version as osVersion in manifest * chore: Change the type to just validate and not generate all solutions. * add publishedAt * fix pegjs exports * integrate exver into sdk * misc fixes * complete satisfiedBy fn * refactor - use greaterThanOrEqual and lessThanOrEqual fns * fix tests * update dependency details * update types * remove interim types * rename alt implementation to flavor * cleanup os update * format exver.ts * add s9pk parsing endpoints * fix build * update to exver * exver and bug fixes * update static endpoints + cleanup * cleanup * update static proxy verification * make mocks more robust; fix dep icon fallback; cleanup * refactor alert versions and update fixtures * registry bugfixes * misc fixes * cleanup unused * convert patchdb ui seed to camelCase * update otherVersions type * change otherVersions: null to 'none' * refactor and complete feature * improve static endpoints * fix install params * mask systemd-networkd-wait-online * fix static file fetching * include non-matching versions in otherVersions * convert release notes to modal and clean up displayExver * alert for no other versions * Fix ack-instructions casing * fix indeterminate loader on service install --------- Co-authored-by: Aiden McClelland <me@drbonez.dev> Co-authored-by: Shadowy Super Coder <musashidisciple@proton.me> Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Co-authored-by: J H <dragondef@gmail.com> Co-authored-by: Matt Hill <mattnine@protonmail.com>
157 lines
4.1 KiB
TypeScript
157 lines
4.1 KiB
TypeScript
import { DEFAULT_SIGTERM_TIMEOUT } from "."
|
|
import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../StartSdk"
|
|
|
|
import * as T from "../types"
|
|
import { MountOptions, Overlay } from "../util/Overlay"
|
|
import { splitCommand } from "../util/splitCommand"
|
|
import { cpExecFile, cpExec } from "./Daemons"
|
|
|
|
export class CommandController {
|
|
private constructor(
|
|
readonly runningAnswer: Promise<unknown>,
|
|
readonly overlay: Overlay,
|
|
readonly pid: number | undefined,
|
|
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
|
) {}
|
|
static of<Manifest extends T.Manifest>() {
|
|
return async <A extends string>(
|
|
effects: T.Effects,
|
|
imageId: {
|
|
id: keyof Manifest["images"] & T.ImageId
|
|
sharedRun?: boolean
|
|
},
|
|
command: T.CommandType,
|
|
options: {
|
|
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
|
|
sigtermTimeout?: number
|
|
mounts?: { path: string; options: MountOptions }[]
|
|
overlay?: Overlay
|
|
env?:
|
|
| {
|
|
[variable: string]: string
|
|
}
|
|
| undefined
|
|
cwd?: string | undefined
|
|
user?: string | undefined
|
|
onStdout?: (x: Buffer) => void
|
|
onStderr?: (x: Buffer) => void
|
|
},
|
|
) => {
|
|
const commands = splitCommand(command)
|
|
const overlay = options.overlay || (await Overlay.of(effects, imageId))
|
|
for (let mount of options.mounts || []) {
|
|
await overlay.mount(mount.options, mount.path)
|
|
}
|
|
const childProcess = await overlay.spawn(commands, {
|
|
env: options.env,
|
|
})
|
|
const answer = new Promise<null>((resolve, reject) => {
|
|
childProcess.stdout.on(
|
|
"data",
|
|
options.onStdout ??
|
|
((data: any) => {
|
|
console.log(data.toString())
|
|
}),
|
|
)
|
|
childProcess.stderr.on(
|
|
"data",
|
|
options.onStderr ??
|
|
((data: any) => {
|
|
console.error(data.toString())
|
|
}),
|
|
)
|
|
|
|
childProcess.on("exit", (code: any) => {
|
|
if (code === 0) {
|
|
return resolve(null)
|
|
}
|
|
return reject(new Error(`${commands[0]} exited with code ${code}`))
|
|
})
|
|
})
|
|
|
|
const pid = childProcess.pid
|
|
|
|
return new CommandController(answer, overlay, pid, options.sigtermTimeout)
|
|
}
|
|
}
|
|
async wait(timeout: number = NO_TIMEOUT) {
|
|
if (timeout > 0)
|
|
setTimeout(() => {
|
|
this.term()
|
|
}, timeout)
|
|
try {
|
|
return await this.runningAnswer
|
|
} finally {
|
|
if (this.pid !== undefined) {
|
|
await cpExecFile("pkill", ["-9", "-s", String(this.pid)]).catch(
|
|
(_) => {},
|
|
)
|
|
}
|
|
await this.overlay.destroy().catch((_) => {})
|
|
}
|
|
}
|
|
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
|
if (this.pid === undefined) return
|
|
try {
|
|
await cpExecFile("pkill", [
|
|
`-${signal.replace("SIG", "")}`,
|
|
"-s",
|
|
String(this.pid),
|
|
])
|
|
|
|
const didTimeout = await waitSession(this.pid, timeout)
|
|
if (didTimeout) {
|
|
await cpExecFile("pkill", [`-9`, "-s", String(this.pid)]).catch(
|
|
(_) => {},
|
|
)
|
|
}
|
|
} finally {
|
|
await this.overlay.destroy()
|
|
}
|
|
}
|
|
}
|
|
|
|
function waitSession(
|
|
sid: number,
|
|
timeout = NO_TIMEOUT,
|
|
interval = 100,
|
|
): Promise<boolean> {
|
|
let nextInterval = interval * 2
|
|
if (timeout >= 0 && timeout < nextInterval) {
|
|
nextInterval = timeout
|
|
}
|
|
let nextTimeout = timeout
|
|
if (timeout > 0) {
|
|
if (timeout >= interval) {
|
|
nextTimeout -= interval
|
|
} else {
|
|
nextTimeout = 0
|
|
}
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
let next: NodeJS.Timeout | null = null
|
|
if (timeout !== 0) {
|
|
next = setTimeout(() => {
|
|
waitSession(sid, nextTimeout, nextInterval).then(resolve, reject)
|
|
}, interval)
|
|
}
|
|
cpExecFile("ps", [`--sid=${sid}`, "-o", "--pid="]).then(
|
|
(_) => {
|
|
if (timeout === 0) {
|
|
resolve(true)
|
|
}
|
|
},
|
|
(e) => {
|
|
if (next) {
|
|
clearTimeout(next)
|
|
}
|
|
if (typeof e === "object" && e && "code" in e && e.code) {
|
|
resolve(false)
|
|
} else {
|
|
reject(e)
|
|
}
|
|
},
|
|
)
|
|
})
|
|
}
|