mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
use hardware requirements to display conflicts and prevent install (#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 <me@drbonez.dev>
This commit is contained in:
676
core/Cargo.lock
generated
676
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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 {
|
||||
@@ -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")]
|
||||
@@ -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
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -127,7 +123,6 @@ impl RpcContext {
|
||||
InitRpcContextPhases {
|
||||
mut load_db,
|
||||
mut init_net_ctrl,
|
||||
mut read_device_info,
|
||||
cleanup_init,
|
||||
}: InitRpcContextPhases,
|
||||
) -> Result<Self, Error> {
|
||||
@@ -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
|
||||
@@ -275,7 +265,6 @@ impl RpcContext {
|
||||
}))
|
||||
.build()
|
||||
.with_kind(crate::ErrorKind::ParseUrl)?,
|
||||
hardware: Hardware { devices, ram },
|
||||
start_time: Instant::now(),
|
||||
crons,
|
||||
// #[cfg(feature = "dev")]
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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(),
|
||||
@@ -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!(
|
||||
@@ -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")]
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
@@ -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()
|
||||
@@ -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(),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
@@ -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")]
|
||||
@@ -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 {
|
||||
|
||||
@@ -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};
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -25,8 +25,9 @@ mod v0_3_6_alpha_3;
|
||||
mod v0_3_6_alpha_4;
|
||||
mod v0_3_6_alpha_5;
|
||||
mod v0_3_6_alpha_6;
|
||||
mod v0_3_6_alpha_7;
|
||||
|
||||
pub type Current = v0_3_6_alpha_6::Version; // VERSION_BUMP
|
||||
pub type Current = v0_3_6_alpha_7::Version; // VERSION_BUMP
|
||||
|
||||
impl Current {
|
||||
#[instrument(skip(self, db))]
|
||||
@@ -102,6 +103,7 @@ enum Version {
|
||||
V0_3_6_alpha_4(Wrapper<v0_3_6_alpha_4::Version>),
|
||||
V0_3_6_alpha_5(Wrapper<v0_3_6_alpha_5::Version>),
|
||||
V0_3_6_alpha_6(Wrapper<v0_3_6_alpha_6::Version>),
|
||||
V0_3_6_alpha_7(Wrapper<v0_3_6_alpha_7::Version>),
|
||||
Other(exver::Version),
|
||||
}
|
||||
|
||||
@@ -132,6 +134,7 @@ impl Version {
|
||||
Self::V0_3_6_alpha_4(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_3_6_alpha_5(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_3_6_alpha_6(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_3_6_alpha_7(v) => DynVersion(Box::new(v.0)),
|
||||
Self::Other(v) => {
|
||||
return Err(Error::new(
|
||||
eyre!("unknown version {v}"),
|
||||
@@ -154,6 +157,7 @@ impl Version {
|
||||
Version::V0_3_6_alpha_4(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_6_alpha_5(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_6_alpha_6(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_6_alpha_7(Wrapper(x)) => x.semver(),
|
||||
Version::Other(x) => x.clone(),
|
||||
}
|
||||
}
|
||||
@@ -172,15 +176,19 @@ fn version_accessor(db: &mut Value) -> Option<&mut Value> {
|
||||
fn version_compat_accessor(db: &mut Value) -> Option<&mut Value> {
|
||||
if db.get("public").is_some() {
|
||||
let server_info = db.get_mut("public")?.get_mut("serverInfo")?;
|
||||
if server_info.get("versionCompat").is_some() {
|
||||
server_info.get_mut("versionCompat")
|
||||
if server_info.get("packageVersionCompat").is_some() {
|
||||
server_info.get_mut("packageVersionCompat")
|
||||
} else {
|
||||
if let Some(prev) = server_info.get("eosVersionCompat").cloned() {
|
||||
server_info
|
||||
.as_object_mut()?
|
||||
.insert("versionCompat".into(), prev);
|
||||
.insert("packageVersionCompat".into(), prev);
|
||||
} else if let Some(prev) = server_info.get("versionCompat").cloned() {
|
||||
server_info
|
||||
.as_object_mut()?
|
||||
.insert("packageVersionCompat".into(), prev);
|
||||
}
|
||||
server_info.get_mut("versionCompat")
|
||||
server_info.get_mut("packageVersionCompat")
|
||||
}
|
||||
} else {
|
||||
db.get_mut("server-info")?.get_mut("eos-version-compat")
|
||||
|
||||
50
core/startos/src/version/v0_3_6_alpha_7.rs
Normal file
50
core/startos/src/version/v0_3_6_alpha_7.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
use imbl_value::{json, InOMap};
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{v0_3_6_alpha_6, VersionT};
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref V0_3_6_alpha_7: exver::Version = exver::Version::new(
|
||||
[0, 3, 6],
|
||||
[PreReleaseSegment::String("alpha".into()), 7.into()]
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_6_alpha_6::Version;
|
||||
type PreUpRes = ();
|
||||
|
||||
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn semver(self) -> exver::Version {
|
||||
V0_3_6_alpha_7.clone()
|
||||
}
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> {
|
||||
let server_info = db["public"]["serverInfo"]
|
||||
.as_object_mut()
|
||||
.or_not_found("public.serverInfo")?;
|
||||
server_info.insert("ram".into(), json!(0));
|
||||
server_info.insert("devices".into(), json!([]));
|
||||
let package_data = db["public"]["packageData"]
|
||||
.as_object_mut()
|
||||
.or_not_found("public.packageData")?;
|
||||
for (_, pde) in package_data.iter_mut() {
|
||||
if let Some(manifest) = pde["stateInfo"].get_mut("manifest") {
|
||||
manifest["hardwareRequirements"]["device"] = json!([]);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user