[WIP] Daemon & CLI

This commit is contained in:
Alex-Emmet 2026-07-23 23:13:02 +02:00
commit 8b158108bb
100 changed files with 6519 additions and 1596 deletions

View file

@ -1,42 +1,34 @@
use crate::DaemonRuntime;
use iota_ipc::{
IpcErrorCode, LocalRequest, ResponseEnvelope, ResponseResult,
};
use crate::{DaemonRuntime, DaemonServices};
use iota_ipc::{ExitIntent, IpcErrorCode, LocalRequest, ResponseEnvelope, ResponseResult};
use iota_logger::{log, log_command};
use iota_storage::users::user_manager;
use iota_storage::util::config_util::modify_config;
use mtp::codec::{CommunicationType, CommunicationValue};
use omikron_connector::omikron_connection::OMIKRON_CONNECTION;
use std::sync::Arc;
use std::time::Duration;
use crate::daemon_state::ShutdownReason;
use crate::daemon_state::{ShutdownReason, StartupPhase};
#[derive(Clone)]
pub struct CommandRouter {
runtime: Arc<DaemonRuntime>,
services: Arc<DaemonServices>,
}
impl CommandRouter {
pub fn new(runtime: Arc<DaemonRuntime>) -> Self {
Self { runtime }
pub fn new(runtime: Arc<DaemonRuntime>, services: Arc<DaemonServices>) -> Self {
Self { runtime, services }
}
pub async fn route(&self, request_id: u64, request: LocalRequest) -> ResponseEnvelope {
log_command!("{:?}", request);
let result = self.execute(request).await;
ResponseEnvelope {
request_id,
result,
}
ResponseEnvelope { request_id, result }
}
/// Parse a legacy console command string into a typed request.
pub fn parse_console_command(line: &str) -> Option<LocalRequest> {
let parts: Vec<&str> = line
.trim_start_matches('/')
.split_whitespace()
.collect();
let parts: Vec<&str> = line.trim_start_matches('/').split_whitespace().collect();
match parts.as_slice() {
["help"] => None,
["tasks"] => Some(LocalRequest::ListTasks),
@ -53,13 +45,33 @@ impl CommandRouter {
["user", "list"] => Some(LocalRequest::ListUsers),
["reconnect"] => Some(LocalRequest::ReconnectOmikron),
["regenerate", "keys"] => Some(LocalRequest::RotateIotaIdentity),
["reload"] | ["restart"] => Some(LocalRequest::RestartDaemon),
["shutdown"] | ["stop"] => Some(LocalRequest::StopDaemon),
["reload"] | ["restart"] => Some(LocalRequest::RequestProcessExit {
intent: ExitIntent::Restart,
}),
["shutdown"] | ["stop"] => Some(LocalRequest::RequestProcessExit {
intent: ExitIntent::Stop,
}),
_ => None,
}
}
async fn execute(&self, request: LocalRequest) -> ResponseResult {
let needs_omikron = matches!(
request,
LocalRequest::CreateUser { .. }
| LocalRequest::RemoveUser { .. }
| LocalRequest::ReconnectOmikron
| LocalRequest::RotateIotaIdentity
);
if needs_omikron && !self.services.omikron.is_connected().await {
return ResponseResult::Error(
if self.runtime.current_startup_phase() != StartupPhase::Ready {
IpcErrorCode::NotReady
} else {
IpcErrorCode::OmikronUnavailable
},
);
}
match request {
LocalRequest::GetStatus => {
let phase = self.runtime.current_startup_phase();
@ -95,10 +107,13 @@ impl CommandRouter {
ResponseResult::Ok(users.join("\n"))
}
LocalRequest::CreateUser { username } => {
match omikron_connector::user_ops::create_user(&username).await {
(Some(user), _) => {
ResponseResult::Ok(format!("Created user {}", user.user_id))
}
match omikron_connector::user_ops::create_user(
self.services.omikron.as_ref(),
&username,
)
.await
{
(Some(user), _) => ResponseResult::Ok(format!("Created user {}", user.user_id)),
_ => ResponseResult::Error(IpcErrorCode::StorageFailure),
}
}
@ -109,24 +124,46 @@ impl CommandRouter {
};
let message = CommunicationValue::new(CommunicationType::DeleteUser)
.with_sender(user.user_id as u64);
if let Err(_e) = OMIKRON_CONNECTION.send_message(&message).await {
if let Err(_e) = self.services.omikron.send_message(&message).await {
return ResponseResult::Error(IpcErrorCode::OmikronUnavailable);
}
user_manager::remove_user(user.user_id);
ResponseResult::Ok(format!("Removed user {}", user.user_id))
}
LocalRequest::ReconnectOmikron => {
OMIKRON_CONNECTION.reconnect().await;
ResponseResult::Ok("Reconnected to Omikron server".into())
}
LocalRequest::ReconnectOmikron => match self.services.omikron.reconnect().await {
Ok(()) => ResponseResult::Ok("Reconnected to Omikron server".into()),
Err(_) => ResponseResult::Error(IpcErrorCode::OmikronUnavailable),
},
LocalRequest::RotateIotaIdentity => {
modify_config(|config| {
config.public_key = None;
config.private_key = None;
config.iota_id = None;
});
OMIKRON_CONNECTION.reconnect().await;
ResponseResult::Ok("Key pair regenerated and Omikron reconnection requested".into())
match self.services.omikron.reconnect().await {
Ok(()) => ResponseResult::Ok(
"Key pair regenerated and Omikron reconnection requested".into(),
),
Err(_) => ResponseResult::Error(IpcErrorCode::OmikronUnavailable),
}
}
LocalRequest::RequestProcessExit { intent } => {
if matches!(intent, ExitIntent::Restart)
&& !matches!(
crate::deployment::from_environment().supervisor,
iota_ipc::SupervisorKind::Systemd | iota_ipc::SupervisorKind::IotaUi
)
{
return ResponseResult::Error(IpcErrorCode::Conflict);
}
self.runtime.shutdown(match intent {
ExitIntent::Stop => ShutdownReason::Stop,
ExitIntent::Restart => ShutdownReason::Restart,
});
ResponseResult::Ok("process exit accepted".into())
}
LocalRequest::GetDaemonStatus => {
ResponseResult::Ok(format!("{:?}", self.runtime.snapshot()))
}
LocalRequest::RestartDaemon => {
self.runtime.shutdown(ShutdownReason::Restart);
@ -140,10 +177,12 @@ impl CommandRouter {
}
pub async fn ping(&self, seconds: u64) -> Result<String, String> {
let response = OMIKRON_CONNECTION
let response = self
.services
.omikron
.await_response(
&CommunicationValue::new(CommunicationType::Ping),
Some(Duration::from_secs(seconds)),
Duration::from_secs(seconds),
)
.await;
match response {