This commit is contained in:
Alex 2026-07-25 22:59:25 +02:00
commit 009173a97d
Signed by: alex
SSH key fingerprint: SHA256:D1+Ub8o0v4K5y1JNivW8IxEOelqLSvPmUzBbDIoZkRQ
49 changed files with 1788 additions and 389 deletions

View file

@ -1,8 +1,8 @@
use crate::{DaemonRuntime, DaemonServices};
use crate::log_buffer::LogBuffer;
use crate::{DaemonRuntime, DaemonServices};
use iota_ipc::{
ComponentStatusResponse, CommunitySummary, ConfigResponse, ExitIntent, IpcErrorCode,
LogEntriesResponse, LocalRequest, OmikronStatusResponse, ResponseEnvelope, ResponsePayload,
CommunitySummary, ComponentStatusResponse, ConfigResponse, ExitIntent, IpcErrorCode,
LocalRequest, LogEntriesResponse, OmikronStatusResponse, ResponseEnvelope, ResponsePayload,
ResponseResult, StatusResponse, TaskSummary, UpdateStatusResponse, UserDetailResponse,
UserSummary,
};
@ -23,8 +23,16 @@ pub struct CommandRouter {
}
impl CommandRouter {
pub fn new(runtime: Arc<DaemonRuntime>, services: Arc<DaemonServices>, log_buffer: Arc<Mutex<LogBuffer>>) -> Self {
Self { runtime, services, log_buffer }
pub fn new(
runtime: Arc<DaemonRuntime>,
services: Arc<DaemonServices>,
log_buffer: Arc<Mutex<LogBuffer>>,
) -> Self {
Self {
runtime,
services,
log_buffer,
}
}
pub async fn route(&self, request_id: u64, request: LocalRequest) -> ResponseEnvelope {
@ -34,6 +42,14 @@ impl CommandRouter {
}
async fn execute(&self, request: LocalRequest) -> ResponseResult {
if !self.services.active
&& !matches!(
request,
LocalRequest::GetStatus | LocalRequest::GetDaemonStatus
)
{
return ResponseResult::Error(IpcErrorCode::Unauthorized);
}
let needs_omikron = matches!(
request,
LocalRequest::CreateUser { .. }
@ -96,12 +112,10 @@ impl CommandRouter {
)
.await
{
(Some(user), _) => {
ResponseResult::Ok(ResponsePayload::UserCreated {
user_id: user.user_id,
username: user.username,
})
}
(Some(user), _) => ResponseResult::Ok(ResponsePayload::UserCreated {
user_id: user.user_id,
username: user.username,
}),
_ => ResponseResult::Error(IpcErrorCode::StorageFailure),
}
}
@ -154,13 +168,11 @@ impl CommandRouter {
message: "process exit accepted".into(),
})
}
LocalRequest::GetDaemonStatus => {
ResponseResult::Ok(ResponsePayload::DaemonStatus(
iota_ipc::DaemonStatusResponse {
formatted: format!("{:?}", self.runtime.snapshot()),
},
))
}
LocalRequest::GetDaemonStatus => ResponseResult::Ok(ResponsePayload::DaemonStatus(
iota_ipc::DaemonStatusResponse {
formatted: format!("{:?}", self.runtime.snapshot()),
},
)),
LocalRequest::RestartDaemon => {
self.runtime.shutdown(ShutdownReason::Restart);
ResponseResult::Ok(ResponsePayload::Acknowledged {
@ -195,12 +207,10 @@ impl CommandRouter {
LocalRequest::GetOmikronStatus => {
let connected = self.services.omikron.is_connected().await;
let iota_id = config_util::CONFIG.load().iota_id;
ResponseResult::Ok(ResponsePayload::OmikronStatus(
OmikronStatusResponse {
connected,
iota_id,
},
))
ResponseResult::Ok(ResponsePayload::OmikronStatus(OmikronStatusResponse {
connected,
iota_id,
}))
}
LocalRequest::ListComponents => {
let snapshot = self.runtime.snapshot();
@ -215,20 +225,16 @@ impl CommandRouter {
.collect();
ResponseResult::Ok(ResponsePayload::Components(components))
}
LocalRequest::GetUser { user_id } => {
match user_manager::get_user(user_id) {
Some(user) => ResponseResult::Ok(ResponsePayload::UserDetail(
UserDetailResponse {
user_id: user.user_id,
username: user.username,
display_name: user.display_name,
created_at: user.created_at,
trusted_apps: user.trusted_apps.keys().cloned().collect(),
},
)),
None => ResponseResult::Error(IpcErrorCode::NotFound),
}
}
LocalRequest::GetUser { user_id } => match user_manager::get_user(user_id) {
Some(user) => ResponseResult::Ok(ResponsePayload::UserDetail(UserDetailResponse {
user_id: user.user_id,
username: user.username,
display_name: user.display_name,
created_at: user.created_at,
trusted_apps: user.trusted_apps.keys().cloned().collect(),
})),
None => ResponseResult::Error(IpcErrorCode::NotFound),
},
LocalRequest::ImportUser { username } => {
match user_manager::load_from_tu(&username).await {
Ok(()) => ResponseResult::Ok(ResponsePayload::Acknowledged {
@ -245,17 +251,22 @@ impl CommandRouter {
};
ResponseResult::Ok(ResponsePayload::LogEntries(LogEntriesResponse { entries }))
}
LocalRequest::CheckUpdate => {
match iota_updater::check_update().await {
Ok(available) => ResponseResult::Ok(ResponsePayload::UpdateStatus(
UpdateStatusResponse { available },
)),
Err(_e) => ResponseResult::Error(IpcErrorCode::InternalFailure),
LocalRequest::CheckUpdate => match iota_updater::check_update().await {
Ok(available) => {
ResponseResult::Ok(ResponsePayload::UpdateStatus(UpdateStatusResponse {
available,
}))
}
}
Err(_e) => ResponseResult::Error(IpcErrorCode::InternalFailure),
},
LocalRequest::ListCommunities => {
let iota_id = config_util::CONFIG.load().iota_id.map(|id| id as i64).unwrap_or(0);
let stored = iota_storage::util::communities_util::CommunitiesUtil::get_communities(iota_id);
let iota_id = config_util::CONFIG
.load()
.iota_id
.map(|id| id as i64)
.unwrap_or(0);
let stored =
iota_storage::util::communities_util::CommunitiesUtil::get_communities(iota_id);
let summaries: Vec<CommunitySummary> = stored
.into_iter()
.map(|c| CommunitySummary {

View file

@ -1,5 +1,5 @@
use crate::log_buffer::LogBuffer;
use crate::deployment::from_environment;
use crate::log_buffer::LogBuffer;
use crate::{CommandRouter, DaemonRuntime, DaemonServices};
use iota_ipc::{
ClientMessage, DaemonMessage, HelloAck, MIN_PROTOCOL_VERSION, PROTOCOL_VERSION, read_msg,
@ -136,8 +136,16 @@ impl IpcServer {
let state_rx = self.state_rx.clone();
let instance_id = self.instance_id.clone();
tokio::spawn(async move {
if let Err(error) =
handle_client(stream, runtime, services, log_tx, log_buffer, state_rx, instance_id).await
if let Err(error) = handle_client(
stream,
runtime,
services,
log_tx,
log_buffer,
state_rx,
instance_id,
)
.await
{
eprintln!("IPC client error: {error}");
}

View file

@ -6,10 +6,7 @@ use tokio::sync::broadcast;
/* The daemon adapts logger output to the wire protocol so the logger stays
* independent from both the socket implementation and TUI state. */
pub fn spawn(
message_tx: broadcast::Sender<DaemonMessage>,
buffer: Arc<Mutex<LogBuffer>>,
) {
pub fn spawn(message_tx: broadcast::Sender<DaemonMessage>, buffer: Arc<Mutex<LogBuffer>>) {
let Some(mut logs) = subscribe() else {
return;
};

View file

@ -1,5 +1,8 @@
use omikron_connector::{OmikronClient, OmikronConnection};
use async_trait::async_trait;
use mtp::codec::CommunicationValue;
use omikron_connector::{OmikronClient, OmikronConnection, OmikronError};
use std::sync::Arc;
use std::time::Duration;
#[derive(Default)]
pub struct UserService;
@ -10,6 +13,7 @@ pub struct DaemonServices {
pub omikron: Arc<dyn OmikronClient>,
pub users: Arc<UserService>,
pub config: Arc<ConfigService>,
pub active: bool,
}
impl DaemonServices {
@ -18,6 +22,46 @@ impl DaemonServices {
omikron,
users: Arc::new(UserService),
config: Arc::new(ConfigService),
active: true,
})
}
/// Services used while the daemon is awaiting terms acceptance. They can
/// never initiate a connection; the command router exposes status only.
pub fn inactive() -> Arc<Self> {
Arc::new(Self {
omikron: Arc::new(InactiveOmikron),
users: Arc::new(UserService),
config: Arc::new(ConfigService),
active: false,
})
}
}
struct InactiveOmikron;
#[async_trait]
impl OmikronClient for InactiveOmikron {
async fn send_message(&self, _: &CommunicationValue) -> Result<(), OmikronError> {
Err(OmikronError::Disconnected(
"terms have not been accepted".into(),
))
}
async fn await_response(
&self,
_: &CommunicationValue,
_: Duration,
) -> Result<CommunicationValue, OmikronError> {
Err(OmikronError::Disconnected(
"terms have not been accepted".into(),
))
}
async fn reconnect(&self) -> Result<(), OmikronError> {
Err(OmikronError::Disconnected(
"terms have not been accepted".into(),
))
}
async fn is_connected(&self) -> bool {
false
}
}

View file

@ -1,6 +1,6 @@
use async_trait::async_trait;
use iota_daemon_lib::{CommandRouter, DaemonRuntime, DaemonServices};
use iota_daemon_lib::log_buffer::LogBuffer;
use iota_daemon_lib::{CommandRouter, DaemonRuntime, DaemonServices};
use iota_ipc::{LocalRequest, ResponseResult};
use mtp::codec::CommunicationValue;
use omikron_connector::{OmikronClient, OmikronError};
@ -44,7 +44,11 @@ async fn reconnect_uses_the_injected_client() {
users: Default::default(),
config: Default::default(),
});
let router = CommandRouter::new(Arc::new(DaemonRuntime::new()), services, Arc::new(Mutex::new(LogBuffer::new(100))));
let router = CommandRouter::new(
Arc::new(DaemonRuntime::new()),
services,
Arc::new(Mutex::new(LogBuffer::new(100))),
);
assert!(matches!(
router.route(1, LocalRequest::ReconnectOmikron).await.result,
ResponseResult::Ok(_)