From 9eff920989dc53550e14cbc2aeddad19edfbd90c Mon Sep 17 00:00:00 2001 From: Jade <2364004+Blu-J@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:46:10 -0600 Subject: [PATCH] Feat/logging (#2602) * wip: Working on something to help * chore: Add in some of the logging now * chore: fix the type to interned instead of id * wip * wip * chore: fix the logging by moving levels * Apply suggestions from code review * mount at machine id for journal * Persistant * limit log size * feat: Actually logging and mounting now * fix: Get the logs from the previous versions of the boot * Chore: Add the boot id --------- Co-authored-by: Aiden McClelland --- container-runtime/RPCSpec.md | 34 +++- container-runtime/deb-install.sh | 6 +- container-runtime/package.json | 2 +- core/startos/src/context/rpc.rs | 4 +- core/startos/src/logs.rs | 163 ++++++++++++------ core/startos/src/lxc/mod.rs | 125 ++++++++++---- core/startos/src/s9pk/v2/mod.rs | 2 +- core/startos/src/service/mod.rs | 58 ++++--- .../src/service/persistent_container.rs | 28 +-- core/startos/src/service/service_map.rs | 6 +- 10 files changed, 308 insertions(+), 120 deletions(-) diff --git a/container-runtime/RPCSpec.md b/container-runtime/RPCSpec.md index 679671614..fd1014add 100644 --- a/container-runtime/RPCSpec.md +++ b/container-runtime/RPCSpec.md @@ -3,38 +3,61 @@ ## Methods ### init + initialize runtime (mount `/proc`, `/sys`, `/dev`, and `/run` to each image in `/media/images`) called after os has mounted js and images to the container + #### args + `[]` + #### response + `null` ### exit + shutdown runtime + #### args + `[]` + #### response + `null` ### start + run main method if not already running + #### args + `[]` + #### response + `null` ### stop + stop main method by sending SIGTERM to child processes, and SIGKILL after timeout + #### args + `{ timeout: millis }` + #### response + `null` ### execute + run a specific package procedure -#### args + +#### args + ```ts { procedure: JsonPath, @@ -42,12 +65,17 @@ run a specific package procedure timeout: millis, } ``` + #### response + `any` ### sandbox + run a specific package procedure in sandbox mode -#### args + +#### args + ```ts { procedure: JsonPath, @@ -55,5 +83,7 @@ run a specific package procedure in sandbox mode timeout: millis, } ``` + #### response + `any` diff --git a/container-runtime/deb-install.sh b/container-runtime/deb-install.sh index 9a64dbbdd..b439c6308 100644 --- a/container-runtime/deb-install.sh +++ b/container-runtime/deb-install.sh @@ -11,9 +11,13 @@ apt-get install -y curl rsync curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc nvm install 20 - ln -s $(which node) /usr/bin/node +sed -i '/\(^\|#\)Storage=/c\Storage=persistent' /etc/systemd/journald.conf +sed -i '/\(^\|#\)Compress=/c\Compress=yes' /etc/systemd/journald.conf +sed -i '/\(^\|#\)SystemMaxUse=/c\SystemMaxUse=1G' /etc/systemd/journald.conf +sed -i '/\(^\|#\)ForwardToSyslog=/c\ForwardToSyslog=no' /etc/systemd/journald.conf + systemctl enable container-runtime.service rm -rf /run/systemd \ No newline at end of file diff --git a/container-runtime/package.json b/container-runtime/package.json index 2fa407408..13cf14ed5 100644 --- a/container-runtime/package.json +++ b/container-runtime/package.json @@ -5,7 +5,7 @@ "module": "./index.js", "scripts": { "check": "tsc --noEmit", - "build": "prettier --write '**/*.ts' && rm -rf dist && tsc", + "build": "prettier . '!tmp/**' --write && rm -rf dist && tsc", "tsc": "rm -rf dist; tsc" }, "author": "", diff --git a/core/startos/src/context/rpc.rs b/core/startos/src/context/rpc.rs index 5adab5e58..f2c859273 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -16,7 +16,6 @@ use tokio::time::Instant; use tracing::instrument; use super::setup::CURRENT_SECRET; -use crate::account::AccountInfo; use crate::context::config::ServerConfig; use crate::core::rpc_continuations::{RequestGuid, RestHandler, RpcContinuation, WebSocketHandler}; use crate::db::prelude::PatchDbExt; @@ -33,6 +32,7 @@ use crate::service::ServiceMap; use crate::shutdown::Shutdown; use crate::system::get_mem_info; use crate::util::lshw::{lshw, LshwDevice}; +use crate::{account::AccountInfo, lxc::ContainerId}; pub struct RpcContextSeed { is_closed: AtomicBool, @@ -60,7 +60,7 @@ pub struct RpcContextSeed { } pub struct Dev { - pub lxc: Mutex>, + pub lxc: Mutex>, } pub struct Hardware { diff --git a/core/startos/src/logs.rs b/core/startos/src/logs.rs index 1cd84c331..99bfac250 100644 --- a/core/startos/src/logs.rs +++ b/core/startos/src/logs.rs @@ -18,15 +18,21 @@ use tokio_stream::wrappers::LinesStream; use tokio_tungstenite::tungstenite::Message; use tracing::instrument; -use crate::context::{CliContext, RpcContext}; -use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::error::ResultExt; use crate::prelude::*; use crate::util::serde::Reversible; +use crate::{ + context::{CliContext, RpcContext}, + lxc::ContainerId, +}; +use crate::{ + core::rpc_continuations::{RequestGuid, RpcContinuation}, + util::Invoke, +}; #[pin_project::pin_project] pub struct LogStream { - _child: Child, + _child: Option, #[pin] entries: BoxStream<'static, Result>, } @@ -116,9 +122,11 @@ pub struct LogFollowResponse { } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] pub struct LogEntry { timestamp: DateTime, message: String, + boot_id: String, } impl std::fmt::Display for LogEntry { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { @@ -141,6 +149,8 @@ pub struct JournalctlEntry { pub message: String, #[serde(rename = "__CURSOR")] pub cursor: String, + #[serde(rename = "_BOOT_ID")] + pub boot_id: String, } impl JournalctlEntry { fn log_entry(self) -> Result<(String, LogEntry), Error> { @@ -151,6 +161,7 @@ impl JournalctlEntry { UNIX_EPOCH + Duration::from_micros(self.timestamp.parse::()?), ), message: self.message, + boot_id: self.boot_id, }, )) } @@ -200,12 +211,12 @@ fn deserialize_log_message<'de, D: serde::de::Deserializer<'de>>( /// --user-unit=UNIT Show logs from the specified user unit)) /// System: Unit is startd, but we also filter on the comm /// Container: Filtering containers, like podman/docker is done by filtering on the CONTAINER_NAME -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum LogSource { Kernel, Unit(&'static str), System, - Container(PackageId), + Container(ContainerId), } pub const SYSTEM_UNIT: &str = "startd"; @@ -276,7 +287,7 @@ pub async fn cli_logs( } } pub async fn logs_nofollow( - _ctx: (), + ctx: RpcContext, _: Empty, LogsParam { id, @@ -286,14 +297,38 @@ pub async fn logs_nofollow( .. }: LogsParam, ) -> Result { - fetch_logs(LogSource::Container(id), limit, cursor, before).await + let container_id = ctx + .services + .get(&id) + .await + .as_ref() + .map(|x| x.container_id()) + .ok_or_else(|| { + Error::new( + eyre!("No service found with id: {}", id), + ErrorKind::NotFound, + ) + })??; + fetch_logs(LogSource::Container(container_id), limit, cursor, before).await } pub async fn logs_follow( ctx: RpcContext, _: Empty, LogsParam { id, limit, .. }: LogsParam, ) -> Result { - follow_logs(ctx, LogSource::Container(id), limit).await + let container_id = ctx + .services + .get(&id) + .await + .as_ref() + .map(|x| x.container_id()) + .ok_or_else(|| { + Error::new( + eyre!("No service found with id: {}", id), + ErrorKind::NotFound, + ) + })??; + follow_logs(ctx, LogSource::Container(container_id), limit).await } pub async fn cli_logs_generic_nofollow( @@ -358,7 +393,74 @@ pub async fn journalctl( before: bool, follow: bool, ) -> Result { - let mut cmd = Command::new("journalctl"); + let mut cmd = gen_journalctl_command(&id, limit); + + let cursor_formatted = format!("--after-cursor={}", cursor.unwrap_or("")); + if cursor.is_some() { + cmd.arg(&cursor_formatted); + if before { + cmd.arg("--reverse"); + } + } + + let deserialized_entries = String::from_utf8(cmd.invoke(ErrorKind::Journald).await?)? + .lines() + .map(serde_json::from_str::) + .collect::, _>>() + .with_kind(ErrorKind::Deserialization)?; + + if follow { + let mut follow_cmd = gen_journalctl_command(&id, limit); + follow_cmd.arg("-f"); + if let Some(last) = deserialized_entries.last() { + cmd.arg(format!("--after-cursor={}", last.cursor)); + } + let mut child = cmd.stdout(Stdio::piped()).spawn()?; + let out = + BufReader::new(child.stdout.take().ok_or_else(|| { + Error::new(eyre!("No stdout available"), crate::ErrorKind::Journald) + })?); + + let journalctl_entries = LinesStream::new(out.lines()); + + let follow_deserialized_entries = journalctl_entries + .map_err(|e| Error::new(e, crate::ErrorKind::Journald)) + .and_then(|s| { + futures::future::ready( + serde_json::from_str::(&s) + .with_kind(crate::ErrorKind::Deserialization), + ) + }); + + let entries = futures::stream::iter(deserialized_entries) + .map(Ok) + .chain(follow_deserialized_entries) + .boxed(); + Ok(LogStream { + _child: Some(child), + entries, + }) + } else { + let entries = futures::stream::iter(deserialized_entries).map(Ok).boxed(); + + Ok(LogStream { + _child: None, + entries, + }) + } +} + +fn gen_journalctl_command(id: &LogSource, limit: usize) -> Command { + let mut cmd = match id { + LogSource::Container(container_id) => { + let mut cmd = Command::new("lxc-attach"); + cmd.arg(format!("{}", container_id)) + .arg("--") + .arg("journalctl"); + cmd + } + _ => Command::new("journalctl"), + }; cmd.kill_on_drop(true); cmd.arg("--output=json"); @@ -377,48 +479,11 @@ pub async fn journalctl( cmd.arg(SYSTEM_UNIT); cmd.arg(format!("_COMM={}", SYSTEM_UNIT)); } - LogSource::Container(id) => { - #[cfg(not(feature = "docker"))] - cmd.arg(format!("SYSLOG_IDENTIFIER={}.embassy", id)); - #[cfg(feature = "docker")] - cmd.arg(format!("CONTAINER_NAME={}.embassy", id)); + LogSource::Container(_container_id) => { + cmd.arg("-u").arg("container-runtime.service"); } }; - - let cursor_formatted = format!("--after-cursor={}", cursor.unwrap_or("")); - if cursor.is_some() { - cmd.arg(&cursor_formatted); - if before { - cmd.arg("--reverse"); - } - } - if follow { - cmd.arg("--follow"); - } - - let mut child = cmd.stdout(Stdio::piped()).spawn()?; - let out = BufReader::new( - child - .stdout - .take() - .ok_or_else(|| Error::new(eyre!("No stdout available"), crate::ErrorKind::Journald))?, - ); - - let journalctl_entries = LinesStream::new(out.lines()); - - let deserialized_entries = journalctl_entries - .map_err(|e| Error::new(e, crate::ErrorKind::Journald)) - .and_then(|s| { - futures::future::ready( - serde_json::from_str::(&s) - .with_kind(crate::ErrorKind::Deserialization), - ) - }); - - Ok(LogStream { - _child: child, - entries: deserialized_entries.boxed(), - }) + cmd } #[instrument(skip_all)] diff --git a/core/startos/src/lxc/mod.rs b/core/startos/src/lxc/mod.rs index 0ebefdd96..5a6e05500 100644 --- a/core/startos/src/lxc/mod.rs +++ b/core/startos/src/lxc/mod.rs @@ -5,9 +5,11 @@ use std::path::Path; use std::sync::{Arc, Weak}; use std::time::Duration; +use clap::builder::ValueParserFactory; use clap::Parser; use futures::{AsyncWriteExt, FutureExt, StreamExt}; use imbl_value::{InOMap, InternedString}; +use models::InvalidId; use rpc_toolkit::yajrc::{RpcError, RpcResponse}; use rpc_toolkit::{ from_fn_async, AnyContext, CallRemoteHandler, GenericRpcMethod, Handler, HandlerArgs, @@ -28,10 +30,11 @@ use crate::disk::mount::filesystem::bind::Bind; use crate::disk::mount::filesystem::block_dev::BlockDev; use crate::disk::mount::filesystem::idmapped::IdMapped; use crate::disk::mount::filesystem::overlayfs::OverlayGuard; -use crate::disk::mount::filesystem::ReadWrite; -use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard}; +use crate::disk::mount::filesystem::{MountType, ReadWrite}; +use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard}; use crate::disk::mount::util::unmount; use crate::prelude::*; +use crate::util::clap::FromStrParser; use crate::util::rpc_client::UnixRpcClient; use crate::util::{new_guid, Invoke}; @@ -41,18 +44,57 @@ pub const CONTAINER_RPC_SERVER_SOCKET: &str = "service.sock"; // must not be abs pub const HOST_RPC_SERVER_SOCKET: &str = "host.sock"; // must not be absolute path const CONTAINER_DHCP_TIMEOUT: Duration = Duration::from_secs(30); -pub struct LxcManager { - containers: Mutex>>, +#[derive( + Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord, Hash, TS, +)] +#[ts(type = "string")] +pub struct ContainerId(InternedString); +impl std::ops::Deref for ContainerId { + type Target = str; + fn deref(&self) -> &Self::Target { + &self.0 + } } +impl std::fmt::Display for ContainerId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &*self.0) + } +} +impl TryFrom<&str> for ContainerId { + type Error = InvalidId; + fn try_from(value: &str) -> Result { + Ok(ContainerId(InternedString::intern(value))) + } +} +impl std::str::FromStr for ContainerId { + type Err = InvalidId; + fn from_str(s: &str) -> Result { + Self::try_from(s) + } +} +impl ValueParserFactory for ContainerId { + type Parser = FromStrParser; + fn value_parser() -> Self::Parser { + FromStrParser::new() + } +} + +#[derive(Default)] +pub struct LxcManager { + containers: Mutex>>, +} + impl LxcManager { pub fn new() -> Self { - Self { - containers: Default::default(), - } + Self::default() } - pub async fn create(self: &Arc, config: LxcConfig) -> Result { - let container = LxcContainer::new(self, config).await?; + pub async fn create( + self: &Arc, + log_mount: Option<&Path>, + config: LxcConfig, + ) -> Result { + let container = LxcContainer::new(self, log_mount, config).await?; let mut guard = self.containers.lock().await; *guard = std::mem::take(&mut *guard) .into_iter() @@ -69,7 +111,7 @@ impl LxcManager { .await .iter() .filter_map(|g| g.upgrade()) - .map(|g| (&*g).clone()), + .map(|g| (*g).clone()), ); for container in String::from_utf8( Command::new("lxc-ls") @@ -80,7 +122,7 @@ impl LxcManager { .lines() .map(|s| s.trim()) { - if !expected.contains(container) { + if !expected.contains(&ContainerId::try_from(container)?) { let rootfs_path = Path::new(LXC_CONTAINER_DIR).join(container).join("rootfs"); if tokio::fs::metadata(&rootfs_path).await.is_ok() { unmount(Path::new(LXC_CONTAINER_DIR).join(container).join("rootfs")).await?; @@ -112,14 +154,20 @@ impl LxcManager { pub struct LxcContainer { manager: Weak, rootfs: OverlayGuard, - guid: Arc, + pub guid: Arc, rpc_bind: TmpMountGuard, + log_mount: Option, config: LxcConfig, exited: bool, } impl LxcContainer { - async fn new(manager: &Arc, config: LxcConfig) -> Result { + async fn new( + manager: &Arc, + log_mount: Option<&Path>, + config: LxcConfig, + ) -> Result { let guid = new_guid(); + let machine_id = hex::encode(rand::random::<[u8; 16]>()); let container_dir = Path::new(LXC_CONTAINER_DIR).join(&*guid); tokio::fs::create_dir_all(&container_dir).await?; tokio::fs::write( @@ -145,6 +193,7 @@ impl LxcContainer { &rootfs_dir, ) .await?; + tokio::fs::write(rootfs_dir.join("etc/machine-id"), format!("{machine_id}\n")).await?; tokio::fs::write(rootfs_dir.join("etc/hostname"), format!("{guid}\n")).await?; Command::new("sed") .arg("-i") @@ -166,6 +215,20 @@ impl LxcContainer { .arg(rpc_bind.path()) .invoke(ErrorKind::Filesystem) .await?; + let log_mount = if let Some(path) = log_mount { + let log_mount_point = rootfs_dir.join("var/log/journal").join(machine_id); + let log_mount = + MountGuard::mount(&Bind::new(path), &log_mount_point, MountType::ReadWrite).await?; + Command::new("chown") + // This was needed as 100999 because the group id of journald + .arg("100000:100999") + .arg(&log_mount_point) + .invoke(crate::ErrorKind::Filesystem) + .await?; + Some(log_mount) + } else { + None + }; Command::new("lxc-start") .arg("-d") .arg("--name") @@ -175,10 +238,11 @@ impl LxcContainer { Ok(Self { manager: Arc::downgrade(manager), rootfs, - guid: Arc::new(guid), + guid: Arc::new(ContainerId::try_from(&*guid)?), rpc_bind, config, exited: false, + log_mount, }) } @@ -188,11 +252,12 @@ impl LxcContainer { pub async fn ip(&self) -> Result { let start = Instant::now(); + let guid: &str = &self.guid; loop { let output = String::from_utf8( Command::new("lxc-info") .arg("--name") - .arg(&*self.guid) + .arg(guid) .arg("-iH") .invoke(ErrorKind::Docker) .await?, @@ -218,6 +283,9 @@ impl LxcContainer { #[instrument(skip_all)] pub async fn exit(mut self) -> Result<(), Error> { self.rpc_bind.take().unmount().await?; + if let Some(log_mount) = self.log_mount.take() { + log_mount.unmount(true).await?; + } self.rootfs.take().unmount(true).await?; let rootfs_path = self.rootfs_dir(); let err_path = rootfs_path.join("var/log/containerRuntime.err"); @@ -228,17 +296,16 @@ impl LxcContainer { tracing::error!(container, "{}", line); } } - if tokio::fs::metadata(&rootfs_path).await.is_ok() { - if tokio_stream::wrappers::ReadDirStream::new(tokio::fs::read_dir(&rootfs_path).await?) + if tokio::fs::metadata(&rootfs_path).await.is_ok() + && tokio_stream::wrappers::ReadDirStream::new(tokio::fs::read_dir(&rootfs_path).await?) .count() .await > 0 - { - return Err(Error::new( - eyre!("rootfs is not empty, refusing to delete"), - ErrorKind::InvalidRequest, - )); - } + { + return Err(Error::new( + eyre!("rootfs is not empty, refusing to delete"), + ErrorKind::InvalidRequest, + )); } Command::new("lxc-destroy") .arg("--force") @@ -341,21 +408,21 @@ pub fn lxc() -> ParentHandler { .subcommand("connect", from_fn_async(connect_rpc_cli).no_display()) } -pub async fn create(ctx: RpcContext) -> Result { - let container = ctx.lxc_manager.create(LxcConfig::default()).await?; +pub async fn create(ctx: RpcContext) -> Result { + let container = ctx.lxc_manager.create(None, LxcConfig::default()).await?; let guid = container.guid.deref().clone(); ctx.dev.lxc.lock().await.insert(guid.clone(), container); Ok(guid) } -pub async fn list(ctx: RpcContext) -> Result, Error> { +pub async fn list(ctx: RpcContext) -> Result, Error> { Ok(ctx.dev.lxc.lock().await.keys().cloned().collect()) } #[derive(Deserialize, Serialize, Parser, TS)] pub struct RemoveParams { #[ts(type = "string")] - pub guid: InternedString, + pub guid: ContainerId, } pub async fn remove(ctx: RpcContext, RemoveParams { guid }: RemoveParams) -> Result<(), Error> { @@ -368,7 +435,7 @@ pub async fn remove(ctx: RpcContext, RemoveParams { guid }: RemoveParams) -> Res #[derive(Deserialize, Serialize, Parser, TS)] pub struct ConnectParams { #[ts(type = "string")] - pub guid: InternedString, + pub guid: ContainerId, } pub async fn connect_rpc( @@ -502,7 +569,7 @@ pub async fn connect_cli(ctx: &CliContext, guid: RequestGuid) -> Result<(), Erro if let Some((method, rest)) = command.split_first() { let mut params = InOMap::new(); for arg in rest { - if let Some((name, value)) = arg.split_once("=") { + if let Some((name, value)) = arg.split_once('=') { params.insert(InternedString::intern(name), if value.is_empty() { Value::Null } else if let Ok(v) = serde_json::from_str(value) { diff --git a/core/startos/src/s9pk/v2/mod.rs b/core/startos/src/s9pk/v2/mod.rs index af1cd1c17..df82baf7e 100644 --- a/core/startos/src/s9pk/v2/mod.rs +++ b/core/startos/src/s9pk/v2/mod.rs @@ -70,7 +70,7 @@ fn filter(p: &Path) -> bool { #[derive(Clone)] pub struct S9pk> { - manifest: Manifest, + pub manifest: Manifest, manifest_dirty: bool, archive: MerkleArchive, size: Option, diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index 38923d726..e092a59e6 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -13,7 +13,6 @@ use start_stop::StartStop; use tokio::sync::Notify; use ts_rs::TS; -use crate::context::{CliContext, RpcContext}; use crate::core::rpc_continuations::RequestGuid; use crate::db::model::package::{ InstalledState, PackageDataEntry, PackageState, PackageStateMatchModelRef, UpdatingState, @@ -32,6 +31,10 @@ use crate::util::actor::concurrent::ConcurrentActor; use crate::util::actor::Actor; use crate::util::serde::Pem; use crate::volume::data_dir; +use crate::{ + context::{CliContext, RpcContext}, + lxc::ContainerId, +}; mod action; pub mod cli; @@ -193,8 +196,8 @@ impl Service { |db| { db.as_public_mut() .as_package_data_mut() - .as_idx_mut(&id) - .or_not_found(&id)? + .as_idx_mut(id) + .or_not_found(id)? .as_state_info_mut() .map_mutate(|s| { if let PackageState::Updating(UpdatingState { @@ -223,16 +226,12 @@ impl Service { tracing::debug!("{e:?}") }) { - if service - .uninstall(None) - .await - .map_err(|e| { + match service.uninstall(None).await { + Err(e) => { tracing::error!("Error uninstalling service: {e}"); tracing::debug!("{e:?}") - }) - .is_ok() - { - return Ok(None); + } + Ok(()) => return Ok(None), } } } @@ -299,10 +298,10 @@ impl Service { } pub async fn restore( - ctx: RpcContext, - s9pk: S9pk, - guard: impl GenericMountGuard, - progress: Option, + _ctx: RpcContext, + _s9pk: S9pk, + _guard: impl GenericMountGuard, + _progress: Option, ) -> Result { // TODO Err(Error::new(eyre!("not yet implemented"), ErrorKind::Unknown)) @@ -341,21 +340,36 @@ impl Service { .execute(ProcedureName::Uninit, to_value(&target_version)?, None) // TODO timeout .await?; let id = self.seed.persistent_container.s9pk.as_manifest().id.clone(); - self.seed - .ctx - .db - .mutate(|d| d.as_public_mut().as_package_data_mut().remove(&id)) - .await?; - self.shutdown().await + let ctx = self.seed.ctx.clone(); + self.shutdown().await?; + if target_version.is_none() { + ctx.db + .mutate(|d| d.as_public_mut().as_package_data_mut().remove(&id)) + .await?; + } + Ok(()) } pub async fn backup(&self, _guard: impl GenericMountGuard) -> Result { // TODO Err(Error::new(eyre!("not yet implemented"), ErrorKind::Unknown)) } + + pub fn container_id(&self) -> Result { + let id = &self.seed.id; + let container_id = (*self + .seed + .persistent_container + .lxc_container + .get() + .or_not_found(format!("container for {id}"))? + .guid) + .clone(); + Ok(container_id) + } } #[derive(Debug, Clone)] -struct RunningStatus { +pub struct RunningStatus { health: OrdMap, started: DateTime, } diff --git a/core/startos/src/service/persistent_container.rs b/core/startos/src/service/persistent_container.rs index 038661ace..d1303b0f9 100644 --- a/core/startos/src/service/persistent_container.rs +++ b/core/startos/src/service/persistent_container.rs @@ -10,7 +10,7 @@ use imbl_value::InternedString; use models::{ProcedureName, VolumeId}; use rpc_toolkit::{Empty, Server, ShutdownHandle}; use serde::de::DeserializeOwned; -use tokio::fs::File; +use tokio::fs::{create_dir_all, File}; use tokio::process::Command; use tokio::sync::{oneshot, watch, Mutex, OnceCell}; use tracing::instrument; @@ -39,8 +39,6 @@ use crate::ARCH; const RPC_CONNECT_TIMEOUT: Duration = Duration::from_secs(10); -struct ProcedureId(u64); - #[derive(Debug)] pub struct ServiceState { // This contains the start time and health check information for when the service is running. Note: Will be overwritting to the db, @@ -99,7 +97,17 @@ pub struct PersistentContainer { impl PersistentContainer { #[instrument(skip_all)] pub async fn new(ctx: &RpcContext, s9pk: S9pk, start: StartStop) -> Result { - let lxc_container = ctx.lxc_manager.create(LxcConfig::default()).await?; + let lxc_container = ctx + .lxc_manager + .create( + Some( + &ctx.datadir + .join("package-data/logs") + .join(&s9pk.as_manifest().id), + ), + LxcConfig::default(), + ) + .await?; let rpc_client = lxc_container.connect_rpc(Some(RPC_CONNECT_TIMEOUT)).await?; let js_mount = MountGuard::mount( &LoopDev::from( @@ -114,6 +122,7 @@ impl PersistentContainer { ReadOnly, ) .await?; + let mut volumes = BTreeMap::new(); for volume in &s9pk.as_manifest().volumes { let mountpoint = lxc_container @@ -175,7 +184,7 @@ impl PersistentContainer { if let Some(env) = s9pk .as_archive() .contents() - .get_path(Path::new("images").join(&*ARCH).join(&env_filename)) + .get_path(Path::new("images").join(*ARCH).join(&env_filename)) .and_then(|e| e.as_file()) { env.copy(&mut File::create(image_path.join(&env_filename)).await?) @@ -185,7 +194,7 @@ impl PersistentContainer { if let Some(json) = s9pk .as_archive() .contents() - .get_path(Path::new("images").join(&*ARCH).join(&json_filename)) + .get_path(Path::new("images").join(*ARCH).join(&json_filename)) .and_then(|e| e.as_file()) { json.copy(&mut File::create(image_path.join(&json_filename)).await?) @@ -231,7 +240,7 @@ impl PersistentContainer { .join(HOST_RPC_SERVER_SOCKET); let (send, recv) = oneshot::channel(); let handle = NonDetachingJoinHandle::from(tokio::spawn(async move { - let (shutdown, fut) = match async { + let chown_status = async { let res = server.run_unix(&path, |err| { tracing::error!("error on unix socket {}: {err}", path.display()) })?; @@ -241,9 +250,8 @@ impl PersistentContainer { .invoke(ErrorKind::Filesystem) .await?; Ok::<_, Error>(res) - } - .await - { + }; + let (shutdown, fut) = match chown_status.await { Ok((shutdown, fut)) => (Ok(shutdown), Some(fut)), Err(e) => (Err(e), None), }; diff --git a/core/startos/src/service/service_map.rs b/core/startos/src/service/service_map.rs index 68e1fc8a8..4386b39b7 100644 --- a/core/startos/src/service/service_map.rs +++ b/core/startos/src/service/service_map.rs @@ -32,9 +32,9 @@ use crate::util::serde::Pem; pub type DownloadInstallFuture = BoxFuture<'static, Result>; pub type InstallFuture = BoxFuture<'static, Result<(), Error>>; -pub(super) struct InstallProgressHandles { - pub(super) finalization_progress: PhaseProgressTrackerHandle, - pub(super) progress_handle: FullProgressTrackerHandle, +pub struct InstallProgressHandles { + pub finalization_progress: PhaseProgressTrackerHandle, + pub progress_handle: FullProgressTrackerHandle, } /// This is the structure to contain all the services