mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-12-23 00:21:38 +03:00
remote installs?
This commit is contained in:
parent
b85413339a
commit
ae766b4e9a
2
build.rs
2
build.rs
@ -126,7 +126,7 @@ fn main() {
|
||||
}
|
||||
// only execute if one of the modules has source code changes
|
||||
const WASI_APPS: [&str; 9] = [
|
||||
"app_tracker",
|
||||
"app_store",
|
||||
"chess",
|
||||
"homepage",
|
||||
"http_bindings",
|
||||
|
@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "app_tracker"
|
||||
name = "app_store"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "app_tracker"
|
||||
name = "app_store"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"process_name": "app_tracker",
|
||||
"process_wasm_path": "/app_tracker.wasm",
|
||||
"process_name": "main",
|
||||
"process_wasm_path": "/app_store.wasm",
|
||||
"on_panic": "Restart",
|
||||
"request_networking": true,
|
||||
"request_messaging": [
|
5
modules/app_store/pkg/metadata.json
Normal file
5
modules/app_store/pkg/metadata.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"package": "app_store",
|
||||
"publisher": "uqbar",
|
||||
"description": "A package manager + app store. This JSON field is optional and you can add whatever you want in addition to this."
|
||||
}
|
489
modules/app_store/src/lib.rs
Normal file
489
modules/app_store/src/lib.rs
Normal file
@ -0,0 +1,489 @@
|
||||
cargo_component_bindings::generate!();
|
||||
|
||||
use bindings::{
|
||||
component::uq_process::types::*, get_capability, get_payload, print_to_terminal, receive,
|
||||
send_request, send_response, Guest,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod kernel_types;
|
||||
use kernel_types as kt;
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod process_lib;
|
||||
use process_lib::PackageId;
|
||||
|
||||
mod transfer_lib;
|
||||
|
||||
struct Component;
|
||||
|
||||
// #[derive(Serialize, Deserialize)]
|
||||
// struct AppState {
|
||||
// // TODO this should mirror onchain listing
|
||||
// pub name: String,
|
||||
// pub owner: NodeId,
|
||||
// pub desc: String,
|
||||
// pub website: Option<String>,
|
||||
// pub versions: Vec<(u32, String)>, // TODO
|
||||
// }
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct AppTrackerState {
|
||||
pub mirrored_packages: HashSet<PackageId>,
|
||||
pub requested_packages: HashMap<PackageId, NodeId>, // who we're expecting it from
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum AppTrackerRequest {
|
||||
New {
|
||||
package: String,
|
||||
mirror: bool,
|
||||
},
|
||||
NewFromRemote {
|
||||
package_id: PackageId,
|
||||
install_from: NodeId,
|
||||
},
|
||||
Install {
|
||||
package: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum AppTrackerResponse {
|
||||
New { package: String },
|
||||
NewFromRemote { package_id: PackageId },
|
||||
Install { package: String },
|
||||
Error { error: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PackageMetadata {
|
||||
pub package: String,
|
||||
pub publisher: String,
|
||||
pub desc: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PackageManifestEntry {
|
||||
pub process_name: String,
|
||||
pub process_wasm_path: String,
|
||||
pub on_panic: kt::OnPanic,
|
||||
pub request_networking: bool,
|
||||
pub request_messaging: Vec<String>,
|
||||
pub public: bool,
|
||||
}
|
||||
|
||||
fn parse_command(
|
||||
our: &Address,
|
||||
source: &Address,
|
||||
request_string: String,
|
||||
state: &mut AppTrackerState,
|
||||
) -> anyhow::Result<Option<AppTrackerResponse>> {
|
||||
match serde_json::from_str(&request_string)? {
|
||||
// create a new package based on local payload
|
||||
AppTrackerRequest::New { package, mirror } => {
|
||||
if our.node != source.node {
|
||||
return Err(anyhow::anyhow!("new package request from non-local node"));
|
||||
}
|
||||
let Some(payload) = get_payload() else {
|
||||
return Err(anyhow::anyhow!("no payload"));
|
||||
};
|
||||
|
||||
let vfs_address = Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::from_str("vfs:sys:uqbar")?,
|
||||
};
|
||||
|
||||
let _ = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
false,
|
||||
Some(serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::New,
|
||||
})?),
|
||||
None,
|
||||
None,
|
||||
5,
|
||||
)?;
|
||||
|
||||
// add zip bytes
|
||||
let _ = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
true,
|
||||
Some(serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::Add {
|
||||
full_path: package.clone().into(),
|
||||
entry_type: kt::AddEntryType::ZipArchive,
|
||||
},
|
||||
})?),
|
||||
None,
|
||||
Some(&payload),
|
||||
5,
|
||||
)?;
|
||||
|
||||
// save the zip file itself in VFS for sharing with other nodes
|
||||
// call it <package>.zip
|
||||
let _ = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
true,
|
||||
Some(serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::Add {
|
||||
full_path: format!("/{}.zip", package),
|
||||
entry_type: kt::AddEntryType::NewFile,
|
||||
},
|
||||
})?),
|
||||
None,
|
||||
Some(&payload),
|
||||
5,
|
||||
)?;
|
||||
|
||||
// if mirror, save in our state
|
||||
if mirror {
|
||||
let _ = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
false,
|
||||
Some(serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::GetEntry("/metadata.json".into()),
|
||||
})?),
|
||||
None,
|
||||
None,
|
||||
5,
|
||||
)?;
|
||||
let Some(payload) = get_payload() else {
|
||||
return Err(anyhow::anyhow!("no metadata payload"));
|
||||
};
|
||||
let metadata = String::from_utf8(payload.bytes)?;
|
||||
let metadata = serde_json::from_str::<PackageMetadata>(&metadata)?;
|
||||
state
|
||||
.mirrored_packages
|
||||
.insert(PackageId::new(&metadata.package, &metadata.publisher));
|
||||
process_lib::set_state::<AppTrackerState>(&state);
|
||||
}
|
||||
|
||||
Ok(Some(AppTrackerResponse::New { package }))
|
||||
}
|
||||
// if we are the source, forward to install_from target.
|
||||
// if we install_from, respond with package if we have it
|
||||
AppTrackerRequest::NewFromRemote {
|
||||
package_id,
|
||||
install_from,
|
||||
} => {
|
||||
if our.node == source.node {
|
||||
let _ = send_request(
|
||||
&Address {
|
||||
node: install_from.clone(),
|
||||
process: our.process.clone(),
|
||||
},
|
||||
&Request {
|
||||
inherit: true,
|
||||
expects_response: Some(5), // TODO
|
||||
ipc: Some(serde_json::to_string(&AppTrackerRequest::NewFromRemote {
|
||||
package_id,
|
||||
install_from,
|
||||
})?),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
Ok(None)
|
||||
} else if our.node == install_from {
|
||||
let Some(_mirror) = state.mirrored_packages.get(&package_id) else {
|
||||
return Ok(Some(AppTrackerResponse::Error { error: "package not mirrored here!".into() }))
|
||||
};
|
||||
// get the .zip from VFS and attach as payload to response
|
||||
let vfs_address = Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::from_str("vfs:sys:uqbar")?,
|
||||
};
|
||||
let _ = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
false,
|
||||
Some(serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package_id.to_string(),
|
||||
action: kt::VfsAction::GetEntry(format!("/{}.zip", package_id.to_string())),
|
||||
})?),
|
||||
None,
|
||||
None,
|
||||
5,
|
||||
)?;
|
||||
Ok(Some(AppTrackerResponse::NewFromRemote { package_id }))
|
||||
} else {
|
||||
// TODO what to do here?
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
AppTrackerRequest::Install { package } => {
|
||||
if our.node != source.node {
|
||||
return Err(anyhow::anyhow!("install request from non-local node"));
|
||||
}
|
||||
let vfs_address = Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::from_str("vfs:sys:uqbar")?,
|
||||
};
|
||||
|
||||
let _ = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
false,
|
||||
Some(serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::GetEntry("/manifest.json".into()),
|
||||
})?),
|
||||
None,
|
||||
None,
|
||||
5,
|
||||
)?;
|
||||
let Some(payload) = get_payload() else {
|
||||
return Err(anyhow::anyhow!("no payload"));
|
||||
};
|
||||
let manifest = String::from_utf8(payload.bytes)?;
|
||||
let manifest = serde_json::from_str::<Vec<PackageManifestEntry>>(&manifest)?;
|
||||
|
||||
for entry in manifest {
|
||||
let path = if entry.process_wasm_path.starts_with("/") {
|
||||
entry.process_wasm_path
|
||||
} else {
|
||||
format!("/{}", entry.process_wasm_path)
|
||||
};
|
||||
|
||||
let (_, hash_response) = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
false,
|
||||
Some(serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::GetHash(path.clone()),
|
||||
})?),
|
||||
None,
|
||||
None,
|
||||
5,
|
||||
)?;
|
||||
|
||||
let Message::Response((Response { ipc: Some(ipc), .. }, _)) = hash_response else {
|
||||
return Err(anyhow::anyhow!("bad vfs response"));
|
||||
};
|
||||
let kt::VfsResponse::GetHash(Some(hash)) = serde_json::from_str(&ipc)? else {
|
||||
return Err(anyhow::anyhow!("no hash in vfs"));
|
||||
};
|
||||
|
||||
// build initial caps
|
||||
let mut initial_capabilities: HashSet<kt::SignedCapability> = HashSet::new();
|
||||
if entry.request_networking {
|
||||
let Some(networking_cap) = get_capability(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::from_str("kernel:sys:uqbar")?,
|
||||
},
|
||||
&"\"network\"".to_string(),
|
||||
) else {
|
||||
return Err(anyhow::anyhow!("app-store: no net cap"));
|
||||
};
|
||||
initial_capabilities.insert(kt::de_wit_signed_capability(networking_cap));
|
||||
}
|
||||
let Some(read_cap) = get_capability(
|
||||
&vfs_address.clone(),
|
||||
&serde_json::to_string(&serde_json::json!({
|
||||
"kind": "read",
|
||||
"drive": package,
|
||||
}))?,
|
||||
) else {
|
||||
return Err(anyhow::anyhow!("app-store: no read cap"));
|
||||
};
|
||||
initial_capabilities.insert(kt::de_wit_signed_capability(read_cap));
|
||||
let Some(write_cap) = get_capability(
|
||||
&vfs_address.clone(),
|
||||
&serde_json::to_string(&serde_json::json!({
|
||||
"kind": "write",
|
||||
"drive": package,
|
||||
}))?,
|
||||
) else {
|
||||
return Err(anyhow::anyhow!("app-store: no write cap"));
|
||||
};
|
||||
initial_capabilities.insert(kt::de_wit_signed_capability(write_cap));
|
||||
|
||||
for process_name in &entry.request_messaging {
|
||||
let Ok(parsed_process_id) = ProcessId::from_str(&process_name) else {
|
||||
// TODO handle arbitrary caps here
|
||||
continue;
|
||||
};
|
||||
let Some(messaging_cap) = get_capability(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: parsed_process_id.clone(),
|
||||
},
|
||||
&"\"messaging\"".into()
|
||||
) else {
|
||||
print_to_terminal(0, &format!("app-store: no cap for {} to give away!", process_name));
|
||||
continue;
|
||||
};
|
||||
initial_capabilities.insert(kt::de_wit_signed_capability(messaging_cap));
|
||||
}
|
||||
|
||||
let process_id = format!("{}:{}", entry.process_name, package.clone());
|
||||
let Ok(parsed_new_process_id) = ProcessId::from_str(&process_id) else {
|
||||
return Err(anyhow::anyhow!("app-store: invalid process id!"));
|
||||
};
|
||||
let _ = process_lib::send_request(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::from_str("kernel:sys:uqbar")?,
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&kt::KernelCommand::KillProcess(
|
||||
kt::ProcessId::de_wit(parsed_new_process_id.clone()),
|
||||
))?),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
// kernel start process takes bytes as payload + wasm_bytes_handle...
|
||||
// reconsider perhaps
|
||||
let (_, _bytes_response) = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
false,
|
||||
Some(serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::GetEntry(path),
|
||||
})?),
|
||||
None,
|
||||
None,
|
||||
5,
|
||||
)?;
|
||||
|
||||
let Some(payload) = get_payload() else {
|
||||
return Err(anyhow::anyhow!("no wasm bytes payload."));
|
||||
};
|
||||
|
||||
let _ = process_lib::send_and_await_response(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::from_str("kernel:sys:uqbar")?,
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&kt::KernelCommand::StartProcess {
|
||||
id: kt::ProcessId::de_wit(parsed_new_process_id),
|
||||
wasm_bytes_handle: hash,
|
||||
on_panic: entry.on_panic,
|
||||
initial_capabilities,
|
||||
public: entry.public,
|
||||
})?),
|
||||
None,
|
||||
Some(&payload),
|
||||
5,
|
||||
)?;
|
||||
}
|
||||
Ok(Some(AppTrackerResponse::Install { package }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Guest for Component {
|
||||
fn init(our: Address) {
|
||||
assert_eq!(our.process.to_string(), "main:app_store:uqbar");
|
||||
print_to_terminal(0, &format!("app_store main proc: start"));
|
||||
|
||||
let mut state = process_lib::get_state::<AppTrackerState>().unwrap_or(AppTrackerState {
|
||||
mirrored_packages: HashSet::new(),
|
||||
requested_packages: HashMap::new(),
|
||||
});
|
||||
|
||||
loop {
|
||||
let (source, message) = match receive() {
|
||||
Ok((source, message)) => (source, message),
|
||||
Err((error, _context)) => {
|
||||
print_to_terminal(0, &format!("net error: {:?}", error.kind));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match message {
|
||||
Message::Request(Request {
|
||||
ipc,
|
||||
expects_response,
|
||||
metadata,
|
||||
..
|
||||
}) => {
|
||||
let Some(command) = ipc else {
|
||||
continue;
|
||||
};
|
||||
match parse_command(&our, &source, command, &mut state) {
|
||||
Ok(response) => {
|
||||
if let Some(_) = expects_response {
|
||||
let _ = send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::to_string(&response).unwrap()),
|
||||
metadata,
|
||||
},
|
||||
None, // payload will be attached here if created in parse_command
|
||||
);
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
print_to_terminal(0, &format!("app-store: got error {}", e));
|
||||
if let Some(_) = expects_response {
|
||||
let error = AppTrackerResponse::Error {
|
||||
error: format!("{}", e),
|
||||
};
|
||||
let _ = send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::to_string(&error).unwrap()),
|
||||
metadata,
|
||||
},
|
||||
None,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Response((response, _)) => {
|
||||
// only expecting NewFromRemote for apps we've requested
|
||||
match serde_json::from_str(&response.ipc.unwrap_or_default()) {
|
||||
Ok(AppTrackerResponse::NewFromRemote { package_id }) => {
|
||||
if let Some(install_from) = state.requested_packages.remove(&package_id)
|
||||
{
|
||||
if install_from == source.node {
|
||||
// auto-take zip from payload and request ourself with New
|
||||
let _ = send_request(
|
||||
&our,
|
||||
&Request {
|
||||
inherit: true, // will inherit payload!
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
serde_json::to_string(&AppTrackerRequest::New {
|
||||
package: package_id.package().into(),
|
||||
mirror: true,
|
||||
})
|
||||
.unwrap(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
print_to_terminal(
|
||||
0,
|
||||
&format!(
|
||||
"app-store: got install response from bad source: {}",
|
||||
install_from
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
err => {
|
||||
print_to_terminal(
|
||||
0,
|
||||
&format!("app-store: got unexpected response {:?}", err),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"package": "app_tracker",
|
||||
"publisher": "uqbar",
|
||||
"desc": "A package manager + app store. This JSON field is optional and you can add whatever you want in addition to this."
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
### App Tracker: our built-in package manager that lives in userspace
|
||||
|
||||
*note: 'app' and 'package' will be used interchangeably, but they are the same thing. generally, end users should see 'apps', and developers and the system itself should see 'packages'*
|
||||
|
||||
### Backend
|
||||
|
||||
Tracker requires full read-write to filesystem, along with caps for every other distro app and runtime module. It takes all the caps because it needs the ability to grant them to packages we install!
|
||||
|
||||
In order to load in the currently installed packages, Tracker will access the VFS and read from a hardcoded set of
|
||||
|
||||
### Frontend
|
||||
|
||||
Tracker will present a frontend that shows all the apps you currently have installed. You can see some metadata about them, and uninstall them from this list.
|
||||
|
||||
Tracker will contain a "store" to browse for new apps to install. TODO
|
@ -1,336 +0,0 @@
|
||||
cargo_component_bindings::generate!();
|
||||
|
||||
use bindings::{
|
||||
component::uq_process::types::*, get_capability, get_payload, print_to_terminal, receive,
|
||||
send_request, send_response, Guest,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod kernel_types;
|
||||
use kernel_types as kt;
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod process_lib;
|
||||
mod transfer_lib;
|
||||
|
||||
struct Component;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum AppTrackerRequest {
|
||||
New { package: String },
|
||||
Install { package: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum AppTrackerResponse {
|
||||
New { package: String },
|
||||
Install { package: String },
|
||||
Error { error: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PackageManifestEntry {
|
||||
pub process_name: String,
|
||||
pub process_wasm_path: String,
|
||||
pub on_panic: kt::OnPanic,
|
||||
pub request_networking: bool,
|
||||
pub request_messaging: Vec<String>,
|
||||
pub public: bool,
|
||||
}
|
||||
|
||||
fn parse_command(our: &Address, request_string: String) -> anyhow::Result<AppTrackerResponse> {
|
||||
match serde_json::from_str(&request_string)? {
|
||||
AppTrackerRequest::New { package } => {
|
||||
let Some(payload) = get_payload() else {
|
||||
return Err(anyhow::anyhow!("no payload"));
|
||||
};
|
||||
|
||||
let vfs_address = Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::from_str("vfs:sys:uqbar").unwrap(),
|
||||
};
|
||||
|
||||
let _ = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
false,
|
||||
Some(
|
||||
serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::New,
|
||||
})
|
||||
.unwrap(),
|
||||
),
|
||||
None,
|
||||
None,
|
||||
5,
|
||||
)?;
|
||||
|
||||
// add zip bytes
|
||||
let _ = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
true,
|
||||
Some(
|
||||
serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::Add {
|
||||
full_path: package.clone().into(),
|
||||
entry_type: kt::AddEntryType::ZipArchive,
|
||||
},
|
||||
})
|
||||
.unwrap(),
|
||||
),
|
||||
None,
|
||||
Some(&payload),
|
||||
5,
|
||||
)?;
|
||||
Ok(AppTrackerResponse::New { package })
|
||||
}
|
||||
AppTrackerRequest::Install { package } => {
|
||||
let vfs_address = Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::from_str("vfs:sys:uqbar").unwrap(),
|
||||
};
|
||||
|
||||
let _ = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
false,
|
||||
Some(
|
||||
serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::GetEntry("/manifest.json".into()),
|
||||
})
|
||||
.unwrap(),
|
||||
),
|
||||
None,
|
||||
None,
|
||||
5,
|
||||
)?;
|
||||
let Some(payload) = get_payload() else {
|
||||
return Err(anyhow::anyhow!("no payload"));
|
||||
};
|
||||
let manifest = String::from_utf8(payload.bytes)?;
|
||||
let manifest = serde_json::from_str::<Vec<PackageManifestEntry>>(&manifest).unwrap();
|
||||
|
||||
for entry in manifest {
|
||||
let path = if entry.process_wasm_path.starts_with("/") {
|
||||
entry.process_wasm_path
|
||||
} else {
|
||||
format!("/{}", entry.process_wasm_path)
|
||||
};
|
||||
|
||||
let (_, hash_response) = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
false,
|
||||
Some(
|
||||
serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::GetHash(path.clone()),
|
||||
})
|
||||
.unwrap(),
|
||||
),
|
||||
None,
|
||||
None,
|
||||
5,
|
||||
)?;
|
||||
|
||||
let Message::Response((Response { ipc: Some(ipc), .. }, _)) = hash_response else {
|
||||
return Err(anyhow::anyhow!("bad vfs response"));
|
||||
};
|
||||
let kt::VfsResponse::GetHash(Some(hash)) = serde_json::from_str(&ipc).unwrap() else {
|
||||
return Err(anyhow::anyhow!("no hash in vfs"));
|
||||
};
|
||||
|
||||
// build initial caps
|
||||
let mut initial_capabilities: HashSet<kt::SignedCapability> = HashSet::new();
|
||||
if entry.request_networking {
|
||||
let Some(networking_cap) = get_capability(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::from_str("kernel:sys:uqbar").unwrap(),
|
||||
},
|
||||
&"\"network\"".to_string(),
|
||||
) else {
|
||||
return Err(anyhow::anyhow!("app_tracker: no net cap"));
|
||||
};
|
||||
initial_capabilities.insert(kt::de_wit_signed_capability(networking_cap));
|
||||
}
|
||||
let Some(read_cap) = get_capability(
|
||||
&vfs_address.clone(),
|
||||
&serde_json::to_string(&serde_json::json!({
|
||||
"kind": "read",
|
||||
"drive": package,
|
||||
})).unwrap(),
|
||||
) else {
|
||||
return Err(anyhow::anyhow!("app_tracker: no read cap"));
|
||||
};
|
||||
initial_capabilities.insert(kt::de_wit_signed_capability(read_cap));
|
||||
let Some(write_cap) = get_capability(
|
||||
&vfs_address.clone(),
|
||||
&serde_json::to_string(&serde_json::json!({
|
||||
"kind": "write",
|
||||
"drive": package,
|
||||
})).unwrap(),
|
||||
) else {
|
||||
return Err(anyhow::anyhow!("app_tracker: no write cap"));
|
||||
};
|
||||
initial_capabilities.insert(kt::de_wit_signed_capability(write_cap));
|
||||
let mut public = false;
|
||||
|
||||
let entry_process_id = match ProcessId::from_str(
|
||||
&[entry.process_name.clone(), ":".into(), package.clone()].concat(),
|
||||
) {
|
||||
Ok(process_id) => process_id,
|
||||
Err(_) => {
|
||||
return Err(anyhow::anyhow!("app_tracker: invalid process id!"));
|
||||
}
|
||||
};
|
||||
|
||||
for process_name in &entry.request_messaging {
|
||||
let Ok(parsed_process_id) = ProcessId::from_str(&process_name) else {
|
||||
// TODO handle arbitrary caps here
|
||||
continue;
|
||||
};
|
||||
let Some(messaging_cap) = get_capability(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: parsed_process_id.clone(),
|
||||
},
|
||||
&"\"messaging\"".into()
|
||||
) else {
|
||||
print_to_terminal(0, &format!("app_tracker: no cap for {} to give away!", process_name));
|
||||
continue;
|
||||
};
|
||||
initial_capabilities.insert(kt::de_wit_signed_capability(messaging_cap));
|
||||
}
|
||||
|
||||
let process_id = format!("{}:{}", entry.process_name, package.clone());
|
||||
let Ok(parsed_new_process_id) = ProcessId::from_str(&process_id) else {
|
||||
return Err(anyhow::anyhow!("app_tracker: invalid process id!"));
|
||||
};
|
||||
let _ = process_lib::send_request(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::from_str("kernel:sys:uqbar").unwrap(),
|
||||
},
|
||||
false,
|
||||
Some(
|
||||
serde_json::to_string(&kt::KernelCommand::KillProcess(
|
||||
kt::ProcessId::de_wit(parsed_new_process_id.clone()),
|
||||
))
|
||||
.unwrap(),
|
||||
),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
// kernel start process takes bytes as payload + wasm_bytes_handle...
|
||||
// reconsider perhaps
|
||||
let (_, _bytes_response) = process_lib::send_and_await_response(
|
||||
&vfs_address,
|
||||
false,
|
||||
Some(
|
||||
serde_json::to_string(&kt::VfsRequest {
|
||||
drive: package.clone(),
|
||||
action: kt::VfsAction::GetEntry(path),
|
||||
})
|
||||
.unwrap(),
|
||||
),
|
||||
None,
|
||||
None,
|
||||
5,
|
||||
)?;
|
||||
|
||||
let Some(payload) = get_payload() else {
|
||||
return Err(anyhow::anyhow!("no wasm bytes payload."));
|
||||
};
|
||||
|
||||
let _ = process_lib::send_and_await_response(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::from_str("kernel:sys:uqbar").unwrap(),
|
||||
},
|
||||
false,
|
||||
Some(
|
||||
serde_json::to_string(&kt::KernelCommand::StartProcess {
|
||||
id: kt::ProcessId::de_wit(parsed_new_process_id),
|
||||
wasm_bytes_handle: hash,
|
||||
on_panic: entry.on_panic,
|
||||
initial_capabilities,
|
||||
public,
|
||||
})
|
||||
.unwrap(),
|
||||
),
|
||||
None,
|
||||
Some(&payload),
|
||||
5,
|
||||
)?;
|
||||
}
|
||||
Ok(AppTrackerResponse::Install { package })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Guest for Component {
|
||||
fn init(our: Address) {
|
||||
assert_eq!(our.process.to_string(), "app_tracker:app_tracker:uqbar");
|
||||
print_to_terminal(0, &format!("app_tracker: start"));
|
||||
loop {
|
||||
let message = match receive() {
|
||||
Ok((source, message)) => {
|
||||
if our.node != source.node {
|
||||
continue;
|
||||
}
|
||||
message
|
||||
}
|
||||
Err((error, _context)) => {
|
||||
print_to_terminal(0, &format!("net error: {:?}!", error.kind));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match message {
|
||||
Message::Request(Request {
|
||||
ipc,
|
||||
expects_response,
|
||||
metadata,
|
||||
..
|
||||
}) => {
|
||||
let Some(command) = ipc else {
|
||||
continue;
|
||||
};
|
||||
match parse_command(&our, command) {
|
||||
Ok(response) => {
|
||||
if let Some(_) = expects_response {
|
||||
let _ = send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::to_string(&response).unwrap()),
|
||||
metadata,
|
||||
},
|
||||
None,
|
||||
);
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
print_to_terminal(0, &format!("app_tracker: got error {}", e));
|
||||
if let Some(_) = expects_response {
|
||||
let error = AppTrackerResponse::Error {
|
||||
error: format!("{}", e),
|
||||
};
|
||||
let _ = send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::to_string(&error).unwrap()),
|
||||
metadata,
|
||||
},
|
||||
None,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
"request_networking": false,
|
||||
"request_messaging": [
|
||||
"http_bindings:http_bindings:uqbar",
|
||||
"app_tracker:app_tracker:uqbar",
|
||||
"main:app_store:uqbar",
|
||||
"http_server:sys:uqbar"
|
||||
],
|
||||
"public": false
|
||||
|
@ -1971,7 +1971,7 @@ async fn make_event_loop(
|
||||
t::Printout {
|
||||
verbosity: 0,
|
||||
content: format!(
|
||||
"event loop: process {:?} doesn't have capability to send networked messages",
|
||||
"event loop: process {} doesn't have capability to send networked messages",
|
||||
kernel_message.source.process
|
||||
)
|
||||
}
|
||||
@ -2007,7 +2007,7 @@ async fn make_event_loop(
|
||||
t::Printout {
|
||||
verbosity: 0,
|
||||
content: format!(
|
||||
"event loop: process {:?} doesn't have capability to message process {:?}",
|
||||
"event loop: process {} doesn't have capability to message process {}",
|
||||
kernel_message.source.process, kernel_message.target.process
|
||||
)
|
||||
}
|
||||
|
@ -3,6 +3,49 @@ use serde::{Deserialize, Serialize};
|
||||
use super::bindings::component::uq_process::types::*;
|
||||
use super::bindings::{Address, Payload, ProcessId, SendError};
|
||||
|
||||
#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PackageId {
|
||||
pub package_name: String,
|
||||
pub publisher_node: String,
|
||||
}
|
||||
|
||||
impl PackageId {
|
||||
pub fn new(package_name: &str, publisher_node: &str) -> Self {
|
||||
PackageId {
|
||||
package_name: package_name.into(),
|
||||
publisher_node: publisher_node.into(),
|
||||
}
|
||||
}
|
||||
pub fn from_str(input: &str) -> Result<Self, ProcessIdParseError> {
|
||||
// split string on colons into 2 segments
|
||||
let mut segments = input.split(':');
|
||||
let package_name = segments
|
||||
.next()
|
||||
.ok_or(ProcessIdParseError::MissingField)?
|
||||
.to_string();
|
||||
let publisher_node = segments
|
||||
.next()
|
||||
.ok_or(ProcessIdParseError::MissingField)?
|
||||
.to_string();
|
||||
if segments.next().is_some() {
|
||||
return Err(ProcessIdParseError::TooManyColons);
|
||||
}
|
||||
Ok(PackageId {
|
||||
package_name,
|
||||
publisher_node,
|
||||
})
|
||||
}
|
||||
pub fn to_string(&self) -> String {
|
||||
[self.package_name.as_str(), self.publisher_node.as_str()].join(":")
|
||||
}
|
||||
pub fn package(&self) -> &str {
|
||||
&self.package_name
|
||||
}
|
||||
pub fn publisher_node(&self) -> &str {
|
||||
&self.publisher_node
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl ProcessId {
|
||||
/// generates a random u64 number if process_name is not declared
|
||||
@ -80,6 +123,28 @@ pub enum ProcessIdParseError {
|
||||
MissingField,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ProcessIdParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
ProcessIdParseError::TooManyColons => "Too many colons in ProcessId string",
|
||||
ProcessIdParseError::MissingField => "Missing field in ProcessId string",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ProcessIdParseError {
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
ProcessIdParseError::TooManyColons => "Too many colons in ProcessId string",
|
||||
ProcessIdParseError::MissingField => "Missing field in ProcessId string",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_and_await_response(
|
||||
target: &Address,
|
||||
inherit: bool,
|
||||
|
Loading…
Reference in New Issue
Block a user