mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
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 <me@drbonez.dev>
This commit is contained in:
@@ -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`
|
||||
|
||||
@@ -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
|
||||
@@ -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": "",
|
||||
|
||||
@@ -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<BTreeMap<InternedString, LxcContainer>>,
|
||||
pub lxc: Mutex<BTreeMap<ContainerId, LxcContainer>>,
|
||||
}
|
||||
|
||||
pub struct Hardware {
|
||||
|
||||
@@ -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<Child>,
|
||||
#[pin]
|
||||
entries: BoxStream<'static, Result<JournalctlEntry, Error>>,
|
||||
}
|
||||
@@ -116,9 +122,11 @@ pub struct LogFollowResponse {
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LogEntry {
|
||||
timestamp: DateTime<Utc>,
|
||||
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::<u64>()?),
|
||||
),
|
||||
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<LogResponse, Error> {
|
||||
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<LogFollowResponse, Error> {
|
||||
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<LogStream, Error> {
|
||||
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::<JournalctlEntry>)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.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::<JournalctlEntry>(&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::<JournalctlEntry>(&s)
|
||||
.with_kind(crate::ErrorKind::Deserialization),
|
||||
)
|
||||
});
|
||||
|
||||
Ok(LogStream {
|
||||
_child: child,
|
||||
entries: deserialized_entries.boxed(),
|
||||
})
|
||||
cmd
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
|
||||
@@ -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<Vec<Weak<InternedString>>>,
|
||||
#[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<Self, Self::Error> {
|
||||
Ok(ContainerId(InternedString::intern(value)))
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for ContainerId {
|
||||
type Err = InvalidId;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::try_from(s)
|
||||
}
|
||||
}
|
||||
impl ValueParserFactory for ContainerId {
|
||||
type Parser = FromStrParser<Self>;
|
||||
fn value_parser() -> Self::Parser {
|
||||
FromStrParser::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LxcManager {
|
||||
containers: Mutex<Vec<Weak<ContainerId>>>,
|
||||
}
|
||||
|
||||
impl LxcManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
containers: Default::default(),
|
||||
}
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub async fn create(self: &Arc<Self>, config: LxcConfig) -> Result<LxcContainer, Error> {
|
||||
let container = LxcContainer::new(self, config).await?;
|
||||
pub async fn create(
|
||||
self: &Arc<Self>,
|
||||
log_mount: Option<&Path>,
|
||||
config: LxcConfig,
|
||||
) -> Result<LxcContainer, Error> {
|
||||
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<LxcManager>,
|
||||
rootfs: OverlayGuard,
|
||||
guid: Arc<InternedString>,
|
||||
pub guid: Arc<ContainerId>,
|
||||
rpc_bind: TmpMountGuard,
|
||||
log_mount: Option<MountGuard>,
|
||||
config: LxcConfig,
|
||||
exited: bool,
|
||||
}
|
||||
impl LxcContainer {
|
||||
async fn new(manager: &Arc<LxcManager>, config: LxcConfig) -> Result<Self, Error> {
|
||||
async fn new(
|
||||
manager: &Arc<LxcManager>,
|
||||
log_mount: Option<&Path>,
|
||||
config: LxcConfig,
|
||||
) -> Result<Self, Error> {
|
||||
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<Ipv4Addr, Error> {
|
||||
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<InternedString, Error> {
|
||||
let container = ctx.lxc_manager.create(LxcConfig::default()).await?;
|
||||
pub async fn create(ctx: RpcContext) -> Result<ContainerId, Error> {
|
||||
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<Vec<InternedString>, Error> {
|
||||
pub async fn list(ctx: RpcContext) -> Result<Vec<ContainerId>, 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) {
|
||||
|
||||
@@ -70,7 +70,7 @@ fn filter(p: &Path) -> bool {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct S9pk<S = Section<MultiCursorFile>> {
|
||||
manifest: Manifest,
|
||||
pub manifest: Manifest,
|
||||
manifest_dirty: bool,
|
||||
archive: MerkleArchive<S>,
|
||||
size: Option<u64>,
|
||||
|
||||
@@ -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<InstallProgressHandles>,
|
||||
_ctx: RpcContext,
|
||||
_s9pk: S9pk,
|
||||
_guard: impl GenericMountGuard,
|
||||
_progress: Option<InstallProgressHandles>,
|
||||
) -> Result<Self, Error> {
|
||||
// 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<BackupReturn, Error> {
|
||||
// TODO
|
||||
Err(Error::new(eyre!("not yet implemented"), ErrorKind::Unknown))
|
||||
}
|
||||
|
||||
pub fn container_id(&self) -> Result<ContainerId, Error> {
|
||||
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<HealthCheckId, HealthCheckResult>,
|
||||
started: DateTime<Utc>,
|
||||
}
|
||||
|
||||
@@ -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<Self, Error> {
|
||||
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),
|
||||
};
|
||||
|
||||
@@ -32,9 +32,9 @@ use crate::util::serde::Pem;
|
||||
pub type DownloadInstallFuture = BoxFuture<'static, Result<InstallFuture, Error>>;
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user