Refactor/sdk init (#2947)

* 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
This commit is contained in:
Aiden McClelland
2025-05-21 10:24:37 -06:00
committed by GitHub
parent 46fd01c264
commit 44560c8da8
237 changed files with 1827 additions and 98800 deletions

View File

@@ -2,11 +2,7 @@ import { DEFAULT_SIGTERM_TIMEOUT } from "."
import { NO_TIMEOUT, SIGTERM } from "../../../base/lib/types"
import * as T from "../../../base/lib/types"
import {
MountOptions,
SubContainerHandle,
SubContainer,
} from "../util/SubContainer"
import { MountOptions, SubContainer } from "../util/SubContainer"
import { Drop, splitCommand } from "../util"
import * as cp from "child_process"
import * as fs from "node:fs/promises"
@@ -44,7 +40,7 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
) => {
try {
let commands: string[]
if (command instanceof T.UseEntrypoint) {
if (T.isUseEntrypoint(command)) {
const imageMeta: T.ImageMetadata = await fs
.readFile(`/media/startos/images/${subcontainer.imageId}.json`, {
encoding: "utf8",
@@ -110,13 +106,10 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
}
}
}
get subContainerHandle() {
return new SubContainerHandle(this.subcontainer)
}
async wait({ timeout = NO_TIMEOUT, keepSubcontainer = false } = {}) {
async wait({ timeout = NO_TIMEOUT } = {}) {
if (timeout > 0)
setTimeout(() => {
this.term({ keepSubcontainer })
this.term()
}, timeout)
try {
return await this.runningAnswer
@@ -124,14 +117,10 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
if (!this.state.exited) {
this.process.kill("SIGKILL")
}
if (!keepSubcontainer) await this.subcontainer.destroy()
await this.subcontainer.destroy()
}
}
async term({
signal = SIGTERM,
timeout = this.sigtermTimeout,
keepSubcontainer = false,
} = {}) {
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
try {
if (!this.state.exited) {
if (signal !== "SIGKILL") {
@@ -148,10 +137,10 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
await this.runningAnswer
} finally {
if (!keepSubcontainer) await this.subcontainer.destroy()
await this.subcontainer.destroy()
}
}
onDrop(): void {
this.term({ keepSubcontainer: true }).catch(console.error)
this.term().catch(console.error)
}
}

View File

@@ -1,8 +1,13 @@
import * as T from "../../../base/lib/types"
import { asError } from "../../../base/lib/util/asError"
import { Drop } from "../util"
import { ExecSpawnable, SubContainer } from "../util/SubContainer"
import {
SubContainer,
SubContainerOwned,
SubContainerRc,
} from "../util/SubContainer"
import { CommandController } from "./CommandController"
import { Oneshot } from "./Oneshot"
const TIMEOUT_INCREMENT_MS = 1000
const MAX_TIMEOUT_MS = 30000
@@ -16,14 +21,15 @@ export class Daemon<Manifest extends T.SDKManifest> extends Drop {
private shouldBeRunning = false
protected exitedSuccess = false
protected constructor(
private subcontainer: SubContainer<Manifest>,
private startCommand: () => Promise<CommandController<Manifest>>,
readonly oneshot: boolean = false,
protected onExitSuccessFns: (() => void)[] = [],
) {
super()
}
get subContainerHandle(): undefined | ExecSpawnable {
return this.commandController?.subContainerHandle
isOneshot(): this is Oneshot<Manifest> {
return this.oneshot
}
static of<Manifest extends T.SDKManifest>() {
return async (
@@ -44,14 +50,15 @@ export class Daemon<Manifest extends T.SDKManifest> extends Drop {
sigtermTimeout?: number
},
) => {
if (subcontainer.isOwned()) subcontainer = subcontainer.rc()
const startCommand = () =>
CommandController.of<Manifest>()(
effects,
subcontainer,
subcontainer.rc(),
command,
options,
)
return new Daemon(startCommand)
return new Daemon(subcontainer, startCommand)
}
}
async start() {
@@ -65,11 +72,11 @@ export class Daemon<Manifest extends T.SDKManifest> extends Drop {
while (this.shouldBeRunning) {
if (this.commandController)
await this.commandController
.term({ keepSubcontainer: true })
.term({})
.catch((err) => console.error(err))
this.commandController = await this.startCommand()
if (
(await this.commandController.wait({ keepSubcontainer: true }).then(
(await this.commandController.wait().then(
(_) => true,
(err) => {
console.error(err)
@@ -112,6 +119,10 @@ export class Daemon<Manifest extends T.SDKManifest> extends Drop {
?.term({ ...termOptions })
.catch((e) => console.error(asError(e)))
this.commandController = null
await this.subcontainer.destroy()
}
subcontainerRc(): SubContainerRc<Manifest> {
return this.subcontainer.rc()
}
onDrop(): void {
this.stop().catch((e) => console.error(asError(e)))

View File

@@ -5,7 +5,7 @@ import { HealthCheckResult } from "../health/checkFns"
import { Trigger } from "../trigger"
import * as T from "../../../base/lib/types"
import { Mounts } from "./Mounts"
import { ExecSpawnable, MountOptions, SubContainer } from "../util/SubContainer"
import { MountOptions, SubContainer } from "../util/SubContainer"
import { promisify } from "node:util"
import * as CP from "node:child_process"
@@ -17,6 +17,7 @@ import { Daemon } from "./Daemon"
import { CommandController } from "./CommandController"
import { HealthCheck } from "../health/HealthCheck"
import { Oneshot } from "./Oneshot"
import { Manifest } from "../test/output.sdk"
export const cpExec = promisify(CP.exec)
export const cpExecFile = promisify(CP.execFile)
@@ -38,7 +39,7 @@ export type Ready = {
* ```
*/
fn: (
spawnable: ExecSpawnable,
subcontainer: SubContainer<Manifest>,
) => Promise<HealthCheckResult> | HealthCheckResult
/**
* A duration in milliseconds to treat a failing health check as "starting"
@@ -168,9 +169,14 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
const daemon =
"daemon" in options
? Promise.resolve(options.daemon)
: Daemon.of()(this.effects, options.subcontainer, options.command, {
...options,
})
: Daemon.of<Manifest>()(
this.effects,
options.subcontainer,
options.command,
{
...options,
},
)
const healthDaemon = new HealthDaemon(
daemon,
options.requires
@@ -212,7 +218,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
: Id,
options: AddOneshotParams<Manifest, Ids, Id>,
) {
const daemon = Oneshot.of()(
const daemon = Oneshot.of<Manifest>()(
this.effects,
options.subcontainer,
options.command,
@@ -220,7 +226,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
...options,
},
)
const healthDaemon = new HealthDaemon(
const healthDaemon = new HealthDaemon<Manifest>(
daemon,
options.requires
.map((x) => this.ids.indexOf(x))

View File

@@ -91,8 +91,9 @@ export class HealthDaemon<Manifest extends SDKManifest> {
}
private async setupHealthCheck() {
if (this.ready === "EXIT_SUCCESS") {
if (this.daemon instanceof Oneshot) {
this.daemon.onExitSuccess(() =>
const daemon = await this.daemon
if (daemon.isOneshot()) {
daemon.onExitSuccess(() =>
this.setHealth({ result: "success", message: null }),
)
}
@@ -113,9 +114,9 @@ export class HealthDaemon<Manifest extends SDKManifest> {
!res.done;
res = await Promise.race([status, trigger.next()])
) {
const handle = (await this.daemon).subContainerHandle
const handle = (await this.daemon).subcontainerRc()
if (handle) {
try {
const response: HealthCheckResult = await Promise.resolve(
this.ready.fn(handle),
).catch((err) => {
@@ -132,11 +133,8 @@ export class HealthDaemon<Manifest extends SDKManifest> {
this.resolveReady()
}
await this.setHealth(response)
} else {
await this.setHealth({
result: "failure",
message: "Daemon not running",
})
} finally {
await handle.destroy()
}
}
}).catch((err) => console.error(`Daemon ${this.id} failed: ${err}`))
@@ -164,7 +162,7 @@ export class HealthDaemon<Manifest extends SDKManifest> {
if (
result === "failure" &&
this.started &&
performance.now() - this.started <= (this.ready.gracePeriod ?? 5000)
performance.now() - this.started <= (this.ready.gracePeriod ?? 10_000)
)
result = "starting"
await this.effects.setHealth({

View File

@@ -1,5 +1,5 @@
import * as T from "../../../base/lib/types"
import { SubContainer } from "../util/SubContainer"
import { SubContainer, SubContainerOwned } from "../util/SubContainer"
import { CommandController } from "./CommandController"
import { Daemon } from "./Daemon"
@@ -28,14 +28,15 @@ export class Oneshot<Manifest extends T.SDKManifest> extends Daemon<Manifest> {
sigtermTimeout?: number
},
) => {
if (subcontainer.isOwned()) subcontainer = subcontainer.rc()
const startCommand = () =>
CommandController.of<Manifest>()(
effects,
subcontainer,
subcontainer.rc(),
command,
options,
)
return new Oneshot(startCommand, true, [])
return new Oneshot(subcontainer, startCommand, true, [])
}
}