addHealthCheck instead of additionalHealthChecks for Daemons (#2962)

* addHealthCheck on Daemons

* fix bug that prevents domains without protocols from being deleted

* fixes from testing

* version bump

* add sdk version to UI

* fix useEntrypoint

* fix dependency health check error display

* minor fixes

* beta.29

* fixes from testing

* beta.30

* set /etc/os-release (#2918)

* remove check-monitor from kiosk (#2059)

* add units for progress (#2693)

* use new progress type

* alpha.7

* fix up pwa stuff

* fix wormhole-squashfs and prune boot (#2964)

* don't exit on expected errors

* use bash

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
Aiden McClelland
2025-06-17 23:50:01 +00:00
committed by GitHub
parent f5688e077a
commit 3ec4db0225
100 changed files with 846 additions and 757 deletions

View File

@@ -37,9 +37,7 @@ export type Ready = {
})
* ```
*/
fn: (
subcontainer: SubContainer<Manifest>,
) => Promise<HealthCheckResult> | HealthCheckResult
fn: () => Promise<HealthCheckResult> | HealthCheckResult
/**
* A duration in milliseconds to treat a failing health check as "starting"
*
@@ -65,30 +63,40 @@ export type ExecCommandOptions = {
onStderr?: (chunk: Buffer | string | any) => void
}
export type ExecFnOptions = {
export type ExecFnOptions<
Manifest extends T.SDKManifest,
C extends SubContainer<Manifest> | null,
> = {
fn: (
subcontainer: SubContainer<Manifest>,
subcontainer: C,
abort: AbortController,
) => Promise<ExecCommandOptions | null>
) => Promise<C extends null ? null : ExecCommandOptions | null>
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
sigtermTimeout?: number
}
export type DaemonCommandType = ExecCommandOptions | ExecFnOptions
export type DaemonCommandType<
Manifest extends T.SDKManifest,
C extends SubContainer<Manifest> | null,
> = ExecFnOptions<Manifest, C> | (C extends null ? never : ExecCommandOptions)
type NewDaemonParams<Manifest extends T.SDKManifest> = {
type NewDaemonParams<
Manifest extends T.SDKManifest,
C extends SubContainer<Manifest> | null,
> = {
/** What to run as the daemon: either an async fn or a commandline command to run in the subcontainer */
exec: DaemonCommandType | null
/** Information about the subcontainer in which the daemon runs */
subcontainer: SubContainer<Manifest>
exec: DaemonCommandType<Manifest, C>
/** The subcontainer in which the daemon runs */
subcontainer: C
}
type AddDaemonParams<
Manifest extends T.SDKManifest,
Ids extends string,
Id extends string,
C extends SubContainer<Manifest> | null,
> = (
| NewDaemonParams<Manifest>
| NewDaemonParams<Manifest, C>
| {
daemon: Daemon<Manifest>
}
@@ -102,8 +110,15 @@ type AddOneshotParams<
Manifest extends T.SDKManifest,
Ids extends string,
Id extends string,
> = NewDaemonParams<Manifest> & {
exec: DaemonCommandType
C extends SubContainer<Manifest> | null,
> = NewDaemonParams<Manifest, C> & {
exec: DaemonCommandType<Manifest, C>
/** An array of IDs of prior daemons whose successful initializations are required before this daemon will initialize */
requires: Exclude<Ids, Id>[]
}
type AddHealthCheckParams<Ids extends string, Id extends string> = {
ready: Ready
/** An array of IDs of prior daemons whose successful initializations are required before this daemon will initialize */
requires: Exclude<Ids, Id>[]
}
@@ -111,7 +126,7 @@ type AddOneshotParams<
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`
export const runCommand = <Manifest extends T.SDKManifest>() =>
CommandController.of<Manifest>()
CommandController.of<Manifest, SubContainer<Manifest>>()
/**
* A class for defining and controlling the service daemons
@@ -141,11 +156,12 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
{
private constructor(
readonly effects: T.Effects,
readonly started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>,
readonly started:
| ((onTerm: () => PromiseLike<void>) => PromiseLike<null>)
| null,
readonly daemons: Promise<Daemon<Manifest>>[],
readonly ids: Ids[],
readonly healthDaemons: HealthDaemon<Manifest>[],
readonly healthChecks: HealthCheck[],
) {}
/**
* Returns an empty new Daemons class with the provided inputSpec.
@@ -154,13 +170,18 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
*
* Daemons run in the order they are defined, with latter daemons being capable of
* depending on prior daemons
* @param options
*
* @param effects
*
* @param started
* @returns
*/
static of<Manifest extends T.SDKManifest>(options: {
effects: T.Effects
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>
healthChecks: HealthCheck[]
/**
* A closure to run once the system is launched. If you are in main, provide the `started` argument you receive from the function arguments
*/
started: ((onTerm: () => PromiseLike<void>) => PromiseLike<null>) | null
}) {
return new Daemons<Manifest, never>(
options.effects,
@@ -168,7 +189,6 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
[],
[],
[],
options.healthChecks,
)
}
/**
@@ -177,19 +197,19 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
* @param options
* @returns a new Daemons object
*/
addDaemon<Id extends string>(
addDaemon<Id extends string, C extends SubContainer<Manifest> | null>(
// prettier-ignore
id:
"" extends Id ? never :
ErrorDuplicateId<Id> extends Id ? never :
Id extends Ids ? ErrorDuplicateId<Id> :
Id,
options: AddDaemonParams<Manifest, Ids, Id>,
options: AddDaemonParams<Manifest, Ids, Id, C>,
) {
const daemon =
"daemon" in options
? Promise.resolve(options.daemon)
: Daemon.of<Manifest>()(
: Daemon.of<Manifest>()<C>(
this.effects,
options.subcontainer,
options.exec,
@@ -201,11 +221,10 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
.filter((x) => x >= 0)
.map((id) => this.healthDaemons[id]),
id,
this.ids,
options.ready,
this.effects,
)
const daemons = this.daemons.concat(daemon)
const daemons = [...this.daemons, daemon]
const ids = [...this.ids, id] as (Ids | Id)[]
const healthDaemons = [...this.healthDaemons, healthDaemon]
return new Daemons<Manifest, Ids | Id>(
@@ -214,7 +233,6 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
daemons,
ids,
healthDaemons,
this.healthChecks,
)
}
@@ -225,7 +243,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
* @param options
* @returns a new Daemons object
*/
addOneshot<Id extends string>(
addOneshot<Id extends string, C extends SubContainer<Manifest> | null>(
id: "" extends Id
? never
: ErrorDuplicateId<Id> extends Id
@@ -233,9 +251,9 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
: Id extends Ids
? ErrorDuplicateId<Id>
: Id,
options: AddOneshotParams<Manifest, Ids, Id>,
options: AddOneshotParams<Manifest, Ids, Id, C>,
) {
const daemon = Oneshot.of<Manifest>()(
const daemon = Oneshot.of<Manifest>()<C>(
this.effects,
options.subcontainer,
options.exec,
@@ -247,11 +265,10 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
.filter((x) => x >= 0)
.map((id) => this.healthDaemons[id]),
id,
this.ids,
"EXIT_SUCCESS",
this.effects,
)
const daemons = this.daemons.concat(daemon)
const daemons = [...this.daemons, daemon]
const ids = [...this.ids, id] as (Ids | Id)[]
const healthDaemons = [...this.healthDaemons, healthDaemon]
return new Daemons<Manifest, Ids | Id>(
@@ -260,13 +277,95 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
daemons,
ids,
healthDaemons,
this.healthChecks,
)
}
/**
* Returns the complete list of daemons, including a new HealthCheck defined here
* @param id
* @param options
* @returns a new Daemons object
*/
addHealthCheck<Id extends string>(
id: "" extends Id
? never
: ErrorDuplicateId<Id> extends Id
? never
: Id extends Ids
? ErrorDuplicateId<Id>
: Id,
options: AddHealthCheckParams<Ids, Id>,
) {
const healthDaemon = new HealthDaemon<Manifest>(
null,
options.requires
.map((x) => this.ids.indexOf(x))
.filter((x) => x >= 0)
.map((id) => this.healthDaemons[id]),
id,
options.ready,
this.effects,
)
const daemons = [...this.daemons]
const ids = [...this.ids, id] as (Ids | Id)[]
const healthDaemons = [...this.healthDaemons, healthDaemon]
return new Daemons<Manifest, Ids | Id>(
this.effects,
this.started,
daemons,
ids,
healthDaemons,
)
}
/**
* Runs the entire system until all daemons have returned `ready`.
* @param id
* @param options
* @returns a new Daemons object
*/
async runUntilSuccess(timeout: number | null) {
let resolve = (_: void) => {}
const res = new Promise<void>((res, rej) => {
resolve = res
if (timeout)
setTimeout(() => {
const notReady = this.healthDaemons
.filter((d) => !d.isReady)
.map((d) => d.id)
rej(new Error(`Timed out waiting for ${notReady}`))
}, timeout)
})
const daemon = Oneshot.of()(this.effects, null, {
fn: async () => {
resolve()
return null
},
})
const healthDaemon = new HealthDaemon<Manifest>(
daemon,
[...this.healthDaemons],
"__RUN_UNTIL_SUCCESS",
"EXIT_SUCCESS",
this.effects,
)
const daemons = await new Daemons<Manifest, Ids>(
this.effects,
this.started,
[...this.daemons, daemon],
this.ids,
[...this.healthDaemons, healthDaemon],
).build()
try {
await res
} finally {
await daemons.term()
}
return null
}
async term() {
try {
this.healthChecks.forEach((health) => health.stop())
for (let result of await Promise.allSettled(
this.healthDaemons.map((x) => x.term()),
)) {
@@ -283,10 +382,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
for (const daemon of this.healthDaemons) {
await daemon.init()
}
for (const health of this.healthChecks) {
health.start()
}
this.started(() => this.term())
this.started?.(() => this.term())
return this
}
}