mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
feat: Add the stop/start loop for the service
This commit is contained in:
@@ -86,10 +86,11 @@ export class MainLoop {
|
|||||||
|
|
||||||
public async clean(options?: { timeout?: number }) {
|
public async clean(options?: { timeout?: number }) {
|
||||||
const { mainEvent, healthLoops, propertiesEvent } = this
|
const { mainEvent, healthLoops, propertiesEvent } = this
|
||||||
|
const main = await mainEvent
|
||||||
delete this.mainEvent
|
delete this.mainEvent
|
||||||
delete this.healthLoops
|
delete this.healthLoops
|
||||||
delete this.propertiesEvent
|
delete this.propertiesEvent
|
||||||
if (mainEvent) await (await mainEvent).daemon.term()
|
if (mainEvent) await main?.daemon.term()
|
||||||
clearInterval(propertiesEvent)
|
clearInterval(propertiesEvent)
|
||||||
if (healthLoops) healthLoops.forEach((x) => clearInterval(x.interval))
|
if (healthLoops) healthLoops.forEach((x) => clearInterval(x.interval))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use std::{ffi::OsString, time::Instant};
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use clap::builder::{TypedValueParser, ValueParserFactory};
|
use clap::builder::{TypedValueParser, ValueParserFactory};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use imbl_value::json;
|
use imbl_value::{json, InternedString};
|
||||||
use models::{ActionId, HealthCheckId, ImageId, PackageId};
|
use models::{ActionId, HealthCheckId, ImageId, PackageId};
|
||||||
use patch_db::json_ptr::JsonPointer;
|
use patch_db::json_ptr::JsonPointer;
|
||||||
use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler};
|
use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler};
|
||||||
@@ -111,11 +111,15 @@ pub fn service_effect_handler() -> ParentHandler {
|
|||||||
.subcommand(
|
.subcommand(
|
||||||
"createOverlayedImage",
|
"createOverlayedImage",
|
||||||
from_fn_async(create_overlayed_image)
|
from_fn_async(create_overlayed_image)
|
||||||
.with_custom_display_fn::<AnyContext, _>(|_, path| {
|
.with_custom_display_fn::<AnyContext, _>(|_, (path, _)| {
|
||||||
Ok(println!("{}", path.display()))
|
Ok(println!("{}", path.display()))
|
||||||
})
|
})
|
||||||
.with_remote_cli::<ContainerCliContext>(),
|
.with_remote_cli::<ContainerCliContext>(),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"destroyOverlayedImage",
|
||||||
|
from_fn_async(destroy_overlayed_image).no_cli(),
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"getSslCertificate",
|
"getSslCertificate",
|
||||||
from_fn_async(get_ssl_certificate).no_cli(),
|
from_fn_async(get_ssl_certificate).no_cli(),
|
||||||
@@ -668,7 +672,32 @@ async fn set_health(context: EffectContext, params: SetHealth) -> Result<Value,
|
|||||||
.await?;
|
.await?;
|
||||||
Ok(json!(()))
|
Ok(json!(()))
|
||||||
}
|
}
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Parser)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[command(rename_all = "camelCase")]
|
||||||
|
pub struct DestroyOverlayedImageParams {
|
||||||
|
image_id: ImageId,
|
||||||
|
guid: InternedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn destroy_overlayed_image(
|
||||||
|
ctx: EffectContext,
|
||||||
|
DestroyOverlayedImageParams { image_id, guid }: DestroyOverlayedImageParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let ctx = ctx.deref()?;
|
||||||
|
if ctx
|
||||||
|
.persistent_container
|
||||||
|
.overlays
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.remove(&guid)
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
tracing::warn!("Could not find a guard to remove on the destroy overlayed image; assumming that it already is removed and will be skipping");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
#[derive(serde::Deserialize, serde::Serialize, Parser)]
|
#[derive(serde::Deserialize, serde::Serialize, Parser)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[command(rename_all = "camelCase")]
|
#[command(rename_all = "camelCase")]
|
||||||
@@ -680,10 +709,10 @@ pub struct CreateOverlayedImageParams {
|
|||||||
pub async fn create_overlayed_image(
|
pub async fn create_overlayed_image(
|
||||||
ctx: EffectContext,
|
ctx: EffectContext,
|
||||||
CreateOverlayedImageParams { image_id }: CreateOverlayedImageParams,
|
CreateOverlayedImageParams { image_id }: CreateOverlayedImageParams,
|
||||||
) -> Result<PathBuf, Error> {
|
) -> Result<(PathBuf, InternedString), Error> {
|
||||||
let ctx = ctx.deref()?;
|
let ctx = ctx.deref()?;
|
||||||
let path = Path::new("images")
|
let path = Path::new("images")
|
||||||
.join(&*ARCH)
|
.join(*ARCH)
|
||||||
.join(&image_id)
|
.join(&image_id)
|
||||||
.with_extension("squashfs");
|
.with_extension("squashfs");
|
||||||
if let Some(image) = ctx
|
if let Some(image) = ctx
|
||||||
@@ -730,7 +759,7 @@ pub async fn create_overlayed_image(
|
|||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.insert(guid.clone(), guard);
|
.insert(guid.clone(), guard);
|
||||||
Ok(container_mountpoint)
|
Ok((container_mountpoint, guid))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new(
|
Err(Error::new(
|
||||||
eyre!("image {image_id} not found in s9pk"),
|
eyre!("image {image_id} not found in s9pk"),
|
||||||
|
|||||||
@@ -276,7 +276,13 @@ export type Effects = {
|
|||||||
}): Promise<unknown>
|
}): Promise<unknown>
|
||||||
|
|
||||||
/** A low level api used by makeOverlay */
|
/** A low level api used by makeOverlay */
|
||||||
createOverlayedImage(options: { imageId: string }): Promise<string>
|
createOverlayedImage(options: { imageId: string }): Promise<[string, string]>
|
||||||
|
|
||||||
|
/** A low level api used by destroyOverlay + makeOverlay:destroy */
|
||||||
|
destroyOverlayedImage(options: {
|
||||||
|
imageId: string
|
||||||
|
guid: string
|
||||||
|
}): Promise<void>
|
||||||
|
|
||||||
/** Removes all network bindings */
|
/** Removes all network bindings */
|
||||||
clearBindings(): Promise<void>
|
clearBindings(): Promise<void>
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ export class Overlay {
|
|||||||
readonly effects: T.Effects,
|
readonly effects: T.Effects,
|
||||||
readonly imageId: string,
|
readonly imageId: string,
|
||||||
readonly rootfs: string,
|
readonly rootfs: string,
|
||||||
|
readonly guid: string,
|
||||||
) {}
|
) {}
|
||||||
static async of(effects: T.Effects, imageId: string) {
|
static async of(effects: T.Effects, imageId: string) {
|
||||||
const rootfs = await effects.createOverlayedImage({ imageId })
|
const [rootfs, guid] = await effects.createOverlayedImage({ imageId })
|
||||||
|
|
||||||
for (const dirPart of ["dev", "sys", "proc", "run"] as const) {
|
for (const dirPart of ["dev", "sys", "proc", "run"] as const) {
|
||||||
await fs.mkdir(`${rootfs}/${dirPart}`, { recursive: true })
|
await fs.mkdir(`${rootfs}/${dirPart}`, { recursive: true })
|
||||||
@@ -23,7 +24,7 @@ export class Overlay {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Overlay(effects, imageId, rootfs)
|
return new Overlay(effects, imageId, rootfs, guid)
|
||||||
}
|
}
|
||||||
|
|
||||||
async mount(options: MountOptions, path: string): Promise<Overlay> {
|
async mount(options: MountOptions, path: string): Promise<Overlay> {
|
||||||
@@ -51,8 +52,9 @@ export class Overlay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async destroy() {
|
async destroy() {
|
||||||
await execFile("umount", ["-R", this.rootfs])
|
const imageId = this.imageId
|
||||||
await fs.rm(this.rootfs, { recursive: true, force: true })
|
const guid = this.guid
|
||||||
|
await this.effects.destroyOverlayedImage({ imageId, guid })
|
||||||
}
|
}
|
||||||
|
|
||||||
async exec(
|
async exec(
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ export const createUtils = <
|
|||||||
console.error(data.toString())
|
console.error(data.toString())
|
||||||
})
|
})
|
||||||
|
|
||||||
childProcess.on("close", (code: any) => {
|
childProcess.on("exit", (code: any) => {
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
return resolve(null)
|
return resolve(null)
|
||||||
}
|
}
|
||||||
@@ -262,7 +262,7 @@ export const createUtils = <
|
|||||||
try {
|
try {
|
||||||
childProcess.kill(signal)
|
childProcess.kill(signal)
|
||||||
|
|
||||||
if (timeout <= NO_TIMEOUT) {
|
if (timeout > NO_TIMEOUT) {
|
||||||
const didTimeout = await Promise.race([
|
const didTimeout = await Promise.race([
|
||||||
new Promise((resolve) => setTimeout(resolve, timeout)).then(
|
new Promise((resolve) => setTimeout(resolve, timeout)).then(
|
||||||
() => true,
|
() => true,
|
||||||
@@ -270,6 +270,7 @@ export const createUtils = <
|
|||||||
answer.then(() => false),
|
answer.then(() => false),
|
||||||
])
|
])
|
||||||
if (didTimeout) childProcess.kill(SIGKILL)
|
if (didTimeout) childProcess.kill(SIGKILL)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
await answer
|
await answer
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user