diff --git a/core/locales/i18n.yaml b/core/locales/i18n.yaml index 2a7d43343..45a75fdc9 100644 --- a/core/locales/i18n.yaml +++ b/core/locales/i18n.yaml @@ -3684,6 +3684,13 @@ help.arg.s9pk-file-path: fr_FR: "Chemin vers le fichier de paquet s9pk" pl_PL: "Ścieżka do pliku pakietu s9pk" +help.arg.s9pk-file-paths: + en_US: "Paths to s9pk package files" + de_DE: "Pfade zu s9pk-Paketdateien" + es_ES: "Rutas a los archivos de paquete s9pk" + fr_FR: "Chemins vers les fichiers de paquet s9pk" + pl_PL: "Ścieżki do plików pakietów s9pk" + help.arg.session-ids: en_US: "Session identifiers" de_DE: "Sitzungskennungen" @@ -4980,6 +4987,13 @@ about.publish-s9pk: fr_FR: "Publier s9pk dans le bucket S3 et indexer dans le registre" pl_PL: "Opublikuj s9pk do bucketu S3 i zindeksuj w rejestrze" +about.select-s9pk-for-device: + en_US: "Select the best compatible s9pk for a target device" + de_DE: "Das beste kompatible s9pk für ein Zielgerät auswählen" + es_ES: "Seleccionar el s9pk más compatible para un dispositivo destino" + fr_FR: "Sélectionner le meilleur s9pk compatible pour un appareil cible" + pl_PL: "Wybierz najlepiej kompatybilny s9pk dla urządzenia docelowego" + about.rebuild-service-container: en_US: "Rebuild service container" de_DE: "Dienst-Container neu erstellen" diff --git a/core/src/context/rpc.rs b/core/src/context/rpc.rs index 651f8d744..f1fb6343d 100644 --- a/core/src/context/rpc.rs +++ b/core/src/context/rpc.rs @@ -10,7 +10,6 @@ use std::time::Duration; use chrono::{TimeDelta, Utc}; use imbl::OrdMap; use imbl_value::InternedString; -use itertools::Itertools; use josekit::jwk::Jwk; use reqwest::{Client, Proxy}; use rpc_toolkit::yajrc::RpcError; @@ -25,7 +24,6 @@ use crate::account::AccountInfo; use crate::auth::Sessions; use crate::context::config::ServerConfig; use crate::db::model::Database; -use crate::db::model::package::TaskSeverity; use crate::disk::OsPartitionInfo; use crate::disk::mount::filesystem::bind::Bind; use crate::disk::mount::filesystem::block_dev::BlockDev; @@ -44,7 +42,6 @@ use crate::prelude::*; use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle}; use crate::rpc_continuations::{Guid, OpenAuthedContinuations, RpcContinuations}; use crate::service::ServiceMap; -use crate::service::action::update_tasks; use crate::service::effects::callbacks::ServiceCallbacks; use crate::service::effects::subcontainer::NVIDIA_OVERLAY_PATH; use crate::shutdown::Shutdown; @@ -53,7 +50,7 @@ use crate::util::future::NonDetachingJoinHandle; use crate::util::io::{TmpDir, delete_file}; use crate::util::lshw::LshwDevice; use crate::util::sync::{SyncMutex, SyncRwLock, Watch}; -use crate::{ActionId, DATA_DIR, PLATFORM, PackageId}; +use crate::{DATA_DIR, PLATFORM, PackageId}; pub struct RpcContextSeed { is_closed: AtomicBool, @@ -114,7 +111,6 @@ pub struct CleanupInitPhases { cleanup_sessions: PhaseProgressTrackerHandle, init_services: PhaseProgressTrackerHandle, prune_s9pks: PhaseProgressTrackerHandle, - check_tasks: PhaseProgressTrackerHandle, } impl CleanupInitPhases { pub fn new(handle: &FullProgressTracker) -> Self { @@ -122,7 +118,6 @@ impl CleanupInitPhases { cleanup_sessions: handle.add_phase("Cleaning up sessions".into(), Some(1)), init_services: handle.add_phase("Initializing services".into(), Some(10)), prune_s9pks: handle.add_phase("Pruning S9PKs".into(), Some(1)), - check_tasks: handle.add_phase("Checking action requests".into(), Some(1)), } } } @@ -173,7 +168,7 @@ impl RpcContext { init_net_ctrl.complete(); tracing::info!("{}", t!("context.rpc.initialized-net-controller")); - if PLATFORM.ends_with("-nonfree") { + if PLATFORM.ends_with("-nvidia") { if let Err(e) = Command::new("nvidia-smi") .invoke(ErrorKind::ParseSysInfo) .await @@ -411,7 +406,6 @@ impl RpcContext { mut cleanup_sessions, mut init_services, mut prune_s9pks, - mut check_tasks, }: CleanupInitPhases, ) -> Result<(), Error> { cleanup_sessions.start(); @@ -503,76 +497,6 @@ impl RpcContext { } prune_s9pks.complete(); - check_tasks.start(); - let mut action_input: OrdMap> = OrdMap::new(); - let tasks: BTreeSet<_> = peek - .as_public() - .as_package_data() - .as_entries()? - .into_iter() - .map(|(_, pde)| { - Ok(pde - .as_tasks() - .as_entries()? - .into_iter() - .map(|(_, r)| { - let t = r.as_task(); - Ok::<_, Error>(if t.as_input().transpose_ref().is_some() { - Some((t.as_package_id().de()?, t.as_action_id().de()?)) - } else { - None - }) - }) - .filter_map_ok(|a| a)) - }) - .flatten_ok() - .map(|a| a.and_then(|a| a)) - .try_collect()?; - let procedure_id = Guid::new(); - for (package_id, action_id) in tasks { - if let Some(service) = self.services.get(&package_id).await.as_ref() { - if let Some(input) = service - .get_action_input(procedure_id.clone(), action_id.clone(), Value::Null) - .await - .log_err() - .flatten() - .and_then(|i| i.value) - { - action_input - .entry(package_id) - .or_default() - .insert(action_id, input); - } - } - } - - self.db - .mutate(|db| { - for (package_id, action_input) in &action_input { - for (action_id, input) in action_input { - for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? { - pde.as_tasks_mut().mutate(|tasks| { - Ok(update_tasks(tasks, package_id, action_id, input, false)) - })?; - } - } - } - for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? { - if pde - .as_tasks() - .de()? - .into_iter() - .any(|(_, t)| t.active && t.task.severity == TaskSeverity::Critical) - { - pde.as_status_info_mut().stop()?; - } - } - Ok(()) - }) - .await - .result?; - check_tasks.complete(); - Ok(()) } pub async fn call_remote( diff --git a/core/src/service/effects/action.rs b/core/src/service/effects/action.rs index 115e021b1..e01fd0318 100644 --- a/core/src/service/effects/action.rs +++ b/core/src/service/effects/action.rs @@ -251,11 +251,12 @@ async fn create_task( .get(&task.package_id) .await .as_ref() + .filter(|s| s.is_initialized()) { - let Some(prev) = service + let prev = service .get_action_input(procedure_id.clone(), task.action_id.clone(), Value::Null) - .await? - else { + .await?; + let Some(prev) = prev else { return Err(Error::new( eyre!( "{}", @@ -278,7 +279,9 @@ async fn create_task( true } } else { - true // update when service is installed + // Service not installed or not yet initialized — assume active. + // Will be retested when service init completes (Service::recheck_tasks). + true } } }, diff --git a/core/src/service/mod.rs b/core/src/service/mod.rs index f7c458985..f17f2d266 100644 --- a/core/src/service/mod.rs +++ b/core/src/service/mod.rs @@ -215,6 +215,84 @@ pub struct Service { seed: Arc, } impl Service { + pub fn is_initialized(&self) -> bool { + self.seed.persistent_container.state.borrow().rt_initialized + } + + /// Re-evaluate all tasks that reference this service's actions. + /// Called after every service init to update task active state. + #[instrument(skip_all)] + async fn recheck_tasks(&self) -> Result<(), Error> { + let service_id = &self.seed.id; + let peek = self.seed.ctx.db.peek().await; + let mut action_input: BTreeMap = BTreeMap::new(); + let tasks: BTreeSet<_> = peek + .as_public() + .as_package_data() + .as_entries()? + .into_iter() + .map(|(_, pde)| { + Ok(pde + .as_tasks() + .as_entries()? + .into_iter() + .map(|(_, r)| { + let t = r.as_task(); + Ok::<_, Error>( + if t.as_package_id().de()? == *service_id + && t.as_input().transpose_ref().is_some() + { + Some(t.as_action_id().de()?) + } else { + None + }, + ) + }) + .filter_map_ok(|a| a)) + }) + .flatten_ok() + .map(|a| a.and_then(|a| a)) + .try_collect()?; + let procedure_id = Guid::new(); + for action_id in tasks { + if let Some(input) = self + .get_action_input(procedure_id.clone(), action_id.clone(), Value::Null) + .await + .log_err() + .flatten() + .and_then(|i| i.value) + { + action_input.insert(action_id, input); + } + } + self.seed + .ctx + .db + .mutate(|db| { + for (action_id, input) in &action_input { + for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? { + pde.as_tasks_mut().mutate(|tasks| { + Ok(update_tasks(tasks, service_id, action_id, input, false)) + })?; + } + } + for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? { + if pde + .as_tasks() + .de()? + .into_iter() + .any(|(_, t)| t.active && t.task.severity == TaskSeverity::Critical) + { + pde.as_status_info_mut().stop()?; + } + } + Ok(()) + }) + .await + .result?; + Ok(()) + } + #[instrument(skip_all)] async fn new( ctx: RpcContext, @@ -263,6 +341,7 @@ impl Service { .persistent_container .init(service.weak(), procedure_id, init_kind) .await?; + service.recheck_tasks().await?; if let Some(recovery_guard) = recovery_guard { recovery_guard.unmount(true).await?; } @@ -489,70 +568,8 @@ impl Service { ) .await?; - if let Some(mut progress) = progress { - progress.finalization_progress.complete(); - progress.progress.complete(); - tokio::task::yield_now().await; - } - - let peek = ctx.db.peek().await; - let mut action_input: BTreeMap = BTreeMap::new(); - let tasks: BTreeSet<_> = peek - .as_public() - .as_package_data() - .as_entries()? - .into_iter() - .map(|(_, pde)| { - Ok(pde - .as_tasks() - .as_entries()? - .into_iter() - .map(|(_, r)| { - let t = r.as_task(); - Ok::<_, Error>( - if t.as_package_id().de()? == manifest.id - && t.as_input().transpose_ref().is_some() - { - Some(t.as_action_id().de()?) - } else { - None - }, - ) - }) - .filter_map_ok(|a| a)) - }) - .flatten_ok() - .map(|a| a.and_then(|a| a)) - .try_collect()?; - for action_id in tasks { - if peek - .as_public() - .as_package_data() - .as_idx(&manifest.id) - .or_not_found(&manifest.id)? - .as_actions() - .contains_key(&action_id)? - { - if let Some(input) = service - .get_action_input(procedure_id.clone(), action_id.clone(), Value::Null) - .await - .log_err() - .flatten() - .and_then(|i| i.value) - { - action_input.insert(action_id, input); - } - } - } ctx.db .mutate(|db| { - for (action_id, input) in &action_input { - for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? { - pde.as_tasks_mut().mutate(|tasks| { - Ok(update_tasks(tasks, &manifest.id, action_id, input, false)) - })?; - } - } let entry = db .as_public_mut() .as_package_data_mut() @@ -594,6 +611,12 @@ impl Service { .await .result?; + if let Some(mut progress) = progress { + progress.finalization_progress.complete(); + progress.progress.complete(); + tokio::task::yield_now().await; + } + // Trigger manifest callbacks after successful installation let manifest = service.seed.persistent_container.s9pk.as_manifest(); if let Some(callbacks) = ctx.callbacks.get_service_manifest(&manifest.id) { diff --git a/sdk/base/lib/index.ts b/sdk/base/lib/index.ts index 9437d3e43..cb48ea89c 100644 --- a/sdk/base/lib/index.ts +++ b/sdk/base/lib/index.ts @@ -74,21 +74,38 @@ declare module 'zod' { } // Override z.object to produce loose objects by default (extra keys are preserved, not stripped). -// Patches the source module in require.cache where 'object' is a writable property; +const _origObject = _z.object +const _patchedObject = (...args: Parameters) => + _origObject(...args).loose() + +// In CJS (Node.js), patch the source module in require.cache where 'object' is a writable property; // the CJS getter chain (index → external → schemas) then relays the patched version. // We walk only the zod entry module's dependency tree and match by identity (=== origObject). -const _origObject = _z.object -const _zodModule = require.cache[require.resolve('zod')] -for (const child of _zodModule?.children ?? []) { - for (const grandchild of child.children ?? []) { - const desc = Object.getOwnPropertyDescriptor(grandchild.exports, 'object') - if (desc?.value === _origObject && desc.writable) { - grandchild.exports.object = (...args: Parameters) => - _origObject(...args).loose() +try { + const _zodModule = require.cache[require.resolve('zod')] + for (const child of _zodModule?.children ?? []) { + for (const grandchild of child.children ?? []) { + const desc = Object.getOwnPropertyDescriptor(grandchild.exports, 'object') + if (desc?.value === _origObject && desc.writable) { + grandchild.exports.object = _patchedObject + } } } +} catch (_) { + // Not in CJS/Node environment (e.g. browser) — require.cache unavailable } -export { _z as z } +// z.object is a non-configurable getter on the zod namespace, so we can't override it directly. +// Shadow it by exporting a new object with _z as prototype and our patched object on the instance. +const z: typeof _z = Object.create(_z, { + object: { + value: _patchedObject, + writable: true, + configurable: true, + enumerable: true, + }, +}) + +export { z } export * as utils from './util'