mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
Feature/hw filtering (#2368)
* update deno * add proxy * remove query params, now auto added by BE * add hardware requirements and BE reg query params * update query params for BE requests * allow multiple arches in hw reqs * explain git hash mismatch * require lshw --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
7
backend/Cargo.lock
generated
7
backend/Cargo.lock
generated
@@ -4760,6 +4760,7 @@ dependencies = [
|
|||||||
"trust-dns-server",
|
"trust-dns-server",
|
||||||
"typed-builder",
|
"typed-builder",
|
||||||
"url",
|
"url",
|
||||||
|
"urlencoding",
|
||||||
"uuid",
|
"uuid",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -5928,6 +5929,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ tracing-subscriber = { version = "0.3.14", features = ["env-filter"] }
|
|||||||
trust-dns-server = "0.22.0"
|
trust-dns-server = "0.22.0"
|
||||||
typed-builder = "0.10.0"
|
typed-builder = "0.10.0"
|
||||||
url = { version = "2.2.2", features = ["serde"] }
|
url = { version = "2.2.2", features = ["serde"] }
|
||||||
|
urlencoding = "2.1.2"
|
||||||
uuid = { version = "1.1.2", features = ["v4"] }
|
uuid = { version = "1.1.2", features = ["v4"] }
|
||||||
zeroize = "1.5.7"
|
zeroize = "1.5.7"
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use helpers::to_tmp_path;
|
|||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use patch_db::json_ptr::JsonPointer;
|
use patch_db::json_ptr::JsonPointer;
|
||||||
use patch_db::{DbHandle, LockReceipt, LockType, PatchDb};
|
use patch_db::{DbHandle, LockReceipt, LockType, PatchDb};
|
||||||
use reqwest::Url;
|
use reqwest::{Client, Proxy, Url};
|
||||||
use rpc_toolkit::Context;
|
use rpc_toolkit::Context;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::postgres::PgConnectOptions;
|
use sqlx::postgres::PgConnectOptions;
|
||||||
@@ -34,7 +34,9 @@ use crate::net::wifi::WpaCli;
|
|||||||
use crate::notifications::NotificationManager;
|
use crate::notifications::NotificationManager;
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
use crate::status::{MainStatus, Status};
|
use crate::status::{MainStatus, Status};
|
||||||
|
use crate::system::get_mem_info;
|
||||||
use crate::util::config::load_config_from_paths;
|
use crate::util::config::load_config_from_paths;
|
||||||
|
use crate::util::lshw::{lshw, LshwDevice};
|
||||||
use crate::{Error, ErrorKind, ResultExt};
|
use crate::{Error, ErrorKind, ResultExt};
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
@@ -120,6 +122,13 @@ pub struct RpcContextSeed {
|
|||||||
pub rpc_stream_continuations: Mutex<BTreeMap<RequestGuid, RpcContinuation>>,
|
pub rpc_stream_continuations: Mutex<BTreeMap<RequestGuid, RpcContinuation>>,
|
||||||
pub wifi_manager: Option<Arc<RwLock<WpaCli>>>,
|
pub wifi_manager: Option<Arc<RwLock<WpaCli>>>,
|
||||||
pub current_secret: Arc<Jwk>,
|
pub current_secret: Arc<Jwk>,
|
||||||
|
pub client: Client,
|
||||||
|
pub hardware: Hardware,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Hardware {
|
||||||
|
pub devices: Vec<LshwDevice>,
|
||||||
|
pub ram: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RpcCleanReceipts {
|
pub struct RpcCleanReceipts {
|
||||||
@@ -203,6 +212,9 @@ impl RpcContext {
|
|||||||
let metrics_cache = RwLock::new(None);
|
let metrics_cache = RwLock::new(None);
|
||||||
let notification_manager = NotificationManager::new(secret_store.clone());
|
let notification_manager = NotificationManager::new(secret_store.clone());
|
||||||
tracing::info!("Initialized Notification Manager");
|
tracing::info!("Initialized Notification Manager");
|
||||||
|
let tor_proxy_url = format!("socks5h://{tor_proxy}");
|
||||||
|
let devices = lshw().await?;
|
||||||
|
let ram = get_mem_info().await?.total.0 as u64 * 1024 * 1024;
|
||||||
let seed = Arc::new(RpcContextSeed {
|
let seed = Arc::new(RpcContextSeed {
|
||||||
is_closed: AtomicBool::new(false),
|
is_closed: AtomicBool::new(false),
|
||||||
datadir: base.datadir().to_path_buf(),
|
datadir: base.datadir().to_path_buf(),
|
||||||
@@ -235,6 +247,17 @@ impl RpcContext {
|
|||||||
)
|
)
|
||||||
})?,
|
})?,
|
||||||
),
|
),
|
||||||
|
client: Client::builder()
|
||||||
|
.proxy(Proxy::custom(move |url| {
|
||||||
|
if url.host_str().map_or(false, |h| h.ends_with(".onion")) {
|
||||||
|
Some(tor_proxy_url.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build()
|
||||||
|
.with_kind(crate::ErrorKind::ParseUrl)?,
|
||||||
|
hardware: Hardware { devices, ram },
|
||||||
});
|
});
|
||||||
|
|
||||||
let res = Self(seed);
|
let res = Self(seed);
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ use crate::dependencies::{
|
|||||||
};
|
};
|
||||||
use crate::install::cleanup::{cleanup, update_dependency_errors_of_dependents};
|
use crate::install::cleanup::{cleanup, update_dependency_errors_of_dependents};
|
||||||
use crate::install::progress::{InstallProgress, InstallProgressTracker};
|
use crate::install::progress::{InstallProgress, InstallProgressTracker};
|
||||||
|
use crate::marketplace::with_query_params;
|
||||||
use crate::notifications::NotificationLevel;
|
use crate::notifications::NotificationLevel;
|
||||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||||
use crate::s9pk::reader::S9pkReader;
|
use crate::s9pk::reader::S9pkReader;
|
||||||
@@ -136,35 +137,39 @@ pub async fn install(
|
|||||||
let marketplace_url =
|
let marketplace_url =
|
||||||
marketplace_url.unwrap_or_else(|| crate::DEFAULT_MARKETPLACE.parse().unwrap());
|
marketplace_url.unwrap_or_else(|| crate::DEFAULT_MARKETPLACE.parse().unwrap());
|
||||||
let version_priority = version_priority.unwrap_or_default();
|
let version_priority = version_priority.unwrap_or_default();
|
||||||
let man: Manifest = reqwest::get(format!(
|
let man: Manifest = ctx
|
||||||
"{}/package/v0/manifest/{}?spec={}&version-priority={}&eos-version-compat={}&arch={}",
|
.client
|
||||||
marketplace_url,
|
.get(with_query_params(
|
||||||
id,
|
&ctx,
|
||||||
version,
|
format!(
|
||||||
version_priority,
|
"{}/package/v0/manifest/{}?spec={}&version-priority={}",
|
||||||
Current::new().compat(),
|
marketplace_url, id, version, version_priority,
|
||||||
&*crate::ARCH,
|
)
|
||||||
))
|
.parse()?,
|
||||||
.await
|
))
|
||||||
.with_kind(crate::ErrorKind::Registry)?
|
.send()
|
||||||
.error_for_status()
|
.await
|
||||||
.with_kind(crate::ErrorKind::Registry)?
|
.with_kind(crate::ErrorKind::Registry)?
|
||||||
.json()
|
.error_for_status()
|
||||||
.await
|
.with_kind(crate::ErrorKind::Registry)?
|
||||||
.with_kind(crate::ErrorKind::Registry)?;
|
.json()
|
||||||
let s9pk = reqwest::get(format!(
|
.await
|
||||||
"{}/package/v0/{}.s9pk?spec=={}&version-priority={}&eos-version-compat={}&arch={}",
|
.with_kind(crate::ErrorKind::Registry)?;
|
||||||
marketplace_url,
|
let s9pk = ctx
|
||||||
id,
|
.client
|
||||||
man.version,
|
.get(with_query_params(
|
||||||
version_priority,
|
&ctx,
|
||||||
Current::new().compat(),
|
format!(
|
||||||
&*crate::ARCH,
|
"{}/package/v0/{}.s9pk?spec=={}&version-priority={}",
|
||||||
))
|
marketplace_url, id, man.version, version_priority,
|
||||||
.await
|
)
|
||||||
.with_kind(crate::ErrorKind::Registry)?
|
.parse()?,
|
||||||
.error_for_status()
|
))
|
||||||
.with_kind(crate::ErrorKind::Registry)?;
|
.send()
|
||||||
|
.await
|
||||||
|
.with_kind(crate::ErrorKind::Registry)?
|
||||||
|
.error_for_status()
|
||||||
|
.with_kind(crate::ErrorKind::Registry)?;
|
||||||
|
|
||||||
if man.id.as_str() != id || !man.version.satisfies(&version) {
|
if man.id.as_str() != id || !man.version.satisfies(&version) {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
@@ -185,16 +190,18 @@ pub async fn install(
|
|||||||
async {
|
async {
|
||||||
tokio::io::copy(
|
tokio::io::copy(
|
||||||
&mut response_to_reader(
|
&mut response_to_reader(
|
||||||
reqwest::get(format!(
|
ctx.client
|
||||||
"{}/package/v0/license/{}?spec=={}&eos-version-compat={}&arch={}",
|
.get(with_query_params(
|
||||||
marketplace_url,
|
&ctx,
|
||||||
id,
|
format!(
|
||||||
man.version,
|
"{}/package/v0/license/{}?spec=={}",
|
||||||
Current::new().compat(),
|
marketplace_url, id, man.version,
|
||||||
&*crate::ARCH,
|
)
|
||||||
))
|
.parse()?,
|
||||||
.await?
|
))
|
||||||
.error_for_status()?,
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?,
|
||||||
),
|
),
|
||||||
&mut File::create(public_dir_path.join("LICENSE.md")).await?,
|
&mut File::create(public_dir_path.join("LICENSE.md")).await?,
|
||||||
)
|
)
|
||||||
@@ -204,16 +211,18 @@ pub async fn install(
|
|||||||
async {
|
async {
|
||||||
tokio::io::copy(
|
tokio::io::copy(
|
||||||
&mut response_to_reader(
|
&mut response_to_reader(
|
||||||
reqwest::get(format!(
|
ctx.client
|
||||||
"{}/package/v0/instructions/{}?spec=={}&eos-version-compat={}&arch={}",
|
.get(with_query_params(
|
||||||
marketplace_url,
|
&ctx,
|
||||||
id,
|
format!(
|
||||||
man.version,
|
"{}/package/v0/instructions/{}?spec=={}",
|
||||||
Current::new().compat(),
|
marketplace_url, id, man.version,
|
||||||
&*crate::ARCH,
|
)
|
||||||
))
|
.parse()?,
|
||||||
.await?
|
))
|
||||||
.error_for_status()?,
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?,
|
||||||
),
|
),
|
||||||
&mut File::create(public_dir_path.join("INSTRUCTIONS.md")).await?,
|
&mut File::create(public_dir_path.join("INSTRUCTIONS.md")).await?,
|
||||||
)
|
)
|
||||||
@@ -223,16 +232,18 @@ pub async fn install(
|
|||||||
async {
|
async {
|
||||||
tokio::io::copy(
|
tokio::io::copy(
|
||||||
&mut response_to_reader(
|
&mut response_to_reader(
|
||||||
reqwest::get(format!(
|
ctx.client
|
||||||
"{}/package/v0/icon/{}?spec=={}&eos-version-compat={}&arch={}",
|
.get(with_query_params(
|
||||||
marketplace_url,
|
&ctx,
|
||||||
id,
|
format!(
|
||||||
man.version,
|
"{}/package/v0/icon/{}?spec=={}",
|
||||||
Current::new().compat(),
|
marketplace_url, id, man.version,
|
||||||
&*crate::ARCH,
|
)
|
||||||
))
|
.parse()?,
|
||||||
.await?
|
))
|
||||||
.error_for_status()?,
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?,
|
||||||
),
|
),
|
||||||
&mut File::create(public_dir_path.join(format!("icon.{}", icon_type))).await?,
|
&mut File::create(public_dir_path.join(format!("icon.{}", icon_type))).await?,
|
||||||
)
|
)
|
||||||
@@ -928,17 +939,20 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
|||||||
{
|
{
|
||||||
Some(local_man)
|
Some(local_man)
|
||||||
} else if let Some(marketplace_url) = &marketplace_url {
|
} else if let Some(marketplace_url) = &marketplace_url {
|
||||||
match reqwest::get(format!(
|
match ctx
|
||||||
"{}/package/v0/manifest/{}?spec={}&eos-version-compat={}&arch={}",
|
.client
|
||||||
marketplace_url,
|
.get(with_query_params(
|
||||||
dep,
|
ctx,
|
||||||
info.version,
|
format!(
|
||||||
Current::new().compat(),
|
"{}/package/v0/manifest/{}?spec={}",
|
||||||
&*crate::ARCH,
|
marketplace_url, dep, info.version,
|
||||||
))
|
)
|
||||||
.await
|
.parse()?,
|
||||||
.with_kind(crate::ErrorKind::Registry)?
|
))
|
||||||
.error_for_status()
|
.send()
|
||||||
|
.await
|
||||||
|
.with_kind(crate::ErrorKind::Registry)?
|
||||||
|
.error_for_status()
|
||||||
{
|
{
|
||||||
Ok(a) => Ok(Some(
|
Ok(a) => Ok(Some(
|
||||||
a.json()
|
a.json()
|
||||||
@@ -963,16 +977,19 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
|||||||
let icon_path = dir.join(format!("icon.{}", manifest.assets.icon_type()));
|
let icon_path = dir.join(format!("icon.{}", manifest.assets.icon_type()));
|
||||||
if tokio::fs::metadata(&icon_path).await.is_err() {
|
if tokio::fs::metadata(&icon_path).await.is_err() {
|
||||||
tokio::fs::create_dir_all(&dir).await?;
|
tokio::fs::create_dir_all(&dir).await?;
|
||||||
let icon = reqwest::get(format!(
|
let icon = ctx
|
||||||
"{}/package/v0/icon/{}?spec={}&eos-version-compat={}&arch={}",
|
.client
|
||||||
marketplace_url,
|
.get(with_query_params(
|
||||||
dep,
|
ctx,
|
||||||
info.version,
|
format!(
|
||||||
Current::new().compat(),
|
"{}/package/v0/icon/{}?spec={}",
|
||||||
&*crate::ARCH,
|
marketplace_url, dep, info.version,
|
||||||
))
|
)
|
||||||
.await
|
.parse()?,
|
||||||
.with_kind(crate::ErrorKind::Registry)?;
|
))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.with_kind(crate::ErrorKind::Registry)?;
|
||||||
let mut dst = File::create(&icon_path).await?;
|
let mut dst = File::create(&icon_path).await?;
|
||||||
tokio::io::copy(&mut response_to_reader(icon), &mut dst).await?;
|
tokio::io::copy(&mut response_to_reader(icon), &mut dst).await?;
|
||||||
dst.sync_all().await?;
|
dst.sync_all().await?;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ use reqwest::{StatusCode, Url};
|
|||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::context::RpcContext;
|
||||||
|
use crate::version::VersionT;
|
||||||
use crate::{Error, ResultExt};
|
use crate::{Error, ResultExt};
|
||||||
|
|
||||||
#[command(subcommands(get))]
|
#[command(subcommands(get))]
|
||||||
@@ -10,9 +12,34 @@ pub fn marketplace() -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_query_params(ctx: &RpcContext, mut url: Url) -> Url {
|
||||||
|
url.query_pairs_mut()
|
||||||
|
.append_pair(
|
||||||
|
"os.version",
|
||||||
|
&crate::version::Current::new().semver().to_string(),
|
||||||
|
)
|
||||||
|
.append_pair(
|
||||||
|
"os.compat",
|
||||||
|
&crate::version::Current::new().compat().to_string(),
|
||||||
|
)
|
||||||
|
.append_pair("os.arch", crate::OS_ARCH)
|
||||||
|
.append_pair("hardware.arch", &*crate::ARCH)
|
||||||
|
.append_pair("hardware.ram", &ctx.hardware.ram.to_string());
|
||||||
|
|
||||||
|
for hw in &ctx.hardware.devices {
|
||||||
|
url.query_pairs_mut()
|
||||||
|
.append_pair(&format!("hardware.device.{}", hw.class()), hw.product());
|
||||||
|
}
|
||||||
|
|
||||||
|
url
|
||||||
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub async fn get(#[arg] url: Url) -> Result<Value, Error> {
|
pub async fn get(#[context] ctx: RpcContext, #[arg] url: Url) -> Result<Value, Error> {
|
||||||
let mut response = reqwest::get(url)
|
let mut response = ctx
|
||||||
|
.client
|
||||||
|
.get(with_query_params(&ctx, url))
|
||||||
|
.send()
|
||||||
.await
|
.await
|
||||||
.with_kind(crate::ErrorKind::Network)?;
|
.with_kind(crate::ErrorKind::Network)?;
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ static NOT_AUTHORIZED: &[u8] = b"Not Authorized";
|
|||||||
|
|
||||||
static EMBEDDED_UIS: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../frontend/dist/static");
|
static EMBEDDED_UIS: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../frontend/dist/static");
|
||||||
|
|
||||||
|
const PROXY_STRIP_HEADERS: &[&str] = &["cookie", "host", "origin", "referer", "user-agent"];
|
||||||
|
|
||||||
fn status_fn(_: i32) -> StatusCode {
|
fn status_fn(_: i32) -> StatusCode {
|
||||||
StatusCode::OK
|
StatusCode::OK
|
||||||
}
|
}
|
||||||
@@ -236,15 +238,6 @@ pub async fn main_ui_server_router(ctx: RpcContext) -> Result<HttpHandler, Error
|
|||||||
|
|
||||||
async fn alt_ui(req: Request<Body>, ui_mode: UiMode) -> Result<Response<Body>, Error> {
|
async fn alt_ui(req: Request<Body>, ui_mode: UiMode) -> Result<Response<Body>, Error> {
|
||||||
let (request_parts, _body) = req.into_parts();
|
let (request_parts, _body) = req.into_parts();
|
||||||
let accept_encoding = request_parts
|
|
||||||
.headers
|
|
||||||
.get_all(ACCEPT_ENCODING)
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|h| h.to_str().ok())
|
|
||||||
.flat_map(|s| s.split(","))
|
|
||||||
.filter_map(|s| s.split(";").next())
|
|
||||||
.map(|s| s.trim())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
match &request_parts.method {
|
match &request_parts.method {
|
||||||
&Method::GET => {
|
&Method::GET => {
|
||||||
let uri_path = ui_mode.path(
|
let uri_path = ui_mode.path(
|
||||||
@@ -307,6 +300,40 @@ async fn main_embassy_ui(req: Request<Body>, ctx: RpcContext) -> Result<Response
|
|||||||
Err(e) => un_authorized(e, &format!("public/{path}")),
|
Err(e) => un_authorized(e, &format!("public/{path}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(&Method::GET, Some(("proxy", target))) => {
|
||||||
|
match HasValidSession::from_request_parts(&request_parts, &ctx).await {
|
||||||
|
Ok(_) => {
|
||||||
|
let target = urlencoding::decode(target)?;
|
||||||
|
let res = ctx
|
||||||
|
.client
|
||||||
|
.get(target.as_ref())
|
||||||
|
.headers(
|
||||||
|
request_parts
|
||||||
|
.headers
|
||||||
|
.iter()
|
||||||
|
.filter(|(h, _)| {
|
||||||
|
!PROXY_STRIP_HEADERS
|
||||||
|
.iter()
|
||||||
|
.any(|bad| h.as_str().eq_ignore_ascii_case(bad))
|
||||||
|
})
|
||||||
|
.map(|(h, v)| (h.clone(), v.clone()))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.with_kind(crate::ErrorKind::Network)?;
|
||||||
|
let mut hres = Response::builder().status(res.status());
|
||||||
|
for (h, v) in res.headers().clone() {
|
||||||
|
if let Some(h) = h {
|
||||||
|
hres = hres.header(h, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hres.body(Body::wrap_stream(res.bytes_stream()))
|
||||||
|
.with_kind(crate::ErrorKind::Network)
|
||||||
|
}
|
||||||
|
Err(e) => un_authorized(e, &format!("proxy/{target}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
(&Method::GET, Some(("eos", "local.crt"))) => {
|
(&Method::GET, Some(("eos", "local.crt"))) => {
|
||||||
match HasValidSession::from_request_parts(&request_parts, &ctx).await {
|
match HasValidSession::from_request_parts(&request_parts, &ctx).await {
|
||||||
Ok(_) => cert_send(&ctx.account.read().await.root_ca_cert),
|
Ok(_) => cert_send(&ctx.account.read().await.root_ca_cert),
|
||||||
@@ -550,8 +577,3 @@ fn e_tag(path: &Path, metadata: Option<&Metadata>) -> String {
|
|||||||
base32::encode(base32::Alphabet::RFC4648 { padding: false }, res.as_slice()).to_lowercase()
|
base32::encode(base32::Alphabet::RFC4648 { padding: false }, res.as_slice()).to_lowercase()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_packed_html() {
|
|
||||||
assert!(MainUi::get("index.html").is_some())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
@@ -16,6 +17,7 @@ use crate::net::interface::Interfaces;
|
|||||||
use crate::procedure::docker::DockerContainers;
|
use crate::procedure::docker::DockerContainers;
|
||||||
use crate::procedure::PackageProcedure;
|
use crate::procedure::PackageProcedure;
|
||||||
use crate::status::health_check::HealthChecks;
|
use crate::status::health_check::HealthChecks;
|
||||||
|
use crate::util::serde::Regex;
|
||||||
use crate::util::Version;
|
use crate::util::Version;
|
||||||
use crate::version::{Current, VersionT};
|
use crate::version::{Current, VersionT};
|
||||||
use crate::volume::Volumes;
|
use crate::volume::Volumes;
|
||||||
@@ -79,6 +81,9 @@ pub struct Manifest {
|
|||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub replaces: Vec<String>,
|
pub replaces: Vec<String>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub hardware_requirements: HardwareRequirements,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Manifest {
|
impl Manifest {
|
||||||
@@ -109,6 +114,15 @@ impl Manifest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct HardwareRequirements {
|
||||||
|
#[serde(default)]
|
||||||
|
device: BTreeMap<String, Regex>,
|
||||||
|
ram: Option<u64>,
|
||||||
|
arch: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct Assets {
|
pub struct Assets {
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ impl<'de> Deserialize<'de> for Percentage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MebiBytes(f64);
|
pub struct MebiBytes(pub f64);
|
||||||
impl Serialize for MebiBytes {
|
impl Serialize for MebiBytes {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
@@ -310,19 +310,19 @@ pub struct MetricsGeneral {
|
|||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
pub struct MetricsMemory {
|
pub struct MetricsMemory {
|
||||||
#[serde(rename = "Percentage Used")]
|
#[serde(rename = "Percentage Used")]
|
||||||
percentage_used: Percentage,
|
pub percentage_used: Percentage,
|
||||||
#[serde(rename = "Total")]
|
#[serde(rename = "Total")]
|
||||||
total: MebiBytes,
|
pub total: MebiBytes,
|
||||||
#[serde(rename = "Available")]
|
#[serde(rename = "Available")]
|
||||||
available: MebiBytes,
|
pub available: MebiBytes,
|
||||||
#[serde(rename = "Used")]
|
#[serde(rename = "Used")]
|
||||||
used: MebiBytes,
|
pub used: MebiBytes,
|
||||||
#[serde(rename = "Swap Total")]
|
#[serde(rename = "Swap Total")]
|
||||||
swap_total: MebiBytes,
|
pub swap_total: MebiBytes,
|
||||||
#[serde(rename = "Swap Free")]
|
#[serde(rename = "Swap Free")]
|
||||||
swap_free: MebiBytes,
|
pub swap_free: MebiBytes,
|
||||||
#[serde(rename = "Swap Used")]
|
#[serde(rename = "Swap Used")]
|
||||||
swap_used: MebiBytes,
|
pub swap_used: MebiBytes,
|
||||||
}
|
}
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
pub struct MetricsCpu {
|
pub struct MetricsCpu {
|
||||||
@@ -698,7 +698,7 @@ pub struct MemInfo {
|
|||||||
swap_free: Option<u64>,
|
swap_free: Option<u64>,
|
||||||
}
|
}
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn get_mem_info() -> Result<MetricsMemory, Error> {
|
pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
|
||||||
let contents = tokio::fs::read_to_string("/proc/meminfo").await?;
|
let contents = tokio::fs::read_to_string("/proc/meminfo").await?;
|
||||||
let mut mem_info = MemInfo {
|
let mut mem_info = MemInfo {
|
||||||
mem_total: None,
|
mem_total: None,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use crate::db::model::UpdateProgress;
|
|||||||
use crate::disk::mount::filesystem::bind::Bind;
|
use crate::disk::mount::filesystem::bind::Bind;
|
||||||
use crate::disk::mount::filesystem::ReadWrite;
|
use crate::disk::mount::filesystem::ReadWrite;
|
||||||
use crate::disk::mount::guard::MountGuard;
|
use crate::disk::mount::guard::MountGuard;
|
||||||
|
use crate::marketplace::with_query_params;
|
||||||
use crate::notifications::NotificationLevel;
|
use crate::notifications::NotificationLevel;
|
||||||
use crate::sound::{
|
use crate::sound::{
|
||||||
CIRCLE_OF_5THS_SHORT, UPDATE_FAILED_1, UPDATE_FAILED_2, UPDATE_FAILED_3, UPDATE_FAILED_4,
|
CIRCLE_OF_5THS_SHORT, UPDATE_FAILED_1, UPDATE_FAILED_2, UPDATE_FAILED_3, UPDATE_FAILED_4,
|
||||||
@@ -81,18 +82,19 @@ async fn maybe_do_update(
|
|||||||
marketplace_url: Url,
|
marketplace_url: Url,
|
||||||
) -> Result<Option<Arc<Revision>>, Error> {
|
) -> Result<Option<Arc<Revision>>, Error> {
|
||||||
let mut db = ctx.db.handle();
|
let mut db = ctx.db.handle();
|
||||||
let latest_version: Version = reqwest::get(format!(
|
let latest_version: Version = ctx
|
||||||
"{}/eos/v0/latest?eos-version={}&arch={}",
|
.client
|
||||||
marketplace_url,
|
.get(with_query_params(
|
||||||
Current::new().semver(),
|
&ctx,
|
||||||
OS_ARCH,
|
format!("{}/eos/v0/latest", marketplace_url,).parse()?,
|
||||||
))
|
))
|
||||||
.await
|
.send()
|
||||||
.with_kind(ErrorKind::Network)?
|
.await
|
||||||
.json::<LatestInformation>()
|
.with_kind(ErrorKind::Network)?
|
||||||
.await
|
.json::<LatestInformation>()
|
||||||
.with_kind(ErrorKind::Network)?
|
.await
|
||||||
.version;
|
.with_kind(ErrorKind::Network)?
|
||||||
|
.version;
|
||||||
crate::db::DatabaseModel::new()
|
crate::db::DatabaseModel::new()
|
||||||
.server_info()
|
.server_info()
|
||||||
.lock(&mut db, LockType::Write)
|
.lock(&mut db, LockType::Write)
|
||||||
|
|||||||
49
backend/src/util/lshw.rs
Normal file
49
backend/src/util/lshw.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use models::{Error, ResultExt};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::util::Invoke;
|
||||||
|
|
||||||
|
const KNOWN_CLASSES: &[&str] = &["processor", "display"];
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(tag = "class")]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum LshwDevice {
|
||||||
|
Processor(LshwProcessor),
|
||||||
|
Display(LshwDisplay),
|
||||||
|
}
|
||||||
|
impl LshwDevice {
|
||||||
|
pub fn class(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Processor(_) => "processor",
|
||||||
|
Self::Display(_) => "display",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn product(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Processor(hw) => hw.product.as_str(),
|
||||||
|
Self::Display(hw) => hw.product.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct LshwProcessor {
|
||||||
|
pub product: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct LshwDisplay {
|
||||||
|
pub product: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn lshw() -> Result<Vec<LshwDevice>, Error> {
|
||||||
|
let mut cmd = Command::new("lshw");
|
||||||
|
cmd.arg("-json");
|
||||||
|
for class in KNOWN_CLASSES {
|
||||||
|
cmd.arg("-class").arg(*class);
|
||||||
|
}
|
||||||
|
serde_json::from_slice(&cmd.invoke(crate::ErrorKind::Lshw).await?)
|
||||||
|
.with_kind(crate::ErrorKind::Deserialization)
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ pub mod config;
|
|||||||
pub mod http_reader;
|
pub mod http_reader;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
|
pub mod lshw;
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
|||||||
@@ -793,3 +793,42 @@ impl<T: AsRef<[u8]>> Serialize for Base64<T> {
|
|||||||
serializer.serialize_str(&base64::encode(self.0.as_ref()))
|
serializer.serialize_str(&base64::encode(self.0.as_ref()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Regex(regex::Regex);
|
||||||
|
impl From<Regex> for regex::Regex {
|
||||||
|
fn from(value: Regex) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<regex::Regex> for Regex {
|
||||||
|
fn from(value: regex::Regex) -> Self {
|
||||||
|
Regex(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<regex::Regex> for Regex {
|
||||||
|
fn as_ref(&self) -> ®ex::Regex {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsMut<regex::Regex> for Regex {
|
||||||
|
fn as_mut(&mut self) -> &mut regex::Regex {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'de> Deserialize<'de> for Regex {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserialize_from_str(deserializer).map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Serialize for Regex {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serialize_display(&self.0, serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ iw
|
|||||||
jq
|
jq
|
||||||
libavahi-client3
|
libavahi-client3
|
||||||
lm-sensors
|
lm-sensors
|
||||||
|
lshw
|
||||||
lvm2
|
lvm2
|
||||||
magic-wormhole
|
magic-wormhole
|
||||||
man-db
|
man-db
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ sudo losetup -d $OUTPUT_DEVICE
|
|||||||
if [ "$ALLOW_VERSION_MISMATCH" != 1 ]; then
|
if [ "$ALLOW_VERSION_MISMATCH" != 1 ]; then
|
||||||
if [ "$(cat GIT_HASH.txt)" != "$REAL_GIT_HASH" ]; then
|
if [ "$(cat GIT_HASH.txt)" != "$REAL_GIT_HASH" ]; then
|
||||||
>&2 echo "startos.raspberrypi.squashfs GIT_HASH.txt mismatch"
|
>&2 echo "startos.raspberrypi.squashfs GIT_HASH.txt mismatch"
|
||||||
|
>&2 echo "expected $REAL_GIT_HASH (dpkg) found $(cat GIT_HASH.txt) (repo)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ "$(cat VERSION.txt)" != "$REAL_VERSION" ]; then
|
if [ "$(cat VERSION.txt)" != "$REAL_VERSION" ]; then
|
||||||
|
|||||||
@@ -259,19 +259,14 @@ export module RR {
|
|||||||
|
|
||||||
// marketplace
|
// marketplace
|
||||||
|
|
||||||
export type EnvInfo = {
|
export type GetMarketplaceInfoReq = { 'server-id': string }
|
||||||
'server-id': string
|
|
||||||
'eos-version': string
|
|
||||||
}
|
|
||||||
export type GetMarketplaceInfoReq = EnvInfo
|
|
||||||
export type GetMarketplaceInfoRes = StoreInfo
|
export type GetMarketplaceInfoRes = StoreInfo
|
||||||
|
|
||||||
export type GetMarketplaceEosReq = EnvInfo
|
export type GetMarketplaceEosReq = { 'server-id': string }
|
||||||
export type GetMarketplaceEosRes = MarketplaceEOS
|
export type GetMarketplaceEosRes = MarketplaceEOS
|
||||||
|
|
||||||
export type GetMarketplacePackagesReq = {
|
export type GetMarketplacePackagesReq = {
|
||||||
ids?: { id: string; version: string }[]
|
ids?: { id: string; version: string }[]
|
||||||
'eos-version-compat': string
|
|
||||||
// iff !ids
|
// iff !ids
|
||||||
category?: string
|
category?: string
|
||||||
query?: string
|
query?: string
|
||||||
|
|||||||
@@ -125,7 +125,6 @@ export abstract class ApiService {
|
|||||||
path: string,
|
path: string,
|
||||||
params: Record<string, unknown>,
|
params: Record<string, unknown>,
|
||||||
url: string,
|
url: string,
|
||||||
arch?: string,
|
|
||||||
): Promise<T>
|
): Promise<T>
|
||||||
|
|
||||||
abstract getEos(): Promise<RR.GetMarketplaceEosRes>
|
abstract getEos(): Promise<RR.GetMarketplaceEosRes>
|
||||||
|
|||||||
@@ -212,10 +212,7 @@ export class LiveApiService extends ApiService {
|
|||||||
path: string,
|
path: string,
|
||||||
qp: Record<string, string>,
|
qp: Record<string, string>,
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
arch: string = this.config.packageArch,
|
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
// Object.assign(qp, { arch })
|
|
||||||
qp['arch'] = arch
|
|
||||||
const fullUrl = `${baseUrl}${path}?${new URLSearchParams(qp).toString()}`
|
const fullUrl = `${baseUrl}${path}?${new URLSearchParams(qp).toString()}`
|
||||||
return this.rpcRequest({
|
return this.rpcRequest({
|
||||||
method: 'marketplace.get',
|
method: 'marketplace.get',
|
||||||
@@ -224,17 +221,13 @@ export class LiveApiService extends ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getEos(): Promise<RR.GetMarketplaceEosRes> {
|
async getEos(): Promise<RR.GetMarketplaceEosRes> {
|
||||||
const { id, version } = await getServerInfo(this.patch)
|
const { id } = await getServerInfo(this.patch)
|
||||||
const qp: RR.GetMarketplaceEosReq = {
|
const qp: RR.GetMarketplaceEosReq = { 'server-id': id }
|
||||||
'server-id': id,
|
|
||||||
'eos-version': version,
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.marketplaceProxy(
|
return this.marketplaceProxy(
|
||||||
'/eos/v0/latest',
|
'/eos/v0/latest',
|
||||||
qp,
|
qp,
|
||||||
this.config.marketplace.start9,
|
this.config.marketplace.start9,
|
||||||
this.config.osArch,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -352,7 +352,6 @@ export class MockApiService extends ApiService {
|
|||||||
path: string,
|
path: string,
|
||||||
params: Record<string, string>,
|
params: Record<string, string>,
|
||||||
url: string,
|
url: string,
|
||||||
arch = '',
|
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
|
|
||||||
|
|||||||
@@ -217,10 +217,7 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
return this.patch.watch$('server-info').pipe(
|
return this.patch.watch$('server-info').pipe(
|
||||||
take(1),
|
take(1),
|
||||||
switchMap(serverInfo => {
|
switchMap(serverInfo => {
|
||||||
const qp: RR.GetMarketplaceInfoReq = {
|
const qp: RR.GetMarketplaceInfoReq = { 'server-id': serverInfo.id }
|
||||||
'server-id': serverInfo.id,
|
|
||||||
'eos-version': serverInfo.version,
|
|
||||||
}
|
|
||||||
return this.api.marketplaceProxy<RR.GetMarketplaceInfoRes>(
|
return this.api.marketplaceProxy<RR.GetMarketplaceInfoRes>(
|
||||||
'/package/v0/info',
|
'/package/v0/info',
|
||||||
qp,
|
qp,
|
||||||
@@ -274,28 +271,21 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
|
|
||||||
private fetchPackages$(
|
private fetchPackages$(
|
||||||
url: string,
|
url: string,
|
||||||
params: Omit<
|
params: Omit<RR.GetMarketplacePackagesReq, 'page' | 'per-page'> = {},
|
||||||
RR.GetMarketplacePackagesReq,
|
|
||||||
'eos-version-compat' | 'page' | 'per-page'
|
|
||||||
> = {},
|
|
||||||
): Observable<MarketplacePkg[]> {
|
): Observable<MarketplacePkg[]> {
|
||||||
return this.patch.watch$('server-info', 'eos-version-compat').pipe(
|
const qp: RR.GetMarketplacePackagesReq = {
|
||||||
take(1),
|
...params,
|
||||||
switchMap(versionCompat => {
|
page: 1,
|
||||||
const qp: RR.GetMarketplacePackagesReq = {
|
'per-page': 100,
|
||||||
...params,
|
}
|
||||||
'eos-version-compat': versionCompat,
|
if (qp.ids) qp.ids = JSON.stringify(qp.ids)
|
||||||
page: 1,
|
|
||||||
'per-page': 100,
|
|
||||||
}
|
|
||||||
if (qp.ids) qp.ids = JSON.stringify(qp.ids)
|
|
||||||
|
|
||||||
return this.api.marketplaceProxy<RR.GetMarketplacePackagesRes>(
|
return from(
|
||||||
'/package/v0/index',
|
this.api.marketplaceProxy<RR.GetMarketplacePackagesRes>(
|
||||||
qp,
|
'/package/v0/index',
|
||||||
url,
|
qp,
|
||||||
)
|
url,
|
||||||
}),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ pub enum ErrorKind {
|
|||||||
Systemd = 65,
|
Systemd = 65,
|
||||||
OpenSsh = 66,
|
OpenSsh = 66,
|
||||||
Zram = 67,
|
Zram = 67,
|
||||||
|
Lshw = 68,
|
||||||
}
|
}
|
||||||
impl ErrorKind {
|
impl ErrorKind {
|
||||||
pub fn as_str(&self) -> &'static str {
|
pub fn as_str(&self) -> &'static str {
|
||||||
@@ -148,6 +149,7 @@ impl ErrorKind {
|
|||||||
Systemd => "Systemd Error",
|
Systemd => "Systemd Error",
|
||||||
OpenSsh => "OpenSSH Error",
|
OpenSsh => "OpenSSH Error",
|
||||||
Zram => "Zram Error",
|
Zram => "Zram Error",
|
||||||
|
Lshw => "LSHW Error",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
system-images/compat/Cargo.lock
generated
7
system-images/compat/Cargo.lock
generated
@@ -4296,6 +4296,7 @@ dependencies = [
|
|||||||
"trust-dns-server",
|
"trust-dns-server",
|
||||||
"typed-builder",
|
"typed-builder",
|
||||||
"url",
|
"url",
|
||||||
|
"urlencoding",
|
||||||
"uuid 1.2.2",
|
"uuid 1.2.2",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -5021,6 +5022,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
|||||||
Reference in New Issue
Block a user