Skip to content

Commit

Permalink
use hardware requirements to display conflicts and prevent install (#…
Browse files Browse the repository at this point in the history
…2700)

* use hardware requirements to display conflicts and prevent install

* better messaging and also consider OS compatibility

* wip: backend hw requirements

* update backend components

* migration

---------

Co-authored-by: Aiden McClelland <[email protected]>
  • Loading branch information
MattDHill and dr-bonez authored Oct 29, 2024
1 parent e1a91a7 commit 1be9cda
Show file tree
Hide file tree
Showing 35 changed files with 707 additions and 482 deletions.
642 changes: 329 additions & 313 deletions core/Cargo.lock

Large diffs are not rendered by default.

13 changes: 1 addition & 12 deletions core/startos/src/context/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ use crate::service::action::update_requested_actions;
use crate::service::effects::callbacks::ServiceCallbacks;
use crate::service::ServiceMap;
use crate::shutdown::Shutdown;
use crate::system::get_mem_info;
use crate::util::lshw::{lshw, LshwDevice};
use crate::util::lshw::LshwDevice;
use crate::util::sync::SyncMutex;

pub struct RpcContextSeed {
Expand All @@ -67,7 +66,6 @@ pub struct RpcContextSeed {
pub wifi_manager: Option<Arc<RwLock<WpaCli>>>,
pub current_secret: Arc<Jwk>,
pub client: Client,
pub hardware: Hardware,
pub start_time: Instant,
pub crons: SyncMutex<BTreeMap<Guid, NonDetachingJoinHandle<()>>>,
// #[cfg(feature = "dev")]
Expand All @@ -86,7 +84,6 @@ pub struct Hardware {
pub struct InitRpcContextPhases {
load_db: PhaseProgressTrackerHandle,
init_net_ctrl: PhaseProgressTrackerHandle,
read_device_info: PhaseProgressTrackerHandle,
cleanup_init: CleanupInitPhases,
// TODO: migrations
}
Expand All @@ -95,7 +92,6 @@ impl InitRpcContextPhases {
Self {
load_db: handle.add_phase("Loading database".into(), Some(5)),
init_net_ctrl: handle.add_phase("Initializing network".into(), Some(1)),
read_device_info: handle.add_phase("Reading device information".into(), Some(1)),
cleanup_init: CleanupInitPhases::new(handle),
}
}
Expand Down Expand Up @@ -127,7 +123,6 @@ impl RpcContext {
InitRpcContextPhases {
mut load_db,
mut init_net_ctrl,
mut read_device_info,
cleanup_init,
}: InitRpcContextPhases,
) -> Result<Self, Error> {
Expand Down Expand Up @@ -179,11 +174,6 @@ impl RpcContext {
let metrics_cache = RwLock::<Option<crate::system::Metrics>>::new(None);
let tor_proxy_url = format!("socks5h://{tor_proxy}");

read_device_info.start();
let devices = lshw().await?;
let ram = get_mem_info().await?.total.0 as u64 * 1024 * 1024;
read_device_info.complete();

let crons = SyncMutex::new(BTreeMap::new());

if !db
Expand Down Expand Up @@ -275,7 +265,6 @@ impl RpcContext {
}))
.build()
.with_kind(crate::ErrorKind::ParseUrl)?,
hardware: Hardware { devices, ram },
start_time: Instant::now(),
crons,
// #[cfg(feature = "dev")]
Expand Down
10 changes: 8 additions & 2 deletions core/startos/src/db/model/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::prelude::*;
use crate::progress::FullProgress;
use crate::system::SmtpValue;
use crate::util::cpupower::Governor;
use crate::util::lshw::LshwDevice;
use crate::version::{Current, VersionT};
use crate::{ARCH, PLATFORM};

Expand All @@ -46,7 +47,7 @@ impl Public {
version: Current::default().semver(),
hostname: account.hostname.no_dot_host_name(),
last_backup: None,
version_compat: Current::default().compat().clone(),
package_version_compat: Current::default().compat().clone(),
post_init_migration_todos: BTreeSet::new(),
lan_address,
onion_address: account.tor_key.public().get_onion_address(),
Expand Down Expand Up @@ -78,6 +79,8 @@ impl Public {
zram: true,
governor: None,
smtp: None,
ram: 0,
devices: Vec::new(),
},
package_data: AllPackageData::default(),
ui: serde_json::from_str(include_str!(concat!(
Expand Down Expand Up @@ -114,7 +117,7 @@ pub struct ServerInfo {
#[ts(type = "string")]
pub version: Version,
#[ts(type = "string")]
pub version_compat: VersionRange,
pub package_version_compat: VersionRange,
#[ts(type = "string[]")]
pub post_init_migration_todos: BTreeSet<Version>,
#[ts(type = "string | null")]
Expand All @@ -141,6 +144,9 @@ pub struct ServerInfo {
pub zram: bool,
pub governor: Option<Governor>,
pub smtp: Option<SmtpValue>,
#[ts(type = "number")]
pub ram: u64,
pub devices: Vec<LshwDevice>,
}

#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
Expand Down
4 changes: 4 additions & 0 deletions core/startos/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ use crate::progress::{
use crate::rpc_continuations::{Guid, RpcContinuation};
use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL};
use crate::ssh::SSH_AUTHORIZED_KEYS_FILE;
use crate::system::get_mem_info;
use crate::util::io::{create_file, IOHook};
use crate::util::lshw::lshw;
use crate::util::net::WebSocketExt;
use crate::util::{cpupower, Invoke};
use crate::Error;
Expand Down Expand Up @@ -508,6 +510,8 @@ pub async fn init(

update_server_info.start();
server_info.ip_info = crate::net::dhcp::init_ips().await?;
server_info.ram = get_mem_info().await?.total.0 as u64 * 1024 * 1024;
server_info.devices = lshw().await?;
server_info.status_info = ServerStatus {
updated: false,
update_progress: None,
Expand Down
2 changes: 1 addition & 1 deletion core/startos/src/registry/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ impl CallRemote<RegistryContext, RegistryUrlParams> for RpcContext {
.header(CONTENT_TYPE, "application/json")
.header(ACCEPT, "application/json")
.header(CONTENT_LENGTH, body.len())
.header(DEVICE_INFO_HEADER, DeviceInfo::from(self).to_header_value())
// .header(DEVICE_INFO_HEADER, DeviceInfo::from(self).to_header_value())
.body(body)
.send()
.await?;
Expand Down
73 changes: 34 additions & 39 deletions core/startos/src/registry/device_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use url::Url;
use crate::context::RpcContext;
use crate::prelude::*;
use crate::registry::context::RegistryContext;
use crate::util::lshw::{LshwDevice, LshwDisplay, LshwProcessor};
use crate::util::VersionString;
use crate::version::VersionT;

Expand All @@ -26,12 +27,12 @@ pub struct DeviceInfo {
pub os: OsInfo,
pub hardware: HardwareInfo,
}
impl From<&RpcContext> for DeviceInfo {
fn from(value: &RpcContext) -> Self {
Self {
os: OsInfo::from(value),
hardware: HardwareInfo::from(value),
}
impl DeviceInfo {
pub async fn load(ctx: &RpcContext) -> Result<Self, Error> {
Ok(Self {
os: OsInfo::from(ctx),
hardware: HardwareInfo::load(ctx).await?,
})
}
}
impl DeviceInfo {
Expand All @@ -44,11 +45,11 @@ impl DeviceInfo {
.append_pair("hardware.arch", &*self.hardware.arch)
.append_pair("hardware.ram", &self.hardware.ram.to_string());

for (class, products) in &self.hardware.devices {
for product in products {
url.query_pairs_mut()
.append_pair(&format!("hardware.device.{}", class), product);
}
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()
Expand Down Expand Up @@ -80,16 +81,20 @@ impl DeviceInfo {
devices: identity(query)
.split_off("hardware.device.")
.into_iter()
.filter_map(|(k, v)| {
k.strip_prefix("hardware.device.")
.map(|k| (k.into(), v.into_owned()))
.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,
})
.fold(BTreeMap::new(), |mut acc, (k, v)| {
let mut devs = acc.remove(&k).unwrap_or_default();
devs.push(v);
acc.insert(k, devs);
acc
}),
.collect(),
},
})
}
Expand Down Expand Up @@ -122,26 +127,16 @@ pub struct HardwareInfo {
pub arch: InternedString,
#[ts(type = "number")]
pub ram: u64,
#[ts(as = "BTreeMap::<String, Vec<String>>")]
pub devices: BTreeMap<InternedString, Vec<String>>,
pub devices: Vec<LshwDevice>,
}

impl From<&RpcContext> for HardwareInfo {
fn from(value: &RpcContext) -> Self {
Self {
arch: InternedString::intern(crate::ARCH),
ram: value.hardware.ram,
devices: value
.hardware
.devices
.iter()
.fold(BTreeMap::new(), |mut acc, dev| {
let mut devs = acc.remove(dev.class()).unwrap_or_default();
devs.push(dev.product().to_owned());
acc.insert(dev.class().into(), devs);
acc
}),
}
impl HardwareInfo {
pub async fn load(ctx: &RpcContext) -> Result<Self, Error> {
let s = ctx.db.peek().await.into_public().into_server_info();
Ok(Self {
arch: s.as_arch().de()?,
ram: s.as_ram().de()?,
devices: s.as_devices().de()?,
})
}
}

Expand Down
7 changes: 3 additions & 4 deletions core/startos/src/registry/package/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,13 @@ impl Model<PackageVersionInfo> {
return Ok(false);
}
}
for (class, regex) in hw.device {
for device_filter in hw.device {
if !device_info
.hardware
.devices
.get(&*class)
.unwrap_or(&Vec::new())
.iter()
.any(|product| regex.as_ref().is_match(product))
.filter(|d| d.class() == &*device_filter.class)
.any(|d| device_filter.pattern.as_ref().is_match(d.product()))
{
return Ok(false);
}
Expand Down
16 changes: 13 additions & 3 deletions core/startos/src/s9pk/v1/manifest.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};

use exver::{Version, VersionRange};
use imbl_value::InternedString;
use indexmap::IndexMap;
pub use models::PackageId;
use models::{ActionId, HealthCheckId, ImageId, VolumeId};
Expand All @@ -10,8 +11,8 @@ use url::Url;

use crate::prelude::*;
use crate::s9pk::git_hash::GitHash;
use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements};
use crate::util::serde::{Duration, IoFormat};
use crate::s9pk::manifest::{Alerts, Description};
use crate::util::serde::{Duration, IoFormat, Regex};

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
Expand Down Expand Up @@ -192,6 +193,15 @@ impl DependencyRequirement {
}
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HardwareRequirements {
#[serde(default)]
pub device: BTreeMap<InternedString, Regex>,
pub ram: Option<u64>,
pub arch: Option<BTreeSet<InternedString>>,
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Assets {
Expand Down
20 changes: 18 additions & 2 deletions core/startos/src/s9pk/v2/compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use tokio::process::Command;

use crate::dependencies::{DepInfo, Dependencies};
use crate::prelude::*;
use crate::s9pk::manifest::Manifest;
use crate::s9pk::manifest::{DeviceFilter, Manifest};
use crate::s9pk::merkle_archive::directory_contents::DirectoryContents;
use crate::s9pk::merkle_archive::source::TmpSource;
use crate::s9pk::merkle_archive::{Entry, MerkleArchive};
Expand Down Expand Up @@ -246,7 +246,23 @@ impl TryFrom<ManifestV1> for Manifest {
})
.collect(),
),
hardware_requirements: value.hardware_requirements,
hardware_requirements: super::manifest::HardwareRequirements {
arch: value.hardware_requirements.arch,
ram: value.hardware_requirements.ram,
device: value
.hardware_requirements
.device
.into_iter()
.map(|(class, product)| DeviceFilter {
pattern_description: format!(
"a {class} device matching the expression {}",
product.as_ref()
),
class,
pattern: product,
})
.collect(),
},
git_hash: value.git_hash,
os_version: value.eos_version,
})
Expand Down
14 changes: 12 additions & 2 deletions core/startos/src/s9pk/v2/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,24 @@ impl Manifest {
#[ts(export)]
pub struct HardwareRequirements {
#[serde(default)]
#[ts(type = "{ display?: string, processor?: string }")]
pub device: BTreeMap<String, Regex>, // TODO: array
pub device: Vec<DeviceFilter>,
#[ts(type = "number | null")]
pub ram: Option<u64>,
#[ts(type = "string[] | null")]
pub arch: Option<BTreeSet<InternedString>>,
}

#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct DeviceFilter {
#[ts(type = "\"processor\" | \"display\"")]
pub class: InternedString,
#[ts(type = "string")]
pub pattern: Regex,
pub pattern_description: String,
}

#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct Description {
Expand Down
1 change: 1 addition & 0 deletions core/startos/src/util/lshw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const KNOWN_CLASSES: &[&str] = &["processor", "display"];
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(tag = "class")]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub enum LshwDevice {
Processor(LshwProcessor),
Display(LshwDisplay),
Expand Down
Loading

0 comments on commit 1be9cda

Please sign in to comment.