diff --git a/backend/src/backup/backup_bulk.rs b/backend/src/backup/backup_bulk.rs index 4e50d35a8..6af6cd231 100644 --- a/backend/src/backup/backup_bulk.rs +++ b/backend/src/backup/backup_bulk.rs @@ -18,7 +18,6 @@ use super::PackageBackupReport; use crate::auth::check_password_against_db; use crate::backup::{BackupReport, ServerBackupReport}; use crate::context::RpcContext; -use crate::db::model::ServerStatus; use crate::db::util::WithRevision; use crate::disk::mount::backup::BackupMountGuard; use crate::disk::mount::guard::TmpMountGuard; @@ -134,7 +133,10 @@ pub async fn backup_all( let revision = assure_backing_up(&mut db).await?; tokio::task::spawn(async move { let backup_res = perform_backup(&ctx, &mut db, backup_guard).await; - let status_model = crate::db::DatabaseModel::new().server_info().status(); + let status_model = crate::db::DatabaseModel::new() + .server_info() + .status_info() + .backing_up(); status_model .clone() .lock(&mut db, LockType::Write) @@ -203,7 +205,7 @@ pub async fn backup_all( } } status_model - .put(&mut db, &ServerStatus::Running) + .put(&mut db, &false) .await .expect("failed to change server status"); }); @@ -216,33 +218,21 @@ pub async fn backup_all( #[instrument(skip(db))] async fn assure_backing_up(db: &mut PatchDbHandle) -> Result>, Error> { let mut tx = db.begin().await?; - let mut info = crate::db::DatabaseModel::new() + let mut backing_up = crate::db::DatabaseModel::new() .server_info() + .status_info() + .backing_up() .get_mut(&mut tx) .await?; - match &info.status { - ServerStatus::Updating => { - return Err(Error::new( - eyre!("Server is updating!"), - crate::ErrorKind::InvalidRequest, - )) - } - ServerStatus::Updated => { - return Err(Error::new( - eyre!("Server is updated and needs to be reset"), - crate::ErrorKind::InvalidRequest, - )) - } - ServerStatus::BackingUp => { - return Err(Error::new( - eyre!("Server is already backing up!"), - crate::ErrorKind::InvalidRequest, - )) - } - ServerStatus::Running => (), + + if *backing_up { + return Err(Error::new( + eyre!("Server is already backing up!"), + crate::ErrorKind::InvalidRequest, + )); } - info.status = ServerStatus::BackingUp; - info.save(&mut tx).await?; + *backing_up = true; + backing_up.save(&mut tx).await?; Ok(tx.commit(None).await?) } diff --git a/backend/src/backup/restore.rs b/backend/src/backup/restore.rs index a9094b39d..2f316f86c 100644 --- a/backend/src/backup/restore.rs +++ b/backend/src/backup/restore.rs @@ -377,7 +377,7 @@ async fn restore_package<'a>( Ok(( progress.clone(), async move { - download_install_s9pk(&ctx, &manifest, progress, file).await?; + download_install_s9pk(&ctx, &manifest, None, progress, file).await?; guard.unmount().await?; diff --git a/backend/src/context/rpc.rs b/backend/src/context/rpc.rs index 81e6b1474..2afe03b2e 100644 --- a/backend/src/context/rpc.rs +++ b/backend/src/context/rpc.rs @@ -23,7 +23,7 @@ use tracing::instrument; use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::db::model::{Database, InstalledPackageDataEntry, PackageDataEntry}; -use crate::hostname::{derive_hostname, derive_id, get_hostname, get_id, get_product_key}; +use crate::hostname::{derive_hostname, derive_id, get_product_key}; use crate::install::cleanup::{cleanup_failed, uninstall}; use crate::manager::ManagerMap; use crate::middleware::auth::HashSessionToken; @@ -230,31 +230,6 @@ impl RpcContext { tracing::info!("Initialized Package Managers"); Ok(res) } - #[instrument(skip(self))] - pub async fn package_registry_url(&self) -> Result { - Ok( - if let Some(market) = crate::db::DatabaseModel::new() - .server_info() - .package_marketplace() - .get(&mut self.db.handle(), false) - .await? - .to_owned() - { - market - } else { - self.eos_registry_url().await? - }, - ) - } - #[instrument(skip(self))] - pub async fn eos_registry_url(&self) -> Result { - Ok(crate::db::DatabaseModel::new() - .server_info() - .eos_marketplace() - .get(&mut self.db.handle(), false) - .await? - .to_owned()) - } #[instrument(skip(self, db))] pub async fn set_nginx_conf(&self, db: &mut Db) -> Result<(), Error> { tokio::fs::write("/etc/nginx/sites-available/default", { @@ -266,11 +241,6 @@ impl RpcContext { include_str!("../nginx/main-ui.conf.template"), lan_hostname = info.lan_address.host_str().unwrap(), tor_hostname = info.tor_address.host_str().unwrap(), - eos_marketplace = info.eos_marketplace, - package_marketplace = info - .package_marketplace - .as_ref() - .unwrap_or(&info.eos_marketplace), ) }) .await diff --git a/backend/src/context/setup.rs b/backend/src/context/setup.rs index 25231e114..02f67e19d 100644 --- a/backend/src/context/setup.rs +++ b/backend/src/context/setup.rs @@ -18,7 +18,7 @@ use tracing::instrument; use url::Host; use crate::db::model::Database; -use crate::hostname::{derive_hostname, derive_id, get_hostname, get_id, get_product_key}; +use crate::hostname::{derive_hostname, derive_id, get_product_key}; use crate::net::tor::os_key; use crate::setup::{password_hash, RecoveryStatus}; use crate::util::io::from_toml_async_reader; diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index b21fc974e..d8bd4709b 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -50,12 +50,11 @@ impl Database { tor_address: format!("http://{}", tor_key.public().get_onion_address()) .parse() .unwrap(), - status: ServerStatus::Running {}, - #[cfg(not(feature = "beta"))] - eos_marketplace: "https://marketplace.start9.com".parse().unwrap(), - #[cfg(feature = "beta")] - eos_marketplace: "https://beta-registry-0-3.start9labs.com".parse().unwrap(), - package_marketplace: None, + status_info: ServerStatus { + backing_up: false, + updated: false, + update_progress: None, + }, wifi: WifiInfo { ssids: Vec::new(), connected: None, @@ -67,7 +66,6 @@ impl Database { clearnet: Vec::new(), }, share_stats: false, - update_progress: None, password_hash, }, package_data: AllPackageData::default(), @@ -93,25 +91,23 @@ pub struct ServerInfo { pub eos_version_compat: VersionRange, pub lan_address: Url, pub tor_address: Url, - pub status: ServerStatus, - pub eos_marketplace: Url, - pub package_marketplace: Option, // None implies use eos_marketplace + #[model] + #[serde(default)] + pub status_info: ServerStatus, pub wifi: WifiInfo, pub unread_notification_count: u64, pub connection_addresses: ConnectionAddresses, pub share_stats: bool, - #[model] - pub update_progress: Option, pub password_hash: String, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] -pub enum ServerStatus { - Running, - Updating, - Updated, - BackingUp, +pub struct ServerStatus { + pub backing_up: bool, + pub updated: bool, + #[model] + pub update_progress: Option, } #[derive(Debug, Deserialize, Serialize, HasModel)] @@ -257,6 +253,9 @@ impl PackageDataEntryModel { pub struct InstalledPackageDataEntry { #[model] pub status: Status, + pub marketplace_url: Option, + #[serde(default)] + pub developer_key: ed25519_dalek::PublicKey, #[model] pub manifest: Manifest, pub last_backup: Option>, diff --git a/backend/src/init.rs b/backend/src/init.rs index 9b328d2ec..bfc97ed2b 100644 --- a/backend/src/init.rs +++ b/backend/src/init.rs @@ -93,15 +93,11 @@ pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error .server_info() .get_mut(&mut handle) .await?; - match info.status { - ServerStatus::Running | ServerStatus::Updated | ServerStatus::BackingUp => { - info.status = ServerStatus::Running; - } - ServerStatus::Updating => { - info.update_progress = None; - info.status = ServerStatus::Running; - } - } + info.status_info = ServerStatus { + backing_up: false, + updated: false, + update_progress: None, + }; info.save(&mut handle).await?; crate::version::init(&mut handle).await?; diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index 291661981..b12ec21fe 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -15,6 +15,7 @@ use http::header::CONTENT_LENGTH; use http::{Request, Response, StatusCode}; use hyper::Body; use patch_db::{DbHandle, LockType}; +use reqwest::Url; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{command, Context}; use tokio::fs::{File, OpenOptions}; @@ -85,18 +86,23 @@ pub async fn list(#[context] ctx: RpcContext) -> Result, + #[arg(short = "m", long = "marketplace-url", rename = "marketplace-url")] + marketplace_url: Option, + #[arg(short = "v", long = "version-spec", rename = "version-spec")] version_spec: Option< + String, + >, ) -> Result, Error> { let version_str = match &version_spec { None => "*", Some(v) => &*v, }; let version: VersionRange = version_str.parse()?; - let reg_url = ctx.package_registry_url().await?; + let marketplace_url = + marketplace_url.unwrap_or_else(|| crate::DEFAULT_MARKETPLACE.parse().unwrap()); let (man_res, s9pk) = tokio::try_join!( reqwest::get(format!( "{}/package/manifest/{}?spec={}&eos-version-compat={}&arch={}", - reg_url, + marketplace_url, id, version, Current::new().compat(), @@ -104,7 +110,7 @@ pub async fn install( )), reqwest::get(format!( "{}/package/{}.s9pk?spec={}&eos-version-compat={}&arch={}", - reg_url, + marketplace_url, id, version, Current::new().compat(), @@ -112,7 +118,15 @@ pub async fn install( )) ) .with_kind(crate::ErrorKind::Registry)?; - let man: Manifest = man_res.json().await.with_kind(crate::ErrorKind::Registry)?; + let man: Manifest = man_res + .error_for_status() + .with_kind(crate::ErrorKind::Registry)? + .json() + .await + .with_kind(crate::ErrorKind::Registry)?; + let s9pk = s9pk + .error_for_status() + .with_kind(crate::ErrorKind::Registry)?; if man.id.as_str() != id || !man.version.satisfies(&version) { return Err(Error::new( @@ -166,6 +180,7 @@ pub async fn install( if let Err(e) = download_install_s9pk( &ctx, &man, + Some(marketplace_url), InstallProgress::new(s9pk.content_length()), tokio_util::io::StreamReader::new(s9pk.bytes_stream().map_err(|e| { std::io::Error::new( @@ -286,6 +301,7 @@ pub async fn sideload( download_install_s9pk( &new_ctx, &manifest, + None, progress, tokio_util::io::StreamReader::new(req.into_body().map_err(|e| { std::io::Error::new( @@ -330,6 +346,7 @@ pub async fn sideload( async fn cli_install( ctx: CliContext, target: String, + marketplace_url: Option, version_spec: Option, ) -> Result<(), RpcError> { if target.ends_with(".s9pk") { @@ -373,7 +390,9 @@ async fn cli_install( } } else { let params = match (target.split_once("@"), version_spec) { - (Some((pkg, v)), None) => serde_json::json!({ "id": pkg, "version-spec": v }), + (Some((pkg, v)), None) => { + serde_json::json!({ "id": pkg, "marketplace-url": marketplace_url, "version-spec": v }) + } (Some(_), Some(_)) => { return Err(crate::Error::new( eyre!("Invalid package id {}", target), @@ -381,8 +400,10 @@ async fn cli_install( ) .into()) } - (None, Some(v)) => serde_json::json!({ "id": target, "version-spec": v }), - (None, None) => serde_json::json!({ "id": target }), + (None, Some(v)) => { + serde_json::json!({ "id": target, "marketplace-url": marketplace_url, "version-spec": v }) + } + (None, None) => serde_json::json!({ "id": target, "marketplace-url": marketplace_url }), }; tracing::debug!("calling package.install"); rpc_toolkit::command_helpers::call_remote( @@ -489,6 +510,7 @@ pub async fn uninstall_impl(ctx: RpcContext, id: PackageId) -> Result, progress: Arc, mut s9pk: impl AsyncRead + Unpin, ) -> Result<(), Error> { @@ -537,7 +559,15 @@ pub async fn download_install_s9pk( }) .await?; - install_s9pk(&ctx, pkg_id, version, &mut s9pk_reader, progress).await?; + install_s9pk( + &ctx, + pkg_id, + version, + marketplace_url, + &mut s9pk_reader, + progress, + ) + .await?; Ok(()) } @@ -563,11 +593,13 @@ pub async fn install_s9pk( ctx: &RpcContext, pkg_id: &PackageId, version: &Version, + marketplace_url: Option, rdr: &mut S9pkReader>, progress: Arc, ) -> Result<(), Error> { rdr.validate().await?; rdr.validated(); + let developer_key = rdr.developer_key().clone(); rdr.reset().await?; let model = crate::db::DatabaseModel::new() .package_data() @@ -582,78 +614,97 @@ pub async fn install_s9pk( tracing::info!("Install {}@{}: Fetching Dependency Info", pkg_id, version); let mut dependency_info = BTreeMap::new(); - let reg_url = ctx.package_registry_url().await?; for (dep, info) in &manifest.dependencies.0 { - let manifest: Option = match reqwest::get(format!( - "{}/package/manifest/{}?spec={}&eos-version-compat={}&arch={}", - reg_url, - dep, - info.version, - Current::new().compat(), - platforms::TARGET_ARCH, - )) - .await - .with_kind(crate::ErrorKind::Registry)? - .error_for_status() + let manifest: Option = if let Some(local_man) = crate::db::DatabaseModel::new() + .package_data() + .idx_model(dep) + .map::<_, Manifest>(|pde| pde.manifest()) + .get(&mut ctx.db.handle(), false) + .await? + .into_owned() { - Ok(a) => Ok(Some( - a.json() + Some(local_man) + } else if let Some(marketplace_url) = &marketplace_url { + match reqwest::get(format!( + "{}/package/manifest/{}?spec={}&eos-version-compat={}&arch={}", + marketplace_url, + dep, + info.version, + Current::new().compat(), + platforms::TARGET_ARCH, + )) + .await + .with_kind(crate::ErrorKind::Registry)? + .error_for_status() + { + Ok(a) => Ok(Some( + a.json() + .await + .with_kind(crate::ErrorKind::Deserialization)?, + )), + Err(e) if e.status() == Some(StatusCode::BAD_REQUEST) => Ok(None), + Err(e) => Err(e), + } + .with_kind(crate::ErrorKind::Registry)? + } else { + None + }; + + if let Some(marketplace_url) = &marketplace_url { + if let Some(manifest) = &manifest { + let dir = ctx + .datadir + .join(PKG_PUBLIC_DIR) + .join(&manifest.id) + .join(manifest.version.as_str()); + let icon_path = dir.join(format!("icon.{}", manifest.assets.icon_type())); + if tokio::fs::metadata(&icon_path).await.is_err() { + tokio::fs::create_dir_all(&dir).await?; + let icon = reqwest::get(format!( + "{}/package/icon/{}?spec={}&eos-version-compat={}&arch={}", + marketplace_url, + dep, + info.version, + Current::new().compat(), + platforms::TARGET_ARCH, + )) .await - .with_kind(crate::ErrorKind::Deserialization)?, - )), - Err(e) if e.status() == Some(StatusCode::BAD_REQUEST) => Ok(None), - Err(e) => Err(e), - } - .with_kind(crate::ErrorKind::Registry)?; - if let Some(manifest) = manifest { - let dir = ctx - .datadir - .join(PKG_PUBLIC_DIR) - .join(&manifest.id) - .join(manifest.version.as_str()); - let icon_path = dir.join(format!("icon.{}", manifest.assets.icon_type())); - if tokio::fs::metadata(&icon_path).await.is_err() { - tokio::fs::create_dir_all(&dir).await?; - let icon = reqwest::get(format!( - "{}/package/icon/{}?spec={}&eos-version-compat={}&arch={}", - reg_url, - dep, - info.version, - Current::new().compat(), - platforms::TARGET_ARCH, - )) - .await - .with_kind(crate::ErrorKind::Registry)?; - let mut dst = File::create(&icon_path).await?; - tokio::io::copy( - &mut tokio_util::io::StreamReader::new(icon.bytes_stream().map_err(|e| { - std::io::Error::new( - if e.is_connect() { - std::io::ErrorKind::ConnectionRefused - } else if e.is_timeout() { - std::io::ErrorKind::TimedOut - } else { - std::io::ErrorKind::Other - }, - e, - ) - })), - &mut dst, - ) - .await?; - dst.sync_all().await?; + .with_kind(crate::ErrorKind::Registry)?; + let mut dst = File::create(&icon_path).await?; + tokio::io::copy( + &mut tokio_util::io::StreamReader::new(icon.bytes_stream().map_err(|e| { + std::io::Error::new( + if e.is_connect() { + std::io::ErrorKind::ConnectionRefused + } else if e.is_timeout() { + std::io::ErrorKind::TimedOut + } else { + std::io::ErrorKind::Other + }, + e, + ) + })), + &mut dst, + ) + .await?; + dst.sync_all().await?; + } } dependency_info.insert( dep.clone(), StaticDependencyInfo { - icon: format!( - "/public/package-data/{}/{}/icon.{}", - manifest.id, - manifest.version, - manifest.assets.icon_type() - ), - manifest: Some(manifest), + icon: if let Some(manifest) = &manifest { + format!( + "/public/package-data/{}/{}/icon.{}", + manifest.id, + manifest.version, + manifest.assets.icon_type() + ) + } else { + "/assets/img/package-icon.png".to_owned() + }, + manifest, }, ); } @@ -842,13 +893,36 @@ pub async fn install_s9pk( }) .collect(); let current_dependents = { - // search required dependencies let mut deps = BTreeMap::new(); for package in crate::db::DatabaseModel::new() .package_data() .keys(&mut tx, true) .await? { + // update dependency_info on dependents + if let Some(dep_info_model) = crate::db::DatabaseModel::new() + .package_data() + .idx_model(&package) + .expect(&mut tx) + .await? + .installed() + .and_then(|i| i.dependency_info().idx_model(pkg_id)) + .check(&mut tx) + .await? + { + let mut dep_info = dep_info_model.get_mut(&mut tx).await?; + *dep_info = StaticDependencyInfo { + icon: format!( + "/public/package-data/{}/{}/icon.{}", + manifest.id, + manifest.version, + manifest.assets.icon_type() + ), + manifest: Some(manifest.clone()), + }; + } + + // search required dependencies if let Some(dep) = crate::db::DatabaseModel::new() .package_data() .idx_model(&package) @@ -877,6 +951,8 @@ pub async fn install_s9pk( main: MainStatus::Stopped, dependency_errors: DependencyErrors::default(), }, + marketplace_url, + developer_key, manifest: manifest.clone(), last_backup: match &*pde { PackageDataEntry::Updating { diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 8732e4ac4..15d850956 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,4 +1,8 @@ -pub const CONFIG_PATH: &'static str = "/etc/embassy/config.toml"; +pub const CONFIG_PATH: &str = "/etc/embassy/config.yaml"; +#[cfg(not(feature = "beta"))] +pub const DEFAULT_MARKETPLACE: &str = "https://marketplace.start9.com"; +#[cfg(feature = "beta")] +pub const DEFAULT_MARKETPLACE: &str = "https://beta-registry-0-3.start9labs.com"; pub const BUFFER_SIZE: usize = 1024; pub const HOST_IP: [u8; 4] = [172, 18, 0, 1]; @@ -78,7 +82,6 @@ pub fn main_api() -> Result<(), RpcError> { shutdown::restart, shutdown::rebuild, update::update_system, - marketplace::set_eos_url, ))] pub fn server() -> Result<(), RpcError> { Ok(()) @@ -98,7 +101,6 @@ pub fn server() -> Result<(), RpcError> { properties::properties, dependencies::dependency, backup::package_backup, - marketplace::set_package_url, ))] pub fn package() -> Result<(), RpcError> { Ok(()) diff --git a/backend/src/marketplace.rs b/backend/src/marketplace.rs index 5ec5374a4..af326992b 100644 --- a/backend/src/marketplace.rs +++ b/backend/src/marketplace.rs @@ -1,43 +1,35 @@ -use patch_db::{DbHandle, LockType}; -use reqwest::Url; +use color_eyre::eyre::eyre; +use reqwest::{StatusCode, Url}; use rpc_toolkit::command; +use serde_json::Value; -use crate::context::RpcContext; -use crate::util::display_none; -use crate::Error; +use crate::{Error, ResultExt}; -#[command(rename = "set-marketplace", display(display_none))] -pub async fn set_eos_url(#[context] ctx: RpcContext, #[arg] url: Url) -> Result<(), Error> { - let mut db = ctx.db.handle(); - let mut tx = db.begin().await?; - crate::db::DatabaseModel::new() - .server_info() - .lock(&mut tx, LockType::Write) - .await?; - crate::db::DatabaseModel::new() - .server_info() - .eos_marketplace() - .put(&mut tx, &url) - .await?; - ctx.set_nginx_conf(&mut tx).await?; - tx.commit(None).await?; +#[command(subcommands(get))] +pub fn marketplace() -> Result<(), Error> { Ok(()) } -#[command(rename = "set-marketplace", display(display_none))] -pub async fn set_package_url(#[context] ctx: RpcContext, #[arg] url: Url) -> Result<(), Error> { - let mut db = ctx.db.handle(); - let mut tx = db.begin().await?; - crate::db::DatabaseModel::new() - .server_info() - .lock(&mut tx, LockType::Write) - .await?; - crate::db::DatabaseModel::new() - .server_info() - .package_marketplace() - .put(&mut tx, &Some(url)) - .await?; - ctx.set_nginx_conf(&mut tx).await?; - tx.commit(None).await?; - Ok(()) +#[command] +pub async fn get(#[arg] url: Url) -> Result { + let response = reqwest::get(url) + .await + .with_kind(crate::ErrorKind::Network)?; + let status = response.status(); + if status.is_success() { + response + .json() + .await + .with_kind(crate::ErrorKind::Deserialization) + } else { + let message = response.text().await.with_kind(crate::ErrorKind::Network)?; + Err(Error::new( + eyre!("{}", message), + match status { + StatusCode::BAD_REQUEST => crate::ErrorKind::InvalidRequest, + StatusCode::NOT_FOUND => crate::ErrorKind::NotFound, + _ => crate::ErrorKind::Registry, + }, + )) + } } diff --git a/backend/src/nginx/main-ui.conf.template b/backend/src/nginx/main-ui.conf.template index 9c1f04c82..a85e6c90e 100644 --- a/backend/src/nginx/main-ui.conf.template +++ b/backend/src/nginx/main-ui.conf.template @@ -45,18 +45,6 @@ server {{ proxy_pass http://127.0.0.1:5961/; }} - location /marketplace/eos/ {{ - # ns1.digitalocean.com, ns2.digitalocean.com, ns3.digitalocean.com - resolver 173.245.58.51 173.245.59.41 198.41.222.173; - proxy_pass {eos_marketplace}eos/; - }} - - location /marketplace/package/ {{ - # ns1.digitalocean.com, ns2.digitalocean.com, ns3.digitalocean.com - resolver 173.245.58.51 173.245.59.41 198.41.222.173; - proxy_pass {package_marketplace}package/; - }} - location / {{ try_files $uri $uri/ =404; }} @@ -111,18 +99,6 @@ server {{ proxy_pass http://127.0.0.1:5961/; }} - location /marketplace/eos/ {{ - # ns1.digitalocean.com, ns2.digitalocean.com, ns3.digitalocean.com - resolver 173.245.58.51 173.245.59.41 198.41.222.173; - proxy_pass {eos_marketplace}eos/; - }} - - location /marketplace/package/ {{ - # ns1.digitalocean.com, ns2.digitalocean.com, ns3.digitalocean.com - resolver 173.245.58.51 173.245.59.41 198.41.222.173; - proxy_pass {package_marketplace}package/; - }} - location / {{ try_files $uri $uri/ =404; }} diff --git a/backend/src/s9pk/reader.rs b/backend/src/s9pk/reader.rs index a7adbe64f..9e2044e81 100644 --- a/backend/src/s9pk/reader.rs +++ b/backend/src/s9pk/reader.rs @@ -4,6 +4,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use digest::Output; +use ed25519_dalek::PublicKey; use sha2::{Digest, Sha512}; use tokio::fs::File; use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, ReadBuf, Take}; @@ -47,6 +48,7 @@ impl<'a, R: AsyncRead + AsyncSeek + Unpin> AsyncRead for ReadHandle<'a, R> { pub struct S9pkReader { hash: Option>, hash_string: Option, + developer_key: PublicKey, toc: TableOfContents, pos: u64, rdr: R, @@ -105,6 +107,7 @@ impl S9pkReader { Ok(S9pkReader { hash_string, hash, + developer_key: header.pubkey, toc: header.table_of_contents, pos, rdr, @@ -119,6 +122,10 @@ impl S9pkReader { self.hash_string.as_ref().map(|s| s.as_str()) } + pub fn developer_key(&self) -> &PublicKey { + &self.developer_key + } + pub async fn reset(&mut self) -> Result<(), Error> { self.rdr.seek(SeekFrom::Start(0)).await?; Ok(()) diff --git a/backend/src/update/mod.rs b/backend/src/update/mod.rs index ec04151bf..923f738a1 100644 --- a/backend/src/update/mod.rs +++ b/backend/src/update/mod.rs @@ -23,7 +23,7 @@ use tokio_stream::StreamExt; use tracing::instrument; use crate::context::RpcContext; -use crate::db::model::{ServerStatus, UpdateProgress}; +use crate::db::model::UpdateProgress; use crate::db::util::WithRevision; use crate::disk::mount::filesystem::block_dev::BlockDev; use crate::disk::mount::filesystem::FileSystem; @@ -48,6 +48,7 @@ lazy_static! { #[instrument(skip(ctx))] pub async fn update_system( #[context] ctx: RpcContext, + #[arg(rename = "marketplace-url")] marketplace_url: Url, ) -> Result, Error> { let noop = WithRevision { response: UpdateResult::NoUpdates, @@ -56,7 +57,7 @@ pub async fn update_system( if UPDATED.load(Ordering::SeqCst) { return Ok(noop); } - match maybe_do_update(ctx).await? { + match maybe_do_update(ctx, marketplace_url).await? { None => Ok(noop), Some(r) => Ok(WithRevision { response: UpdateResult::Updating, @@ -127,11 +128,14 @@ lazy_static! { } #[instrument(skip(ctx))] -async fn maybe_do_update(ctx: RpcContext) -> Result>, Error> { +async fn maybe_do_update( + ctx: RpcContext, + marketplace_url: Url, +) -> Result>, Error> { let mut db = ctx.db.handle(); let latest_version = reqwest::get(format!( "{}/eos/latest?eos-version={}&arch={}", - ctx.eos_registry_url().await?, + marketplace_url, Current::new().semver(), platforms::TARGET_ARCH, )) @@ -154,67 +158,58 @@ async fn maybe_do_update(ctx: RpcContext) -> Result>, Error return Ok(None); } let mut tx = db.begin().await?; - let mut info = crate::db::DatabaseModel::new() + let mut status = crate::db::DatabaseModel::new() .server_info() + .status_info() .get_mut(&mut tx) .await?; - match &info.status { - ServerStatus::Updating => { - return Err(Error::new( - eyre!("Server is already updating!"), - crate::ErrorKind::InvalidRequest, - )) - } - ServerStatus::Updated => { - return Ok(None); - } - ServerStatus::BackingUp => { - return Err(Error::new( - eyre!("Server is backing up!"), - crate::ErrorKind::InvalidRequest, - )) - } - ServerStatus::Running => (), + if status.update_progress.is_some() { + return Err(Error::new( + eyre!("Server is already updating!"), + crate::ErrorKind::InvalidRequest, + )); + } + if status.updated { + return Ok(None); } let (new_label, _current_label) = query_mounted_label().await?; let (size, download) = download_file( ctx.db.handle(), &EosUrl { - base: info.eos_marketplace.clone(), + base: marketplace_url, version: latest_version.clone(), }, new_label, ) .await?; - info.status = ServerStatus::Updating; - info.update_progress = Some(UpdateProgress { + status.update_progress = Some(UpdateProgress { size, downloaded: 0, }); - info.save(&mut tx).await?; + status.save(&mut tx).await?; let rev = tx.commit(None).await?; tokio::spawn(async move { let mut db = ctx.db.handle(); let res = do_update(download, new_label).await; - let mut info = crate::db::DatabaseModel::new() + let mut status = crate::db::DatabaseModel::new() .server_info() + .status_info() .get_mut(&mut db) .await .expect("could not access status"); - info.update_progress = None; + status.update_progress = None; match res { Ok(()) => { - info.status = ServerStatus::Updated; - info.save(&mut db).await.expect("could not save status"); + status.updated = true; + status.save(&mut db).await.expect("could not save status"); BEP.play().await.expect("could not bep"); BEP.play().await.expect("could not bep"); BEP.play().await.expect("could not bep"); } Err(e) => { - info.status = ServerStatus::Running; - info.save(&mut db).await.expect("could not save status"); + status.save(&mut db).await.expect("could not save status"); ctx.notification_manager .notify( &mut db, @@ -365,6 +360,7 @@ async fn write_stream_to_label( last_progress_update = Instant::now(); crate::db::DatabaseModel::new() .server_info() + .status_info() .update_progress() .put(db, &UpdateProgress { size, downloaded }) .await?;