mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
fixes from testing, client side device filtering for better fingerprinting resistance
This commit is contained in:
@@ -4,7 +4,7 @@ parse_essential_db_info() {
|
|||||||
DB_DUMP="/tmp/startos_db.json"
|
DB_DUMP="/tmp/startos_db.json"
|
||||||
|
|
||||||
if command -v start-cli >/dev/null 2>&1; then
|
if command -v start-cli >/dev/null 2>&1; then
|
||||||
start-cli db dump > "$DB_DUMP" 2>/dev/null || return 1
|
timeout 30 start-cli db dump > "$DB_DUMP" 2>/dev/null || return 1
|
||||||
else
|
else
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
661
core/Cargo.lock
generated
661
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ use std::sync::Arc;
|
|||||||
use cookie::{Cookie, Expiration, SameSite};
|
use cookie::{Cookie, Expiration, SameSite};
|
||||||
use cookie_store::CookieStore;
|
use cookie_store::CookieStore;
|
||||||
use http::HeaderMap;
|
use http::HeaderMap;
|
||||||
|
use imbl::OrdMap;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
@@ -238,10 +239,16 @@ impl CliContext {
|
|||||||
where
|
where
|
||||||
Self: CallRemote<RemoteContext>,
|
Self: CallRemote<RemoteContext>,
|
||||||
{
|
{
|
||||||
<Self as CallRemote<RemoteContext, Empty>>::call_remote(&self, method, params, Empty {})
|
<Self as CallRemote<RemoteContext, Empty>>::call_remote(
|
||||||
.await
|
&self,
|
||||||
.map_err(Error::from)
|
method,
|
||||||
.with_ctx(|e| (e.kind, method))
|
OrdMap::new(),
|
||||||
|
params,
|
||||||
|
Empty {},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(Error::from)
|
||||||
|
.with_ctx(|e| (e.kind, method))
|
||||||
}
|
}
|
||||||
pub async fn call_remote_with<RemoteContext, T>(
|
pub async fn call_remote_with<RemoteContext, T>(
|
||||||
&self,
|
&self,
|
||||||
@@ -252,10 +259,16 @@ impl CliContext {
|
|||||||
where
|
where
|
||||||
Self: CallRemote<RemoteContext, T>,
|
Self: CallRemote<RemoteContext, T>,
|
||||||
{
|
{
|
||||||
<Self as CallRemote<RemoteContext, T>>::call_remote(&self, method, params, extra)
|
<Self as CallRemote<RemoteContext, T>>::call_remote(
|
||||||
.await
|
&self,
|
||||||
.map_err(Error::from)
|
method,
|
||||||
.with_ctx(|e| (e.kind, method))
|
OrdMap::new(),
|
||||||
|
params,
|
||||||
|
extra,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(Error::from)
|
||||||
|
.with_ctx(|e| (e.kind, method))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl AsRef<Jwk> for CliContext {
|
impl AsRef<Jwk> for CliContext {
|
||||||
@@ -292,7 +305,13 @@ impl AsRef<Client> for CliContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CallRemote<RpcContext> for CliContext {
|
impl CallRemote<RpcContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(
|
||||||
|
&self,
|
||||||
|
method: &str,
|
||||||
|
_: OrdMap<&'static str, Value>,
|
||||||
|
params: Value,
|
||||||
|
_: Empty,
|
||||||
|
) -> Result<Value, RpcError> {
|
||||||
if let Ok(local) = read_file_to_string(RpcContext::LOCAL_AUTH_COOKIE_PATH).await {
|
if let Ok(local) = read_file_to_string(RpcContext::LOCAL_AUTH_COOKIE_PATH).await {
|
||||||
self.cookie_store
|
self.cookie_store
|
||||||
.lock()
|
.lock()
|
||||||
@@ -319,7 +338,13 @@ impl CallRemote<RpcContext> for CliContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<DiagnosticContext> for CliContext {
|
impl CallRemote<DiagnosticContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(
|
||||||
|
&self,
|
||||||
|
method: &str,
|
||||||
|
_: OrdMap<&'static str, Value>,
|
||||||
|
params: Value,
|
||||||
|
_: Empty,
|
||||||
|
) -> Result<Value, RpcError> {
|
||||||
crate::middleware::auth::signature::call_remote(
|
crate::middleware::auth::signature::call_remote(
|
||||||
self,
|
self,
|
||||||
self.rpc_url.clone(),
|
self.rpc_url.clone(),
|
||||||
@@ -332,7 +357,13 @@ impl CallRemote<DiagnosticContext> for CliContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<InitContext> for CliContext {
|
impl CallRemote<InitContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(
|
||||||
|
&self,
|
||||||
|
method: &str,
|
||||||
|
_: OrdMap<&'static str, Value>,
|
||||||
|
params: Value,
|
||||||
|
_: Empty,
|
||||||
|
) -> Result<Value, RpcError> {
|
||||||
crate::middleware::auth::signature::call_remote(
|
crate::middleware::auth::signature::call_remote(
|
||||||
self,
|
self,
|
||||||
self.rpc_url.clone(),
|
self.rpc_url.clone(),
|
||||||
@@ -345,7 +376,13 @@ impl CallRemote<InitContext> for CliContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<SetupContext> for CliContext {
|
impl CallRemote<SetupContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(
|
||||||
|
&self,
|
||||||
|
method: &str,
|
||||||
|
_: OrdMap<&'static str, Value>,
|
||||||
|
params: Value,
|
||||||
|
_: Empty,
|
||||||
|
) -> Result<Value, RpcError> {
|
||||||
crate::middleware::auth::signature::call_remote(
|
crate::middleware::auth::signature::call_remote(
|
||||||
self,
|
self,
|
||||||
self.rpc_url.clone(),
|
self.rpc_url.clone(),
|
||||||
@@ -358,7 +395,13 @@ impl CallRemote<SetupContext> for CliContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<InstallContext> for CliContext {
|
impl CallRemote<InstallContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(
|
||||||
|
&self,
|
||||||
|
method: &str,
|
||||||
|
_: OrdMap<&'static str, Value>,
|
||||||
|
params: Value,
|
||||||
|
_: Empty,
|
||||||
|
) -> Result<Value, RpcError> {
|
||||||
crate::middleware::auth::signature::call_remote(
|
crate::middleware::auth::signature::call_remote(
|
||||||
self,
|
self,
|
||||||
self.rpc_url.clone(),
|
self.rpc_url.clone(),
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ use crate::db::model::package::TaskSeverity;
|
|||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
use crate::disk::mount::filesystem::bind::Bind;
|
use crate::disk::mount::filesystem::bind::Bind;
|
||||||
use crate::disk::mount::filesystem::block_dev::BlockDev;
|
use crate::disk::mount::filesystem::block_dev::BlockDev;
|
||||||
use crate::disk::mount::filesystem::loop_dev::LoopDev;
|
|
||||||
use crate::disk::mount::filesystem::{FileSystem, ReadOnly};
|
use crate::disk::mount::filesystem::{FileSystem, ReadOnly};
|
||||||
use crate::disk::mount::guard::MountGuard;
|
use crate::disk::mount::guard::MountGuard;
|
||||||
use crate::init::{InitResult, check_time_is_synchronized};
|
use crate::init::{InitResult, check_time_is_synchronized};
|
||||||
@@ -586,8 +585,14 @@ impl RpcContext {
|
|||||||
where
|
where
|
||||||
Self: CallRemote<RemoteContext>,
|
Self: CallRemote<RemoteContext>,
|
||||||
{
|
{
|
||||||
<Self as CallRemote<RemoteContext, Empty>>::call_remote(&self, method, params, Empty {})
|
<Self as CallRemote<RemoteContext, Empty>>::call_remote(
|
||||||
.await
|
&self,
|
||||||
|
method,
|
||||||
|
OrdMap::new(),
|
||||||
|
params,
|
||||||
|
Empty {},
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
pub async fn call_remote_with<RemoteContext, T>(
|
pub async fn call_remote_with<RemoteContext, T>(
|
||||||
&self,
|
&self,
|
||||||
@@ -598,7 +603,14 @@ impl RpcContext {
|
|||||||
where
|
where
|
||||||
Self: CallRemote<RemoteContext, T>,
|
Self: CallRemote<RemoteContext, T>,
|
||||||
{
|
{
|
||||||
<Self as CallRemote<RemoteContext, T>>::call_remote(&self, method, params, extra).await
|
<Self as CallRemote<RemoteContext, T>>::call_remote(
|
||||||
|
&self,
|
||||||
|
method,
|
||||||
|
OrdMap::new(),
|
||||||
|
params,
|
||||||
|
extra,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl AsRef<Client> for RpcContext {
|
impl AsRef<Client> for RpcContext {
|
||||||
|
|||||||
@@ -416,6 +416,51 @@ impl<T: Map> Model<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Map> Model<T>
|
||||||
|
where
|
||||||
|
T::Key: FromStr,
|
||||||
|
Error: From<<T::Key as FromStr>::Err>,
|
||||||
|
{
|
||||||
|
/// Retains only the elements specified by the predicate.
|
||||||
|
/// The predicate can mutate the values and returns whether to keep each entry.
|
||||||
|
pub fn retain<F>(&mut self, mut f: F) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
F: FnMut(&T::Key, &mut Model<T::Value>) -> Result<bool, Error>,
|
||||||
|
{
|
||||||
|
let mut to_remove = Vec::new();
|
||||||
|
|
||||||
|
match &mut self.value {
|
||||||
|
Value::Object(o) => {
|
||||||
|
for (k, v) in o.iter_mut() {
|
||||||
|
let key = T::Key::from_str(&**k)?;
|
||||||
|
if !f(&key, patch_db::ModelExt::value_as_mut(v))? {
|
||||||
|
to_remove.push(k.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v => {
|
||||||
|
use serde::de::Error;
|
||||||
|
return Err(patch_db::value::Error {
|
||||||
|
source: patch_db::value::ErrorSource::custom(format!(
|
||||||
|
"expected object found {v}"
|
||||||
|
)),
|
||||||
|
kind: patch_db::value::ErrorKind::Deserialization,
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove entries that didn't pass the filter
|
||||||
|
if let Value::Object(o) = &mut self.value {
|
||||||
|
for k in to_remove {
|
||||||
|
o.remove(&k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct JsonKey<T>(pub T);
|
pub struct JsonKey<T>(pub T);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use chrono::Utc;
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cookie::{Cookie, Expiration, SameSite};
|
use cookie::{Cookie, Expiration, SameSite};
|
||||||
use http::HeaderMap;
|
use http::HeaderMap;
|
||||||
|
use imbl::OrdMap;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use patch_db::PatchDb;
|
use patch_db::PatchDb;
|
||||||
use patch_db::json_ptr::ROOT;
|
use patch_db::json_ptr::ROOT;
|
||||||
@@ -171,6 +172,7 @@ impl CallRemote<RegistryContext> for CliContext {
|
|||||||
async fn call_remote(
|
async fn call_remote(
|
||||||
&self,
|
&self,
|
||||||
mut method: &str,
|
mut method: &str,
|
||||||
|
_: OrdMap<&'static str, Value>,
|
||||||
params: Value,
|
params: Value,
|
||||||
_: Empty,
|
_: Empty,
|
||||||
) -> Result<Value, RpcError> {
|
) -> Result<Value, RpcError> {
|
||||||
@@ -240,14 +242,21 @@ impl CallRemote<RegistryContext, RegistryUrlParams> for RpcContext {
|
|||||||
async fn call_remote(
|
async fn call_remote(
|
||||||
&self,
|
&self,
|
||||||
mut method: &str,
|
mut method: &str,
|
||||||
|
metadata: OrdMap<&'static str, Value>,
|
||||||
params: Value,
|
params: Value,
|
||||||
RegistryUrlParams { mut registry }: RegistryUrlParams,
|
RegistryUrlParams { mut registry }: RegistryUrlParams,
|
||||||
) -> Result<Value, RpcError> {
|
) -> Result<Value, RpcError> {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert(
|
let mut device_info = None;
|
||||||
DEVICE_INFO_HEADER,
|
if metadata
|
||||||
DeviceInfo::load(self).await?.to_header_value(),
|
.get("get_device_info")
|
||||||
);
|
.and_then(|m| m.as_bool())
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
let di = DeviceInfo::load(self).await?;
|
||||||
|
headers.insert(DEVICE_INFO_HEADER, di.to_header_value());
|
||||||
|
device_info = Some(di);
|
||||||
|
}
|
||||||
|
|
||||||
registry
|
registry
|
||||||
.path_segments_mut()
|
.path_segments_mut()
|
||||||
@@ -258,15 +267,21 @@ impl CallRemote<RegistryContext, RegistryUrlParams> for RpcContext {
|
|||||||
method = method.strip_prefix("registry.").unwrap_or(method);
|
method = method.strip_prefix("registry.").unwrap_or(method);
|
||||||
let sig_context = registry.host_str().map(InternedString::from);
|
let sig_context = registry.host_str().map(InternedString::from);
|
||||||
|
|
||||||
crate::middleware::auth::signature::call_remote(
|
let mut res = crate::middleware::auth::signature::call_remote(
|
||||||
self,
|
self,
|
||||||
registry,
|
registry,
|
||||||
headers,
|
headers,
|
||||||
sig_context.as_deref(),
|
sig_context.as_deref(),
|
||||||
method,
|
method,
|
||||||
params,
|
params.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
|
|
||||||
|
if let Some(device_info) = device_info {
|
||||||
|
device_info.filter_for_hardware(method, params, &mut res)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::identity;
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use axum::extract::Request;
|
use axum::extract::Request;
|
||||||
@@ -7,6 +6,8 @@ use axum::response::Response;
|
|||||||
use exver::{Version, VersionRange};
|
use exver::{Version, VersionRange};
|
||||||
use http::HeaderValue;
|
use http::HeaderValue;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
use rpc_toolkit::yajrc::RpcMethod;
|
||||||
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -15,8 +16,13 @@ use url::Url;
|
|||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
|
use crate::registry::os::index::OsVersionInfoMap;
|
||||||
|
use crate::registry::package::get::{
|
||||||
|
GetPackageParams, GetPackageResponse, GetPackageResponseFull, PackageDetailLevel,
|
||||||
|
};
|
||||||
|
use crate::registry::package::index::PackageVersionInfo;
|
||||||
use crate::util::VersionString;
|
use crate::util::VersionString;
|
||||||
use crate::util::lshw::{LshwDevice, LshwDisplay, LshwProcessor};
|
use crate::util::lshw::LshwDevice;
|
||||||
use crate::version::VersionT;
|
use crate::version::VersionT;
|
||||||
|
|
||||||
pub const DEVICE_INFO_HEADER: &str = "X-StartOS-Device-Info";
|
pub const DEVICE_INFO_HEADER: &str = "X-StartOS-Device-Info";
|
||||||
@@ -25,13 +31,13 @@ pub const DEVICE_INFO_HEADER: &str = "X-StartOS-Device-Info";
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DeviceInfo {
|
pub struct DeviceInfo {
|
||||||
pub os: OsInfo,
|
pub os: OsInfo,
|
||||||
pub hardware: HardwareInfo,
|
pub hardware: Option<HardwareInfo>,
|
||||||
}
|
}
|
||||||
impl DeviceInfo {
|
impl DeviceInfo {
|
||||||
pub async fn load(ctx: &RpcContext) -> Result<Self, Error> {
|
pub async fn load(ctx: &RpcContext) -> Result<Self, Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
os: OsInfo::from(ctx),
|
os: OsInfo::from(ctx),
|
||||||
hardware: HardwareInfo::load(ctx).await?,
|
hardware: Some(HardwareInfo::load(ctx).await?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,21 +47,13 @@ impl DeviceInfo {
|
|||||||
url.query_pairs_mut()
|
url.query_pairs_mut()
|
||||||
.append_pair("os.version", &self.os.version.to_string())
|
.append_pair("os.version", &self.os.version.to_string())
|
||||||
.append_pair("os.compat", &self.os.compat.to_string())
|
.append_pair("os.compat", &self.os.compat.to_string())
|
||||||
.append_pair("os.platform", &*self.os.platform)
|
.append_pair("os.platform", &*self.os.platform);
|
||||||
.append_pair("hardware.arch", &*self.hardware.arch)
|
|
||||||
.append_pair("hardware.ram", &self.hardware.ram.to_string());
|
|
||||||
|
|
||||||
for device in &self.hardware.devices {
|
|
||||||
url.query_pairs_mut().append_pair(
|
|
||||||
&format!("hardware.device.{}", device.class()),
|
|
||||||
device.product(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
HeaderValue::from_str(url.query().unwrap_or_default()).unwrap()
|
HeaderValue::from_str(url.query().unwrap_or_default()).unwrap()
|
||||||
}
|
}
|
||||||
pub fn from_header_value(header: &HeaderValue) -> Result<Self, Error> {
|
pub fn from_header_value(header: &HeaderValue) -> Result<Self, Error> {
|
||||||
let query: BTreeMap<_, _> = form_urlencoded::parse(header.as_bytes()).collect();
|
let query: BTreeMap<_, _> = form_urlencoded::parse(header.as_bytes()).collect();
|
||||||
|
let has_hw_info = query.keys().any(|k| k.starts_with("hardware."));
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
os: OsInfo {
|
os: OsInfo {
|
||||||
version: query
|
version: query
|
||||||
@@ -69,35 +67,120 @@ impl DeviceInfo {
|
|||||||
.deref()
|
.deref()
|
||||||
.into(),
|
.into(),
|
||||||
},
|
},
|
||||||
hardware: HardwareInfo {
|
hardware: has_hw_info
|
||||||
arch: query
|
.then(|| {
|
||||||
.get("hardware.arch")
|
Ok::<_, Error>(HardwareInfo {
|
||||||
.or_not_found("hardware.arch")?
|
arch: query
|
||||||
.parse()?,
|
.get("hardware.arch")
|
||||||
ram: query
|
.or_not_found("hardware.arch")?
|
||||||
.get("hardware.ram")
|
.parse()?,
|
||||||
.or_not_found("hardware.ram")?
|
ram: query
|
||||||
.parse()?,
|
.get("hardware.ram")
|
||||||
devices: identity(query)
|
.or_not_found("hardware.ram")?
|
||||||
.split_off("hardware.device.")
|
.parse()?,
|
||||||
.into_iter()
|
devices: None,
|
||||||
.filter_map(|(k, v)| match k.strip_prefix("hardware.device.") {
|
|
||||||
Some("processor") => Some(LshwDevice::Processor(LshwProcessor {
|
|
||||||
product: v.into_owned(),
|
|
||||||
})),
|
|
||||||
Some("display") => Some(LshwDevice::Display(LshwDisplay {
|
|
||||||
product: v.into_owned(),
|
|
||||||
})),
|
|
||||||
Some(class) => {
|
|
||||||
tracing::warn!("unknown device class: {class}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
})
|
||||||
.collect(),
|
})
|
||||||
},
|
.transpose()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub fn filter_for_hardware(
|
||||||
|
&self,
|
||||||
|
method: &str,
|
||||||
|
params: Value,
|
||||||
|
res: &mut Value,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match method {
|
||||||
|
"package.get" => {
|
||||||
|
let params: Model<GetPackageParams> = ModelExt::from_value(params);
|
||||||
|
|
||||||
|
let other = params.as_other_versions().de()?;
|
||||||
|
if params.as_id().transpose_ref().is_some() {
|
||||||
|
if other.unwrap_or_default() == PackageDetailLevel::Full {
|
||||||
|
self.filter_package_get_full(ModelExt::value_as_mut(res))?;
|
||||||
|
} else {
|
||||||
|
self.filter_package_get(ModelExt::value_as_mut(res))?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (_, v) in res.as_object_mut().into_iter().flat_map(|o| o.iter_mut()) {
|
||||||
|
if other.unwrap_or_default() == PackageDetailLevel::Full {
|
||||||
|
self.filter_package_get_full(ModelExt::value_as_mut(v))?;
|
||||||
|
} else {
|
||||||
|
self.filter_package_get(ModelExt::value_as_mut(v))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
"os.version.get" => self.filter_os_version(ModelExt::value_as_mut(res)),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_package_versions(
|
||||||
|
&self,
|
||||||
|
versions: &mut Model<BTreeMap<VersionString, PackageVersionInfo>>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let alpha_17: Version = "0.4.0-alpha.17".parse()?;
|
||||||
|
|
||||||
|
// Filter package versions using for_device
|
||||||
|
versions.retain(|_, info| info.for_device(self))?;
|
||||||
|
|
||||||
|
// Alpha.17 compatibility: add legacy fields
|
||||||
|
if self.os.version <= alpha_17 {
|
||||||
|
for (_, info) in versions.as_entries_mut()? {
|
||||||
|
let v = info.as_value_mut();
|
||||||
|
if let Some(mut tup) = v["s9pks"].get(0).cloned() {
|
||||||
|
v["s9pk"] = tup[1].take();
|
||||||
|
v["hardwareRequirements"] = tup[0].take();
|
||||||
|
v["s9pk"]["url"] = v["s9pk"]["urls"][0].clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_package_get(&self, res: &mut Model<GetPackageResponse>) -> Result<(), Error> {
|
||||||
|
self.filter_package_versions(res.as_best_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_package_get_full(
|
||||||
|
&self,
|
||||||
|
res: &mut Model<GetPackageResponseFull>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.filter_package_versions(res.as_best_mut())?;
|
||||||
|
self.filter_package_versions(res.as_other_versions_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_os_version(&self, res: &mut Model<OsVersionInfoMap>) -> Result<(), Error> {
|
||||||
|
let alpha_17: Version = "0.4.0-alpha.17".parse()?;
|
||||||
|
|
||||||
|
// Filter OS versions based on source_version compatibility
|
||||||
|
res.retain(|_, info| {
|
||||||
|
let source_version = info.as_source_version().de()?;
|
||||||
|
Ok(self.os.version.satisfies(&source_version))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Alpha.17 compatibility: add url field from urls array
|
||||||
|
if self.os.version <= alpha_17 {
|
||||||
|
for (_, info) in res.as_entries_mut()? {
|
||||||
|
let v = info.as_value_mut();
|
||||||
|
for asset_ty in ["iso", "squashfs", "img"] {
|
||||||
|
for (_, asset) in v[asset_ty]
|
||||||
|
.as_object_mut()
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|o| o.iter_mut())
|
||||||
|
{
|
||||||
|
asset["url"] = asset["urls"][0].clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
@@ -127,7 +210,7 @@ pub struct HardwareInfo {
|
|||||||
pub arch: InternedString,
|
pub arch: InternedString,
|
||||||
#[ts(type = "number")]
|
#[ts(type = "number")]
|
||||||
pub ram: u64,
|
pub ram: u64,
|
||||||
pub devices: Vec<LshwDevice>,
|
pub devices: Option<Vec<LshwDevice>>,
|
||||||
}
|
}
|
||||||
impl HardwareInfo {
|
impl HardwareInfo {
|
||||||
pub async fn load(ctx: &RpcContext) -> Result<Self, Error> {
|
pub async fn load(ctx: &RpcContext) -> Result<Self, Error> {
|
||||||
@@ -135,7 +218,7 @@ impl HardwareInfo {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
arch: s.as_arch().de()?,
|
arch: s.as_arch().de()?,
|
||||||
ram: s.as_ram().de()?,
|
ram: s.as_ram().de()?,
|
||||||
devices: s.as_devices().de()?,
|
devices: Some(s.as_devices().de()?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,11 +231,17 @@ pub struct Metadata {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DeviceInfoMiddleware {
|
pub struct DeviceInfoMiddleware {
|
||||||
device_info: Option<HeaderValue>,
|
device_info_header: Option<HeaderValue>,
|
||||||
|
device_info: Option<DeviceInfo>,
|
||||||
|
req: Option<RpcRequest>,
|
||||||
}
|
}
|
||||||
impl DeviceInfoMiddleware {
|
impl DeviceInfoMiddleware {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { device_info: None }
|
Self {
|
||||||
|
device_info_header: None,
|
||||||
|
device_info: None,
|
||||||
|
req: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +252,7 @@ impl Middleware<RegistryContext> for DeviceInfoMiddleware {
|
|||||||
_: &RegistryContext,
|
_: &RegistryContext,
|
||||||
request: &mut Request,
|
request: &mut Request,
|
||||||
) -> Result<(), Response> {
|
) -> Result<(), Response> {
|
||||||
self.device_info = request.headers_mut().remove(DEVICE_INFO_HEADER);
|
self.device_info_header = request.headers_mut().remove(DEVICE_INFO_HEADER);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn process_rpc_request(
|
async fn process_rpc_request(
|
||||||
@@ -174,9 +263,11 @@ impl Middleware<RegistryContext> for DeviceInfoMiddleware {
|
|||||||
) -> Result<(), RpcResponse> {
|
) -> Result<(), RpcResponse> {
|
||||||
async move {
|
async move {
|
||||||
if metadata.get_device_info {
|
if metadata.get_device_info {
|
||||||
if let Some(device_info) = &self.device_info {
|
if let Some(device_info) = &self.device_info_header {
|
||||||
request.params["__DeviceInfo_device_info"] =
|
let device_info = DeviceInfo::from_header_value(device_info)?;
|
||||||
to_value(&DeviceInfo::from_header_value(device_info)?)?;
|
request.params["__DeviceInfo_device_info"] = to_value(&device_info)?;
|
||||||
|
self.device_info = Some(device_info);
|
||||||
|
self.req = Some(request.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,4 +276,19 @@ impl Middleware<RegistryContext> for DeviceInfoMiddleware {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| RpcResponse::from_result(Err(e)))
|
.map_err(|e| RpcResponse::from_result(Err(e)))
|
||||||
}
|
}
|
||||||
|
async fn process_rpc_response(
|
||||||
|
&mut self,
|
||||||
|
_: &RegistryContext,
|
||||||
|
response: &mut RpcResponse,
|
||||||
|
) -> () {
|
||||||
|
if let (Some(req), Some(device_info), Ok(res)) =
|
||||||
|
(&self.req, &self.device_info, &mut response.result)
|
||||||
|
{
|
||||||
|
if let Err(e) =
|
||||||
|
device_info.filter_for_hardware(req.method.as_str(), req.params.clone(), res)
|
||||||
|
{
|
||||||
|
response.result = Err(e).map_err(From::from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ pub async fn add_package(
|
|||||||
let peek = ctx.db.peek().await;
|
let peek = ctx.db.peek().await;
|
||||||
let uploader_guid = peek.as_index().as_signers().get_signer(&uploader)?;
|
let uploader_guid = peek.as_index().as_signers().get_signer(&uploader)?;
|
||||||
|
|
||||||
let ([url], rest) = urls.split_at(1) else {
|
let Some(([url], rest)) = urls.split_at_checked(1) else {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("must specify at least 1 url"),
|
eyre!("must specify at least 1 url"),
|
||||||
ErrorKind::InvalidRequest,
|
ErrorKind::InvalidRequest,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use exver::{ExtendedVersion, Version, VersionRange};
|
use exver::{ExtendedVersion, VersionRange};
|
||||||
use imbl_value::{InternedString, json};
|
use imbl_value::{InternedString, json};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -12,14 +12,11 @@ use crate::PackageId;
|
|||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{FullProgressTracker, ProgressUnits};
|
use crate::progress::{FullProgressTracker, ProgressUnits};
|
||||||
use crate::registry::asset::RegistryAsset;
|
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::device_info::DeviceInfo;
|
use crate::registry::device_info::DeviceInfo;
|
||||||
use crate::registry::package::index::{PackageIndex, PackageVersionInfo};
|
use crate::registry::package::index::{PackageIndex, PackageVersionInfo};
|
||||||
use crate::s9pk::manifest::HardwareRequirements;
|
|
||||||
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
||||||
use crate::s9pk::v2::SIG_CONTEXT;
|
use crate::s9pk::v2::SIG_CONTEXT;
|
||||||
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
|
||||||
use crate::util::VersionString;
|
use crate::util::VersionString;
|
||||||
use crate::util::io::{TrackingIO, to_tmp_path};
|
use crate::util::io::{TrackingIO, to_tmp_path};
|
||||||
use crate::util::serde::{WithIoFormat, display_serializable};
|
use crate::util::serde::{WithIoFormat, display_serializable};
|
||||||
@@ -28,7 +25,7 @@ use crate::util::tui::{choose, choose_custom_display};
|
|||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS, ValueEnum,
|
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS, ValueEnum,
|
||||||
)]
|
)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub enum PackageDetailLevel {
|
pub enum PackageDetailLevel {
|
||||||
None,
|
None,
|
||||||
@@ -48,10 +45,11 @@ pub struct PackageInfoShort {
|
|||||||
pub release_notes: String,
|
pub release_notes: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, TS, Parser)]
|
#[derive(Debug, Deserialize, Serialize, TS, Parser, HasModel)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[command(rename_all = "kebab-case")]
|
#[command(rename_all = "kebab-case")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
pub struct GetPackageParams {
|
pub struct GetPackageParams {
|
||||||
pub id: Option<PackageId>,
|
pub id: Option<PackageId>,
|
||||||
#[ts(type = "string | null")]
|
#[ts(type = "string | null")]
|
||||||
@@ -63,14 +61,14 @@ pub struct GetPackageParams {
|
|||||||
#[arg(skip)]
|
#[arg(skip)]
|
||||||
#[serde(rename = "__DeviceInfo_device_info")]
|
#[serde(rename = "__DeviceInfo_device_info")]
|
||||||
pub device_info: Option<DeviceInfo>,
|
pub device_info: Option<DeviceInfo>,
|
||||||
#[serde(default)]
|
|
||||||
#[arg(default_value = "none")]
|
#[arg(default_value = "none")]
|
||||||
pub other_versions: PackageDetailLevel,
|
pub other_versions: Option<PackageDetailLevel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
#[derive(Debug, Deserialize, Serialize, TS, HasModel)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
pub struct GetPackageResponse {
|
pub struct GetPackageResponse {
|
||||||
#[ts(type = "string[]")]
|
#[ts(type = "string[]")]
|
||||||
pub categories: BTreeSet<InternedString>,
|
pub categories: BTreeSet<InternedString>,
|
||||||
@@ -111,9 +109,10 @@ impl GetPackageResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
#[derive(Debug, Deserialize, Serialize, TS, HasModel)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
pub struct GetPackageResponseFull {
|
pub struct GetPackageResponseFull {
|
||||||
#[ts(type = "string[]")]
|
#[ts(type = "string[]")]
|
||||||
pub categories: BTreeSet<InternedString>,
|
pub categories: BTreeSet<InternedString>,
|
||||||
@@ -137,23 +136,15 @@ impl GetPackageResponseFull {
|
|||||||
pub type GetPackagesResponse = BTreeMap<PackageId, GetPackageResponse>;
|
pub type GetPackagesResponse = BTreeMap<PackageId, GetPackageResponse>;
|
||||||
pub type GetPackagesResponseFull = BTreeMap<PackageId, GetPackageResponseFull>;
|
pub type GetPackagesResponseFull = BTreeMap<PackageId, GetPackageResponseFull>;
|
||||||
|
|
||||||
fn get_matching_models<'a>(
|
fn get_matching_models(
|
||||||
db: &'a Model<PackageIndex>,
|
db: &Model<PackageIndex>,
|
||||||
GetPackageParams {
|
GetPackageParams {
|
||||||
id,
|
id,
|
||||||
source_version,
|
source_version,
|
||||||
device_info,
|
device_info,
|
||||||
..
|
..
|
||||||
}: &GetPackageParams,
|
}: &GetPackageParams,
|
||||||
) -> Result<
|
) -> Result<Vec<(PackageId, ExtendedVersion, Model<PackageVersionInfo>)>, Error> {
|
||||||
Vec<(
|
|
||||||
PackageId,
|
|
||||||
ExtendedVersion,
|
|
||||||
&'a Model<PackageVersionInfo>,
|
|
||||||
Vec<(HardwareRequirements, RegistryAsset<MerkleArchiveCommitment>)>,
|
|
||||||
)>,
|
|
||||||
Error,
|
|
||||||
> {
|
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
if let Some(pkg) = db.as_packages().as_idx(id) {
|
if let Some(pkg) = db.as_packages().as_idx(id) {
|
||||||
vec![(id.clone(), pkg)]
|
vec![(id.clone(), pkg)]
|
||||||
@@ -180,12 +171,16 @@ fn get_matching_models<'a>(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
})? {
|
})? {
|
||||||
|
let mut info = info.clone();
|
||||||
if let Some(device_info) = &device_info {
|
if let Some(device_info) = &device_info {
|
||||||
info.for_device(device_info)?
|
if info.for_device(device_info)? {
|
||||||
|
Some((k.clone(), ExtendedVersion::from(v), info))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Some(info.as_s9pks().de()?)
|
Some((k.clone(), ExtendedVersion::from(v), info))
|
||||||
}
|
}
|
||||||
.map(|assets| (k.clone(), ExtendedVersion::from(v), info, assets))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
@@ -199,31 +194,12 @@ fn get_matching_models<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Result<Value, Error> {
|
pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Result<Value, Error> {
|
||||||
use patch_db::ModelExt;
|
|
||||||
|
|
||||||
let peek = ctx.db.peek().await;
|
let peek = ctx.db.peek().await;
|
||||||
let mut best: BTreeMap<
|
let mut best: BTreeMap<PackageId, BTreeMap<VersionString, Model<PackageVersionInfo>>> =
|
||||||
PackageId,
|
Default::default();
|
||||||
BTreeMap<
|
let mut other: BTreeMap<PackageId, BTreeMap<VersionString, Model<PackageVersionInfo>>> =
|
||||||
VersionString,
|
Default::default();
|
||||||
(
|
for (id, version, info) in get_matching_models(&peek.as_index().as_package(), ¶ms)? {
|
||||||
&Model<PackageVersionInfo>,
|
|
||||||
Vec<(HardwareRequirements, RegistryAsset<MerkleArchiveCommitment>)>,
|
|
||||||
),
|
|
||||||
>,
|
|
||||||
> = Default::default();
|
|
||||||
let mut other: BTreeMap<
|
|
||||||
PackageId,
|
|
||||||
BTreeMap<
|
|
||||||
VersionString,
|
|
||||||
(
|
|
||||||
&Model<PackageVersionInfo>,
|
|
||||||
Vec<(HardwareRequirements, RegistryAsset<MerkleArchiveCommitment>)>,
|
|
||||||
),
|
|
||||||
>,
|
|
||||||
> = Default::default();
|
|
||||||
for (id, version, info, assets) in get_matching_models(&peek.as_index().as_package(), ¶ms)?
|
|
||||||
{
|
|
||||||
let package_best = best.entry(id.clone()).or_default();
|
let package_best = best.entry(id.clone()).or_default();
|
||||||
let package_other = other.entry(id.clone()).or_default();
|
let package_other = other.entry(id.clone()).or_default();
|
||||||
if params
|
if params
|
||||||
@@ -242,12 +218,12 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
|||||||
package_other.insert(worse_version, info);
|
package_other.insert(worse_version, info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
package_best.insert(version.into(), (info, assets));
|
package_best.insert(version.into(), info);
|
||||||
} else {
|
} else {
|
||||||
package_other.insert(version.into(), (info, assets));
|
package_other.insert(version.into(), info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut res = if let Some(id) = ¶ms.id {
|
if let Some(id) = ¶ms.id {
|
||||||
let categories = peek
|
let categories = peek
|
||||||
.as_index()
|
.as_index()
|
||||||
.as_package()
|
.as_package()
|
||||||
@@ -256,23 +232,14 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
|||||||
.map(|p| p.as_categories().de())
|
.map(|p| p.as_categories().de())
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let best = best
|
let best: BTreeMap<VersionString, PackageVersionInfo> = best
|
||||||
.remove(id)
|
.remove(id)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, (i, a))| {
|
.map(|(k, i)| Ok::<_, Error>((k, i.de()?)))
|
||||||
Ok::<_, Error>((
|
|
||||||
k,
|
|
||||||
PackageVersionInfo {
|
|
||||||
metadata: i.as_metadata().de()?,
|
|
||||||
source_version: i.as_source_version().de()?,
|
|
||||||
s9pks: a,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.try_collect()?;
|
.try_collect()?;
|
||||||
let other = other.remove(id).unwrap_or_default();
|
let other = other.remove(id).unwrap_or_default();
|
||||||
match params.other_versions {
|
match params.other_versions.unwrap_or_default() {
|
||||||
PackageDetailLevel::None => to_value(&GetPackageResponse {
|
PackageDetailLevel::None => to_value(&GetPackageResponse {
|
||||||
categories,
|
categories,
|
||||||
best,
|
best,
|
||||||
@@ -284,7 +251,7 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
|||||||
other_versions: Some(
|
other_versions: Some(
|
||||||
other
|
other
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, (i, _))| from_value(i.as_value().clone()).map(|v| (k, v)))
|
.map(|(k, i)| from_value(i.into()).map(|v| (k, v)))
|
||||||
.try_collect()?,
|
.try_collect()?,
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
@@ -293,21 +260,12 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
|||||||
best,
|
best,
|
||||||
other_versions: other
|
other_versions: other
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, (i, a))| {
|
.map(|(k, i)| Ok::<_, Error>((k, i.de()?)))
|
||||||
Ok::<_, Error>((
|
|
||||||
k,
|
|
||||||
PackageVersionInfo {
|
|
||||||
metadata: i.as_metadata().de()?,
|
|
||||||
source_version: i.as_source_version().de()?,
|
|
||||||
s9pks: a,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.try_collect()?,
|
.try_collect()?,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match params.other_versions {
|
match params.other_versions.unwrap_or_default() {
|
||||||
PackageDetailLevel::None => to_value(
|
PackageDetailLevel::None => to_value(
|
||||||
&best
|
&best
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -326,9 +284,7 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
|||||||
categories,
|
categories,
|
||||||
best: best
|
best: best
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, (i, _))| {
|
.map(|(k, i)| Ok::<_, Error>((k, i.de()?)))
|
||||||
from_value(i.as_value().clone()).map(|v| (k, v))
|
|
||||||
})
|
|
||||||
.try_collect()?,
|
.try_collect()?,
|
||||||
other_versions: None,
|
other_versions: None,
|
||||||
},
|
},
|
||||||
@@ -355,24 +311,12 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
|||||||
categories,
|
categories,
|
||||||
best: best
|
best: best
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.into_iter()
|
.map(|(k, i)| Ok::<_, Error>((k, i.de()?)))
|
||||||
.map(|(k, (i, a))| {
|
|
||||||
Ok::<_, Error>((
|
|
||||||
k,
|
|
||||||
PackageVersionInfo {
|
|
||||||
metadata: i.as_metadata().de()?,
|
|
||||||
source_version: i.as_source_version().de()?,
|
|
||||||
s9pks: a,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.try_collect()?,
|
.try_collect()?,
|
||||||
other_versions: Some(
|
other_versions: Some(
|
||||||
other
|
other
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, (i, _))| {
|
.map(|(k, i)| from_value(i.into()).map(|v| (k, v)))
|
||||||
from_value(i.as_value().clone()).map(|v| (k, v))
|
|
||||||
})
|
|
||||||
.try_collect()?,
|
.try_collect()?,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -399,31 +343,11 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
|||||||
categories,
|
categories,
|
||||||
best: best
|
best: best
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.into_iter()
|
.map(|(k, i)| Ok::<_, Error>((k, i.de()?)))
|
||||||
.map(|(k, (i, a))| {
|
|
||||||
Ok::<_, Error>((
|
|
||||||
k,
|
|
||||||
PackageVersionInfo {
|
|
||||||
metadata: i.as_metadata().de()?,
|
|
||||||
source_version: i.as_source_version().de()?,
|
|
||||||
s9pks: a,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.try_collect()?,
|
.try_collect()?,
|
||||||
other_versions: other
|
other_versions: other
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.into_iter()
|
.map(|(k, i)| Ok::<_, Error>((k, i.de()?)))
|
||||||
.map(|(k, (i, a))| {
|
|
||||||
Ok::<_, Error>((
|
|
||||||
k,
|
|
||||||
PackageVersionInfo {
|
|
||||||
metadata: i.as_metadata().de()?,
|
|
||||||
source_version: i.as_source_version().de()?,
|
|
||||||
s9pks: a,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.try_collect()?,
|
.try_collect()?,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -431,47 +355,7 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu
|
|||||||
.try_collect::<_, GetPackagesResponseFull, _>()?,
|
.try_collect::<_, GetPackagesResponseFull, _>()?,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}?;
|
|
||||||
|
|
||||||
// TODO: remove
|
|
||||||
if true
|
|
||||||
|| params.device_info.map_or(false, |d| {
|
|
||||||
"0.4.0-alpha.17"
|
|
||||||
.parse::<Version>()
|
|
||||||
.map_or(false, |v| d.os.version <= v)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
let patch = |v: &mut Value| {
|
|
||||||
for kind in ["best", "otherVersions"] {
|
|
||||||
for (_, v) in v[kind]
|
|
||||||
.as_object_mut()
|
|
||||||
.into_iter()
|
|
||||||
.map(|v| v.iter_mut())
|
|
||||||
.flatten()
|
|
||||||
{
|
|
||||||
let Some(mut tup) = v["s9pks"].get(0).cloned() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
v["s9pk"] = tup[1].take();
|
|
||||||
v["hardwareRequirements"] = tup[0].take();
|
|
||||||
v["s9pk"]["url"] = v["s9pk"]["urls"][0].clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if params.id.is_some() {
|
|
||||||
patch(&mut res);
|
|
||||||
} else {
|
|
||||||
for (_, v) in res
|
|
||||||
.as_object_mut()
|
|
||||||
.into_iter()
|
|
||||||
.map(|v| v.iter_mut())
|
|
||||||
.flatten()
|
|
||||||
{
|
|
||||||
patch(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_package_info(
|
pub fn display_package_info(
|
||||||
@@ -483,7 +367,7 @@ pub fn display_package_info(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(_) = params.rest.id {
|
if let Some(_) = params.rest.id {
|
||||||
if params.rest.other_versions == PackageDetailLevel::Full {
|
if params.rest.other_versions.unwrap_or_default() == PackageDetailLevel::Full {
|
||||||
for table in from_value::<GetPackageResponseFull>(info)?.tables() {
|
for table in from_value::<GetPackageResponseFull>(info)?.tables() {
|
||||||
table.print_tty(false)?;
|
table.print_tty(false)?;
|
||||||
println!();
|
println!();
|
||||||
@@ -495,7 +379,7 @@ pub fn display_package_info(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if params.rest.other_versions == PackageDetailLevel::Full {
|
if params.rest.other_versions.unwrap_or_default() == PackageDetailLevel::Full {
|
||||||
for (_, package) in from_value::<GetPackagesResponseFull>(info)? {
|
for (_, package) in from_value::<GetPackagesResponseFull>(info)? {
|
||||||
for table in package.tables() {
|
for table in package.tables() {
|
||||||
table.print_tty(false)?;
|
table.print_tty(false)?;
|
||||||
@@ -620,7 +504,7 @@ pub async fn cli_download(
|
|||||||
} else {
|
} else {
|
||||||
"Devices"
|
"Devices"
|
||||||
},
|
},
|
||||||
hw.device.iter().map(|d| &d.pattern_description).join(", ")
|
hw.device.iter().map(|d| &d.description).join(", ")
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::u32;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use exver::{Version, VersionRange};
|
use exver::{Version, VersionRange};
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
|
use patch_db::ModelExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -223,50 +225,64 @@ impl PackageVersionInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Model<PackageVersionInfo> {
|
impl Model<PackageVersionInfo> {
|
||||||
pub fn for_device(
|
/// Filters this package version for compatibility with the given device.
|
||||||
&self,
|
/// Returns false if the package is incompatible (should be removed).
|
||||||
device_info: &DeviceInfo,
|
/// Modifies s9pks in place to only include compatible variants.
|
||||||
) -> Result<Option<Vec<(HardwareRequirements, RegistryAsset<MerkleArchiveCommitment>)>>, Error>
|
pub fn for_device(&mut self, device_info: &DeviceInfo) -> Result<bool, Error> {
|
||||||
{
|
|
||||||
if !self
|
if !self
|
||||||
.as_metadata()
|
.as_metadata()
|
||||||
.as_os_version()
|
.as_os_version()
|
||||||
.de()?
|
.de()?
|
||||||
.satisfies(&device_info.os.compat)
|
.satisfies(&device_info.os.compat)
|
||||||
{
|
{
|
||||||
return Ok(None);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
let mut s9pk = self.as_s9pks().de()?;
|
if let Some(hw) = &device_info.hardware {
|
||||||
s9pk.retain(|(hw, _)| {
|
self.as_s9pks_mut().mutate(|s9pks| {
|
||||||
if let Some(arch) = &hw.arch {
|
s9pks.retain(|(hw_req, _)| {
|
||||||
if !arch.contains(&device_info.hardware.arch) {
|
if let Some(arch) = &hw_req.arch {
|
||||||
return false;
|
if !arch.contains(&hw.arch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ram) = hw_req.ram {
|
||||||
|
if hw.ram < ram {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(dev) = &hw.devices {
|
||||||
|
for device_filter in &hw_req.device {
|
||||||
|
if !dev
|
||||||
|
.iter()
|
||||||
|
.filter(|d| d.class() == &*device_filter.class)
|
||||||
|
.any(|d| device_filter.matches(d))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
if hw.devices.is_some() {
|
||||||
|
s9pks.sort_by_key(|(req, _)| req.specificity_desc());
|
||||||
|
} else {
|
||||||
|
s9pks.sort_by_key(|(req, _)| {
|
||||||
|
let (dev, arch, ram) = req.specificity_desc();
|
||||||
|
(u32::MAX - dev, arch, ram)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
if let Some(ram) = hw.ram {
|
})?;
|
||||||
if device_info.hardware.ram < ram {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for device_filter in &hw.device {
|
|
||||||
if !device_info
|
|
||||||
.hardware
|
|
||||||
.devices
|
|
||||||
.iter()
|
|
||||||
.filter(|d| d.class() == &*device_filter.class)
|
|
||||||
.any(|d| device_filter.pattern.as_ref().is_match(d.product()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
});
|
|
||||||
|
|
||||||
if s9pk.is_empty() {
|
if ModelExt::as_value(self.as_s9pks())
|
||||||
Ok(None)
|
.as_array()
|
||||||
} else {
|
.map_or(true, |s| s.is_empty())
|
||||||
Ok(Some(s9pk))
|
{
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,12 +242,13 @@ impl TryFrom<ManifestV1> for Manifest {
|
|||||||
.device
|
.device
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(class, product)| DeviceFilter {
|
.map(|(class, product)| DeviceFilter {
|
||||||
pattern_description: format!(
|
description: format!(
|
||||||
"a {class} device matching the expression {}",
|
"a {class} device matching the expression {}",
|
||||||
product.as_ref()
|
product.as_ref()
|
||||||
),
|
),
|
||||||
class,
|
class,
|
||||||
pattern: product,
|
product: Some(product),
|
||||||
|
..Default::default()
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use crate::s9pk::git_hash::GitHash;
|
|||||||
use crate::s9pk::merkle_archive::directory_contents::DirectoryContents;
|
use crate::s9pk::merkle_archive::directory_contents::DirectoryContents;
|
||||||
use crate::s9pk::merkle_archive::expected::{Expected, Filter};
|
use crate::s9pk::merkle_archive::expected::{Expected, Filter};
|
||||||
use crate::s9pk::v2::pack::ImageConfig;
|
use crate::s9pk::v2::pack::ImageConfig;
|
||||||
|
use crate::util::lshw::{LshwDevice, LshwDisplay, LshwProcessor};
|
||||||
use crate::util::serde::Regex;
|
use crate::util::serde::Regex;
|
||||||
use crate::util::{VersionString, mime};
|
use crate::util::{VersionString, mime};
|
||||||
use crate::version::{Current, VersionT};
|
use crate::version::{Current, VersionT};
|
||||||
@@ -189,21 +190,107 @@ impl HardwareRequirements {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct DeviceFilter {
|
pub struct DeviceFilter {
|
||||||
|
pub description: String,
|
||||||
#[ts(type = "\"processor\" | \"display\"")]
|
#[ts(type = "\"processor\" | \"display\"")]
|
||||||
pub class: InternedString,
|
pub class: InternedString,
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string | null")]
|
||||||
pub pattern: Regex,
|
pub product: Option<Regex>,
|
||||||
pub pattern_description: String,
|
#[ts(type = "string | null")]
|
||||||
|
pub vendor: Option<Regex>,
|
||||||
|
#[ts(optional)]
|
||||||
|
pub capabilities: Option<BTreeSet<InternedString>>,
|
||||||
|
#[ts(optional)]
|
||||||
|
pub driver: Option<InternedString>,
|
||||||
}
|
}
|
||||||
|
// Omit description
|
||||||
impl PartialEq for DeviceFilter {
|
impl PartialEq for DeviceFilter {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.class == other.class
|
self.class == other.class
|
||||||
&& InternedString::from_display(self.pattern.as_ref())
|
&& self.product == other.product
|
||||||
== InternedString::from_display(other.pattern.as_ref())
|
&& self.vendor == other.vendor
|
||||||
|
&& self.capabilities == other.capabilities
|
||||||
|
&& self.driver == other.driver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DeviceFilter {
|
||||||
|
pub fn matches(&self, device: &LshwDevice) -> bool {
|
||||||
|
if &*self.class != device.class() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match device {
|
||||||
|
LshwDevice::Processor(LshwProcessor {
|
||||||
|
product,
|
||||||
|
vendor,
|
||||||
|
capabilities,
|
||||||
|
}) => {
|
||||||
|
if let Some(match_product) = &self.product {
|
||||||
|
if !product
|
||||||
|
.as_deref()
|
||||||
|
.map_or(false, |p| match_product.as_ref().is_match(p))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(match_vendor) = &self.vendor {
|
||||||
|
if !vendor
|
||||||
|
.as_deref()
|
||||||
|
.map_or(false, |v| match_vendor.as_ref().is_match(v))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self
|
||||||
|
.capabilities
|
||||||
|
.as_ref()
|
||||||
|
.map_or(true, |c| c.is_subset(capabilities))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
LshwDevice::Display(LshwDisplay {
|
||||||
|
product,
|
||||||
|
vendor,
|
||||||
|
capabilities,
|
||||||
|
driver,
|
||||||
|
}) => {
|
||||||
|
if let Some(match_product) = &self.product {
|
||||||
|
if !product
|
||||||
|
.as_deref()
|
||||||
|
.map_or(false, |p| match_product.as_ref().is_match(p))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(match_vendor) = &self.vendor {
|
||||||
|
if !vendor
|
||||||
|
.as_deref()
|
||||||
|
.map_or(false, |v| match_vendor.as_ref().is_match(v))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self
|
||||||
|
.capabilities
|
||||||
|
.as_ref()
|
||||||
|
.map_or(true, |c| c.is_subset(capabilities))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if !self
|
||||||
|
.driver
|
||||||
|
.as_ref()
|
||||||
|
.map_or(true, |d| Some(d) == driver.as_ref())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use imbl::OrdMap;
|
||||||
use imbl_value::Value;
|
use imbl_value::Value;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
@@ -53,7 +54,13 @@ impl Context for ContainerCliContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CallRemote<EffectContext> for ContainerCliContext {
|
impl CallRemote<EffectContext> for ContainerCliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(
|
||||||
|
&self,
|
||||||
|
method: &str,
|
||||||
|
_: OrdMap<&'static str, Value>,
|
||||||
|
params: Value,
|
||||||
|
_: Empty,
|
||||||
|
) -> Result<Value, RpcError> {
|
||||||
call_remote_socket(
|
call_remote_socket(
|
||||||
tokio::net::UnixStream::connect(&self.0.socket)
|
tokio::net::UnixStream::connect(&self.0.socket)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use std::sync::{Arc, Weak};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use axum::extract::ws::Utf8Bytes;
|
use axum::extract::ws::Utf8Bytes;
|
||||||
use crate::util::net::WebSocket;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use futures::stream::FusedStream;
|
use futures::stream::FusedStream;
|
||||||
@@ -48,6 +47,7 @@ use crate::util::Never;
|
|||||||
use crate::util::actor::concurrent::ConcurrentActor;
|
use crate::util::actor::concurrent::ConcurrentActor;
|
||||||
use crate::util::future::NonDetachingJoinHandle;
|
use crate::util::future::NonDetachingJoinHandle;
|
||||||
use crate::util::io::{AsyncReadStream, AtomicFile, TermSize, delete_file};
|
use crate::util::io::{AsyncReadStream, AtomicFile, TermSize, delete_file};
|
||||||
|
use crate::util::net::WebSocket;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
use crate::util::sync::SyncMutex;
|
use crate::util::sync::SyncMutex;
|
||||||
use crate::volume::data_dir;
|
use crate::volume::data_dir;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ impl Model<StatusInfo> {
|
|||||||
}
|
}
|
||||||
pub fn stopped(&mut self) -> Result<(), Error> {
|
pub fn stopped(&mut self) -> Result<(), Error> {
|
||||||
self.as_started_mut().ser(&None)?;
|
self.as_started_mut().ser(&None)?;
|
||||||
|
self.as_health_mut().ser(&Default::default())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn restart(&mut self) -> Result<(), Error> {
|
pub fn restart(&mut self) -> Result<(), Error> {
|
||||||
@@ -59,7 +60,7 @@ impl Model<StatusInfo> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn init(&mut self) -> Result<(), Error> {
|
pub fn init(&mut self) -> Result<(), Error> {
|
||||||
self.as_started_mut().ser(&None)?;
|
self.stopped()?;
|
||||||
self.as_desired_mut().map_mutate(|s| {
|
self.as_desired_mut().map_mutate(|s| {
|
||||||
Ok(match s {
|
Ok(match s {
|
||||||
DesiredStatus::BackingUp {
|
DesiredStatus::BackingUp {
|
||||||
|
|||||||
@@ -251,6 +251,8 @@ impl CallRemote<TunnelContext> for CliContext {
|
|||||||
async fn call_remote(
|
async fn call_remote(
|
||||||
&self,
|
&self,
|
||||||
mut method: &str,
|
mut method: &str,
|
||||||
|
|
||||||
|
_: OrdMap<&'static str, Value>,
|
||||||
params: Value,
|
params: Value,
|
||||||
_: Empty,
|
_: Empty,
|
||||||
) -> Result<Value, RpcError> {
|
) -> Result<Value, RpcError> {
|
||||||
@@ -315,6 +317,7 @@ impl CallRemote<TunnelContext, TunnelUrlParams> for RpcContext {
|
|||||||
async fn call_remote(
|
async fn call_remote(
|
||||||
&self,
|
&self,
|
||||||
mut method: &str,
|
mut method: &str,
|
||||||
|
_: OrdMap<&'static str, Value>,
|
||||||
params: Value,
|
params: Value,
|
||||||
TunnelUrlParams { tunnel }: TunnelUrlParams,
|
TunnelUrlParams { tunnel }: TunnelUrlParams,
|
||||||
) -> Result<Value, RpcError> {
|
) -> Result<Value, RpcError> {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use imbl_value::InternedString;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::{Error, ResultExt};
|
|
||||||
|
|
||||||
const KNOWN_CLASSES: &[&str] = &["processor", "display"];
|
const KNOWN_CLASSES: &[&str] = &["processor", "display"];
|
||||||
|
|
||||||
@@ -22,22 +25,57 @@ impl LshwDevice {
|
|||||||
Self::Display(_) => "display",
|
Self::Display(_) => "display",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn product(&self) -> &str {
|
pub fn from_value(value: &Value) -> Option<Self> {
|
||||||
match self {
|
match value["class"].as_str() {
|
||||||
Self::Processor(hw) => hw.product.as_str(),
|
Some("processor") => Some(LshwDevice::Processor(LshwProcessor::from_value(value))),
|
||||||
Self::Display(hw) => hw.product.as_str(),
|
Some("display") => Some(LshwDevice::Display(LshwDisplay::from_value(value))),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
pub struct LshwProcessor {
|
pub struct LshwProcessor {
|
||||||
pub product: String,
|
pub product: Option<InternedString>,
|
||||||
|
pub vendor: Option<InternedString>,
|
||||||
|
pub capabilities: BTreeSet<InternedString>,
|
||||||
|
}
|
||||||
|
impl LshwProcessor {
|
||||||
|
fn from_value(value: &Value) -> Self {
|
||||||
|
Self {
|
||||||
|
product: value["product"].as_str().map(From::from),
|
||||||
|
vendor: value["vendor"].as_str().map(From::from),
|
||||||
|
capabilities: value["capabilities"]
|
||||||
|
.as_object()
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|o| o.keys())
|
||||||
|
.map(|k| k.clone())
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
pub struct LshwDisplay {
|
pub struct LshwDisplay {
|
||||||
pub product: String,
|
pub product: Option<InternedString>,
|
||||||
|
pub vendor: Option<InternedString>,
|
||||||
|
pub capabilities: BTreeSet<InternedString>,
|
||||||
|
pub driver: Option<InternedString>,
|
||||||
|
}
|
||||||
|
impl LshwDisplay {
|
||||||
|
fn from_value(value: &Value) -> Self {
|
||||||
|
Self {
|
||||||
|
product: value["product"].as_str().map(From::from),
|
||||||
|
vendor: value["vendor"].as_str().map(From::from),
|
||||||
|
capabilities: value["capabilities"]
|
||||||
|
.as_object()
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|o| o.keys())
|
||||||
|
.map(|k| k.clone())
|
||||||
|
.collect(),
|
||||||
|
driver: value["configuration"]["driver"].as_str().map(From::from),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn lshw() -> Result<Vec<LshwDevice>, Error> {
|
pub async fn lshw() -> Result<Vec<LshwDevice>, Error> {
|
||||||
@@ -47,19 +85,10 @@ pub async fn lshw() -> Result<Vec<LshwDevice>, Error> {
|
|||||||
cmd.arg("-class").arg(*class);
|
cmd.arg("-class").arg(*class);
|
||||||
}
|
}
|
||||||
Ok(
|
Ok(
|
||||||
serde_json::from_slice::<Vec<serde_json::Value>>(
|
serde_json::from_slice::<Vec<Value>>(&cmd.invoke(crate::ErrorKind::Lshw).await?)
|
||||||
&cmd.invoke(crate::ErrorKind::Lshw).await?,
|
.with_kind(crate::ErrorKind::Deserialization)?
|
||||||
)
|
.iter()
|
||||||
.with_kind(crate::ErrorKind::Deserialization)?
|
.filter_map(LshwDevice::from_value)
|
||||||
.into_iter()
|
.collect(),
|
||||||
.filter_map(|v| match serde_json::from_value(v) {
|
|
||||||
Ok(a) => Some(a),
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Failed to parse lshw output: {e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1127,6 +1127,11 @@ impl Serialize for Regex {
|
|||||||
serialize_display(&self.0, serializer)
|
serialize_display(&self.0, serializer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl PartialEq for Regex {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
InternedString::from_display(self.as_ref()) == InternedString::from_display(other.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: make this not allocate
|
// TODO: make this not allocate
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use exver::{PreReleaseSegment, VersionRange};
|
use exver::{PreReleaseSegment, VersionRange};
|
||||||
|
use imbl_value::json;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
|
|
||||||
use super::v0_3_5::V0_3_0_COMPAT;
|
use super::v0_3_5::V0_3_0_COMPAT;
|
||||||
@@ -10,7 +11,7 @@ use crate::context::RpcContext;
|
|||||||
use crate::install::PKG_ARCHIVE_DIR;
|
use crate::install::PKG_ARCHIVE_DIR;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::s9pk::S9pk;
|
use crate::s9pk::S9pk;
|
||||||
use crate::s9pk::manifest::{DeviceFilter, Manifest};
|
use crate::s9pk::manifest::Manifest;
|
||||||
use crate::s9pk::merkle_archive::MerkleArchive;
|
use crate::s9pk::merkle_archive::MerkleArchive;
|
||||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||||
use crate::s9pk::v2::SIG_CONTEXT;
|
use crate::s9pk::v2::SIG_CONTEXT;
|
||||||
@@ -84,28 +85,8 @@ impl VersionT for Version {
|
|||||||
|
|
||||||
let mut manifest = previous_manifest.clone();
|
let mut manifest = previous_manifest.clone();
|
||||||
|
|
||||||
if let Some(device) =
|
if let Some(_) = previous_manifest["hardwareRequirements"]["device"].as_object() {
|
||||||
previous_manifest["hardwareRequirements"]["device"].as_object()
|
manifest["hardwareRequirements"]["device"] = json!([]);
|
||||||
{
|
|
||||||
manifest["hardwareRequirements"]["device"] = to_value(
|
|
||||||
&device
|
|
||||||
.into_iter()
|
|
||||||
.map(|(class, product)| {
|
|
||||||
Ok::<_, Error>(DeviceFilter {
|
|
||||||
pattern_description: format!(
|
|
||||||
"a {class} device matching the expression {}",
|
|
||||||
&product
|
|
||||||
),
|
|
||||||
class: class.clone(),
|
|
||||||
pattern: from_value(product.clone())?,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.fold(Ok::<_, Error>(Vec::new()), |acc, value| {
|
|
||||||
let mut acc = acc?;
|
|
||||||
acc.push(value?);
|
|
||||||
Ok(acc)
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if previous_manifest != manifest {
|
if previous_manifest != manifest {
|
||||||
|
|||||||
28
debian/startos/postinst
vendored
28
debian/startos/postinst
vendored
@@ -29,34 +29,6 @@ if [ -f /etc/default/grub ]; then
|
|||||||
else
|
else
|
||||||
echo 'GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"' >> /etc/default/grub
|
echo 'GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"' >> /etc/default/grub
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat > /etc/grub.d/01_reboot_efi <<-EOF
|
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Only affect EFI systems
|
|
||||||
if [ ! -d /sys/firmware/efi ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Import helpers (path is Debian/Ubuntu style)
|
|
||||||
. /usr/lib/grub/grub-mkconfig_lib
|
|
||||||
|
|
||||||
# Append reboot=efi to GRUB_CMDLINE_LINUX* seen by later scripts
|
|
||||||
if [ -n "\${GRUB_CMDLINE_LINUX}" ]; then
|
|
||||||
GRUB_CMDLINE_LINUX="\${GRUB_CMDLINE_LINUX} reboot=efi"
|
|
||||||
else
|
|
||||||
GRUB_CMDLINE_LINUX="reboot=efi"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "\${GRUB_CMDLINE_LINUX_DEFAULT}" ]; then
|
|
||||||
GRUB_CMDLINE_LINUX_DEFAULT="\${GRUB_CMDLINE_LINUX_DEFAULT} reboot=efi"
|
|
||||||
fi
|
|
||||||
|
|
||||||
export GRUB_CMDLINE_LINUX GRUB_CMDLINE_LINUX_DEFAULT
|
|
||||||
EOF
|
|
||||||
chmod +x /etc/grub.d/01_reboot_efi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
VERSION="$(cat /usr/lib/startos/VERSION.txt)"
|
VERSION="$(cat /usr/lib/startos/VERSION.txt)"
|
||||||
|
|||||||
9
sdk/base/lib/osBindings/AddMirrorParams.ts
Normal file
9
sdk/base/lib/osBindings/AddMirrorParams.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { AnySignature } from "./AnySignature"
|
||||||
|
import type { MerkleArchiveCommitment } from "./MerkleArchiveCommitment"
|
||||||
|
|
||||||
|
export type AddMirrorParams = {
|
||||||
|
url: string
|
||||||
|
commitment: MerkleArchiveCommitment
|
||||||
|
signature: AnySignature
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import type { AnySignature } from "./AnySignature"
|
|||||||
import type { MerkleArchiveCommitment } from "./MerkleArchiveCommitment"
|
import type { MerkleArchiveCommitment } from "./MerkleArchiveCommitment"
|
||||||
|
|
||||||
export type AddPackageParams = {
|
export type AddPackageParams = {
|
||||||
url: string
|
urls: string[]
|
||||||
commitment: MerkleArchiveCommitment
|
commitment: MerkleArchiveCommitment
|
||||||
signature: AnySignature
|
signature: AnySignature
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type DeviceFilter = {
|
export type DeviceFilter = {
|
||||||
|
description: string
|
||||||
class: "processor" | "display"
|
class: "processor" | "display"
|
||||||
pattern: string
|
product: string | null
|
||||||
patternDescription: string
|
vendor: string | null
|
||||||
|
capabilities?: Array<string>
|
||||||
|
driver?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ export type GetPackageParams = {
|
|||||||
id: PackageId | null
|
id: PackageId | null
|
||||||
targetVersion: string | null
|
targetVersion: string | null
|
||||||
sourceVersion: Version | null
|
sourceVersion: Version | null
|
||||||
otherVersions: PackageDetailLevel
|
otherVersions: PackageDetailLevel | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type LshwDisplay = { product: string }
|
export type LshwDisplay = {
|
||||||
|
product: string | null
|
||||||
|
vendor: string | null
|
||||||
|
capabilities: Array<string>
|
||||||
|
driver: string | null
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type LshwProcessor = { product: string }
|
export type LshwProcessor = {
|
||||||
|
product: string | null
|
||||||
|
vendor: string | null
|
||||||
|
capabilities: Array<string>
|
||||||
|
}
|
||||||
|
|||||||
9
sdk/base/lib/osBindings/RemoveMirrorParams.ts
Normal file
9
sdk/base/lib/osBindings/RemoveMirrorParams.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { PackageId } from "./PackageId"
|
||||||
|
import type { Version } from "./Version"
|
||||||
|
|
||||||
|
export type RemoveMirrorParams = {
|
||||||
|
id: PackageId
|
||||||
|
version: Version
|
||||||
|
url: string
|
||||||
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { Base64 } from "./Base64"
|
||||||
import type { PackageId } from "./PackageId"
|
import type { PackageId } from "./PackageId"
|
||||||
import type { Version } from "./Version"
|
import type { Version } from "./Version"
|
||||||
|
|
||||||
export type RemovePackageParams = { id: PackageId; version: Version }
|
export type RemovePackageParams = {
|
||||||
|
id: PackageId
|
||||||
|
version: Version
|
||||||
|
sighash: Base64 | null
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export { ActionVisibility } from "./ActionVisibility"
|
|||||||
export { AddAdminParams } from "./AddAdminParams"
|
export { AddAdminParams } from "./AddAdminParams"
|
||||||
export { AddAssetParams } from "./AddAssetParams"
|
export { AddAssetParams } from "./AddAssetParams"
|
||||||
export { AddCategoryParams } from "./AddCategoryParams"
|
export { AddCategoryParams } from "./AddCategoryParams"
|
||||||
|
export { AddMirrorParams } from "./AddMirrorParams"
|
||||||
export { AddPackageParams } from "./AddPackageParams"
|
export { AddPackageParams } from "./AddPackageParams"
|
||||||
export { AddPackageSignerParams } from "./AddPackageSignerParams"
|
export { AddPackageSignerParams } from "./AddPackageSignerParams"
|
||||||
export { AddPackageToCategoryParams } from "./AddPackageToCategoryParams"
|
export { AddPackageToCategoryParams } from "./AddPackageToCategoryParams"
|
||||||
@@ -171,6 +172,7 @@ export { RegistryInfo } from "./RegistryInfo"
|
|||||||
export { RemoveAdminParams } from "./RemoveAdminParams"
|
export { RemoveAdminParams } from "./RemoveAdminParams"
|
||||||
export { RemoveAssetParams } from "./RemoveAssetParams"
|
export { RemoveAssetParams } from "./RemoveAssetParams"
|
||||||
export { RemoveCategoryParams } from "./RemoveCategoryParams"
|
export { RemoveCategoryParams } from "./RemoveCategoryParams"
|
||||||
|
export { RemoveMirrorParams } from "./RemoveMirrorParams"
|
||||||
export { RemovePackageFromCategoryParams } from "./RemovePackageFromCategoryParams"
|
export { RemovePackageFromCategoryParams } from "./RemovePackageFromCategoryParams"
|
||||||
export { RemovePackageParams } from "./RemovePackageParams"
|
export { RemovePackageParams } from "./RemovePackageParams"
|
||||||
export { RemovePackageSignerParams } from "./RemovePackageSignerParams"
|
export { RemovePackageSignerParams } from "./RemovePackageSignerParams"
|
||||||
|
|||||||
Reference in New Issue
Block a user