take marketplace as an argument from the frontend

This commit is contained in:
Aiden McClelland
2022-01-27 18:30:01 -07:00
committed by Aiden McClelland
parent 9d3f0a9d2b
commit 5c7c23459d
12 changed files with 259 additions and 255 deletions

View File

@@ -18,7 +18,6 @@ use super::PackageBackupReport;
use crate::auth::check_password_against_db; use crate::auth::check_password_against_db;
use crate::backup::{BackupReport, ServerBackupReport}; use crate::backup::{BackupReport, ServerBackupReport};
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::db::model::ServerStatus;
use crate::db::util::WithRevision; use crate::db::util::WithRevision;
use crate::disk::mount::backup::BackupMountGuard; use crate::disk::mount::backup::BackupMountGuard;
use crate::disk::mount::guard::TmpMountGuard; use crate::disk::mount::guard::TmpMountGuard;
@@ -134,7 +133,10 @@ pub async fn backup_all(
let revision = assure_backing_up(&mut db).await?; let revision = assure_backing_up(&mut db).await?;
tokio::task::spawn(async move { tokio::task::spawn(async move {
let backup_res = perform_backup(&ctx, &mut db, backup_guard).await; 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 status_model
.clone() .clone()
.lock(&mut db, LockType::Write) .lock(&mut db, LockType::Write)
@@ -203,7 +205,7 @@ pub async fn backup_all(
} }
} }
status_model status_model
.put(&mut db, &ServerStatus::Running) .put(&mut db, &false)
.await .await
.expect("failed to change server status"); .expect("failed to change server status");
}); });
@@ -216,33 +218,21 @@ pub async fn backup_all(
#[instrument(skip(db))] #[instrument(skip(db))]
async fn assure_backing_up(db: &mut PatchDbHandle) -> Result<Option<Arc<Revision>>, Error> { async fn assure_backing_up(db: &mut PatchDbHandle) -> Result<Option<Arc<Revision>>, Error> {
let mut tx = db.begin().await?; let mut tx = db.begin().await?;
let mut info = crate::db::DatabaseModel::new() let mut backing_up = crate::db::DatabaseModel::new()
.server_info() .server_info()
.status_info()
.backing_up()
.get_mut(&mut tx) .get_mut(&mut tx)
.await?; .await?;
match &info.status {
ServerStatus::Updating => { if *backing_up {
return Err(Error::new( return Err(Error::new(
eyre!("Server is updating!"), eyre!("Server is already backing up!"),
crate::ErrorKind::InvalidRequest, 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 => (),
} }
info.status = ServerStatus::BackingUp; *backing_up = true;
info.save(&mut tx).await?; backing_up.save(&mut tx).await?;
Ok(tx.commit(None).await?) Ok(tx.commit(None).await?)
} }

View File

@@ -377,7 +377,7 @@ async fn restore_package<'a>(
Ok(( Ok((
progress.clone(), progress.clone(),
async move { async move {
download_install_s9pk(&ctx, &manifest, progress, file).await?; download_install_s9pk(&ctx, &manifest, None, progress, file).await?;
guard.unmount().await?; guard.unmount().await?;

View File

@@ -23,7 +23,7 @@ use tracing::instrument;
use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::core::rpc_continuations::{RequestGuid, RpcContinuation};
use crate::db::model::{Database, InstalledPackageDataEntry, PackageDataEntry}; 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::install::cleanup::{cleanup_failed, uninstall};
use crate::manager::ManagerMap; use crate::manager::ManagerMap;
use crate::middleware::auth::HashSessionToken; use crate::middleware::auth::HashSessionToken;
@@ -230,31 +230,6 @@ impl RpcContext {
tracing::info!("Initialized Package Managers"); tracing::info!("Initialized Package Managers");
Ok(res) Ok(res)
} }
#[instrument(skip(self))]
pub async fn package_registry_url(&self) -> Result<Url, Error> {
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<Url, Error> {
Ok(crate::db::DatabaseModel::new()
.server_info()
.eos_marketplace()
.get(&mut self.db.handle(), false)
.await?
.to_owned())
}
#[instrument(skip(self, db))] #[instrument(skip(self, db))]
pub async fn set_nginx_conf<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> { pub async fn set_nginx_conf<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> {
tokio::fs::write("/etc/nginx/sites-available/default", { tokio::fs::write("/etc/nginx/sites-available/default", {
@@ -266,11 +241,6 @@ impl RpcContext {
include_str!("../nginx/main-ui.conf.template"), include_str!("../nginx/main-ui.conf.template"),
lan_hostname = info.lan_address.host_str().unwrap(), lan_hostname = info.lan_address.host_str().unwrap(),
tor_hostname = info.tor_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 .await

View File

@@ -18,7 +18,7 @@ use tracing::instrument;
use url::Host; use url::Host;
use crate::db::model::Database; 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::net::tor::os_key;
use crate::setup::{password_hash, RecoveryStatus}; use crate::setup::{password_hash, RecoveryStatus};
use crate::util::io::from_toml_async_reader; use crate::util::io::from_toml_async_reader;

View File

@@ -50,12 +50,11 @@ impl Database {
tor_address: format!("http://{}", tor_key.public().get_onion_address()) tor_address: format!("http://{}", tor_key.public().get_onion_address())
.parse() .parse()
.unwrap(), .unwrap(),
status: ServerStatus::Running {}, status_info: ServerStatus {
#[cfg(not(feature = "beta"))] backing_up: false,
eos_marketplace: "https://marketplace.start9.com".parse().unwrap(), updated: false,
#[cfg(feature = "beta")] update_progress: None,
eos_marketplace: "https://beta-registry-0-3.start9labs.com".parse().unwrap(), },
package_marketplace: None,
wifi: WifiInfo { wifi: WifiInfo {
ssids: Vec::new(), ssids: Vec::new(),
connected: None, connected: None,
@@ -67,7 +66,6 @@ impl Database {
clearnet: Vec::new(), clearnet: Vec::new(),
}, },
share_stats: false, share_stats: false,
update_progress: None,
password_hash, password_hash,
}, },
package_data: AllPackageData::default(), package_data: AllPackageData::default(),
@@ -93,25 +91,23 @@ pub struct ServerInfo {
pub eos_version_compat: VersionRange, pub eos_version_compat: VersionRange,
pub lan_address: Url, pub lan_address: Url,
pub tor_address: Url, pub tor_address: Url,
pub status: ServerStatus, #[model]
pub eos_marketplace: Url, #[serde(default)]
pub package_marketplace: Option<Url>, // None implies use eos_marketplace pub status_info: ServerStatus,
pub wifi: WifiInfo, pub wifi: WifiInfo,
pub unread_notification_count: u64, pub unread_notification_count: u64,
pub connection_addresses: ConnectionAddresses, pub connection_addresses: ConnectionAddresses,
pub share_stats: bool, pub share_stats: bool,
#[model]
pub update_progress: Option<UpdateProgress>,
pub password_hash: String, pub password_hash: String,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Default, Deserialize, Serialize, HasModel)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum ServerStatus { pub struct ServerStatus {
Running, pub backing_up: bool,
Updating, pub updated: bool,
Updated, #[model]
BackingUp, pub update_progress: Option<UpdateProgress>,
} }
#[derive(Debug, Deserialize, Serialize, HasModel)] #[derive(Debug, Deserialize, Serialize, HasModel)]
@@ -257,6 +253,9 @@ impl PackageDataEntryModel {
pub struct InstalledPackageDataEntry { pub struct InstalledPackageDataEntry {
#[model] #[model]
pub status: Status, pub status: Status,
pub marketplace_url: Option<Url>,
#[serde(default)]
pub developer_key: ed25519_dalek::PublicKey,
#[model] #[model]
pub manifest: Manifest, pub manifest: Manifest,
pub last_backup: Option<DateTime<Utc>>, pub last_backup: Option<DateTime<Utc>>,

View File

@@ -93,15 +93,11 @@ pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error
.server_info() .server_info()
.get_mut(&mut handle) .get_mut(&mut handle)
.await?; .await?;
match info.status { info.status_info = ServerStatus {
ServerStatus::Running | ServerStatus::Updated | ServerStatus::BackingUp => { backing_up: false,
info.status = ServerStatus::Running; updated: false,
} update_progress: None,
ServerStatus::Updating => { };
info.update_progress = None;
info.status = ServerStatus::Running;
}
}
info.save(&mut handle).await?; info.save(&mut handle).await?;
crate::version::init(&mut handle).await?; crate::version::init(&mut handle).await?;

View File

@@ -15,6 +15,7 @@ use http::header::CONTENT_LENGTH;
use http::{Request, Response, StatusCode}; use http::{Request, Response, StatusCode};
use hyper::Body; use hyper::Body;
use patch_db::{DbHandle, LockType}; use patch_db::{DbHandle, LockType};
use reqwest::Url;
use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::yajrc::RpcError;
use rpc_toolkit::{command, Context}; use rpc_toolkit::{command, Context};
use tokio::fs::{File, OpenOptions}; use tokio::fs::{File, OpenOptions};
@@ -85,18 +86,23 @@ pub async fn list(#[context] ctx: RpcContext) -> Result<Vec<(PackageId, Version)
pub async fn install( pub async fn install(
#[context] ctx: RpcContext, #[context] ctx: RpcContext,
#[arg] id: String, #[arg] id: String,
#[arg(rename = "version-spec")] version_spec: Option<String>, #[arg(short = "m", long = "marketplace-url", rename = "marketplace-url")]
marketplace_url: Option<Url>,
#[arg(short = "v", long = "version-spec", rename = "version-spec")] version_spec: Option<
String,
>,
) -> Result<WithRevision<()>, Error> { ) -> Result<WithRevision<()>, Error> {
let version_str = match &version_spec { let version_str = match &version_spec {
None => "*", None => "*",
Some(v) => &*v, Some(v) => &*v,
}; };
let version: VersionRange = version_str.parse()?; 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!( let (man_res, s9pk) = tokio::try_join!(
reqwest::get(format!( reqwest::get(format!(
"{}/package/manifest/{}?spec={}&eos-version-compat={}&arch={}", "{}/package/manifest/{}?spec={}&eos-version-compat={}&arch={}",
reg_url, marketplace_url,
id, id,
version, version,
Current::new().compat(), Current::new().compat(),
@@ -104,7 +110,7 @@ pub async fn install(
)), )),
reqwest::get(format!( reqwest::get(format!(
"{}/package/{}.s9pk?spec={}&eos-version-compat={}&arch={}", "{}/package/{}.s9pk?spec={}&eos-version-compat={}&arch={}",
reg_url, marketplace_url,
id, id,
version, version,
Current::new().compat(), Current::new().compat(),
@@ -112,7 +118,15 @@ pub async fn install(
)) ))
) )
.with_kind(crate::ErrorKind::Registry)?; .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) { if man.id.as_str() != id || !man.version.satisfies(&version) {
return Err(Error::new( return Err(Error::new(
@@ -166,6 +180,7 @@ pub async fn install(
if let Err(e) = download_install_s9pk( if let Err(e) = download_install_s9pk(
&ctx, &ctx,
&man, &man,
Some(marketplace_url),
InstallProgress::new(s9pk.content_length()), InstallProgress::new(s9pk.content_length()),
tokio_util::io::StreamReader::new(s9pk.bytes_stream().map_err(|e| { tokio_util::io::StreamReader::new(s9pk.bytes_stream().map_err(|e| {
std::io::Error::new( std::io::Error::new(
@@ -286,6 +301,7 @@ pub async fn sideload(
download_install_s9pk( download_install_s9pk(
&new_ctx, &new_ctx,
&manifest, &manifest,
None,
progress, progress,
tokio_util::io::StreamReader::new(req.into_body().map_err(|e| { tokio_util::io::StreamReader::new(req.into_body().map_err(|e| {
std::io::Error::new( std::io::Error::new(
@@ -330,6 +346,7 @@ pub async fn sideload(
async fn cli_install( async fn cli_install(
ctx: CliContext, ctx: CliContext,
target: String, target: String,
marketplace_url: Option<Url>,
version_spec: Option<String>, version_spec: Option<String>,
) -> Result<(), RpcError> { ) -> Result<(), RpcError> {
if target.ends_with(".s9pk") { if target.ends_with(".s9pk") {
@@ -373,7 +390,9 @@ async fn cli_install(
} }
} else { } else {
let params = match (target.split_once("@"), version_spec) { 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(_)) => { (Some(_), Some(_)) => {
return Err(crate::Error::new( return Err(crate::Error::new(
eyre!("Invalid package id {}", target), eyre!("Invalid package id {}", target),
@@ -381,8 +400,10 @@ async fn cli_install(
) )
.into()) .into())
} }
(None, Some(v)) => serde_json::json!({ "id": target, "version-spec": v }), (None, Some(v)) => {
(None, None) => serde_json::json!({ "id": target }), 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"); tracing::debug!("calling package.install");
rpc_toolkit::command_helpers::call_remote( rpc_toolkit::command_helpers::call_remote(
@@ -489,6 +510,7 @@ pub async fn uninstall_impl(ctx: RpcContext, id: PackageId) -> Result<WithRevisi
pub async fn download_install_s9pk( pub async fn download_install_s9pk(
ctx: &RpcContext, ctx: &RpcContext,
temp_manifest: &Manifest, temp_manifest: &Manifest,
marketplace_url: Option<Url>,
progress: Arc<InstallProgress>, progress: Arc<InstallProgress>,
mut s9pk: impl AsyncRead + Unpin, mut s9pk: impl AsyncRead + Unpin,
) -> Result<(), Error> { ) -> Result<(), Error> {
@@ -537,7 +559,15 @@ pub async fn download_install_s9pk(
}) })
.await?; .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(()) Ok(())
} }
@@ -563,11 +593,13 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
ctx: &RpcContext, ctx: &RpcContext,
pkg_id: &PackageId, pkg_id: &PackageId,
version: &Version, version: &Version,
marketplace_url: Option<Url>,
rdr: &mut S9pkReader<InstallProgressTracker<R>>, rdr: &mut S9pkReader<InstallProgressTracker<R>>,
progress: Arc<InstallProgress>, progress: Arc<InstallProgress>,
) -> Result<(), Error> { ) -> Result<(), Error> {
rdr.validate().await?; rdr.validate().await?;
rdr.validated(); rdr.validated();
let developer_key = rdr.developer_key().clone();
rdr.reset().await?; rdr.reset().await?;
let model = crate::db::DatabaseModel::new() let model = crate::db::DatabaseModel::new()
.package_data() .package_data()
@@ -582,78 +614,97 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
tracing::info!("Install {}@{}: Fetching Dependency Info", pkg_id, version); tracing::info!("Install {}@{}: Fetching Dependency Info", pkg_id, version);
let mut dependency_info = BTreeMap::new(); let mut dependency_info = BTreeMap::new();
let reg_url = ctx.package_registry_url().await?;
for (dep, info) in &manifest.dependencies.0 { for (dep, info) in &manifest.dependencies.0 {
let manifest: Option<Manifest> = match reqwest::get(format!( let manifest: Option<Manifest> = if let Some(local_man) = crate::db::DatabaseModel::new()
"{}/package/manifest/{}?spec={}&eos-version-compat={}&arch={}", .package_data()
reg_url, .idx_model(dep)
dep, .map::<_, Manifest>(|pde| pde.manifest())
info.version, .get(&mut ctx.db.handle(), false)
Current::new().compat(), .await?
platforms::TARGET_ARCH, .into_owned()
))
.await
.with_kind(crate::ErrorKind::Registry)?
.error_for_status()
{ {
Ok(a) => Ok(Some( Some(local_man)
a.json() } 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 .await
.with_kind(crate::ErrorKind::Deserialization)?, .with_kind(crate::ErrorKind::Registry)?;
)), let mut dst = File::create(&icon_path).await?;
Err(e) if e.status() == Some(StatusCode::BAD_REQUEST) => Ok(None), tokio::io::copy(
Err(e) => Err(e), &mut tokio_util::io::StreamReader::new(icon.bytes_stream().map_err(|e| {
} std::io::Error::new(
.with_kind(crate::ErrorKind::Registry)?; if e.is_connect() {
if let Some(manifest) = manifest { std::io::ErrorKind::ConnectionRefused
let dir = ctx } else if e.is_timeout() {
.datadir std::io::ErrorKind::TimedOut
.join(PKG_PUBLIC_DIR) } else {
.join(&manifest.id) std::io::ErrorKind::Other
.join(manifest.version.as_str()); },
let icon_path = dir.join(format!("icon.{}", manifest.assets.icon_type())); e,
if tokio::fs::metadata(&icon_path).await.is_err() { )
tokio::fs::create_dir_all(&dir).await?; })),
let icon = reqwest::get(format!( &mut dst,
"{}/package/icon/{}?spec={}&eos-version-compat={}&arch={}", )
reg_url, .await?;
dep, dst.sync_all().await?;
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?;
} }
dependency_info.insert( dependency_info.insert(
dep.clone(), dep.clone(),
StaticDependencyInfo { StaticDependencyInfo {
icon: format!( icon: if let Some(manifest) = &manifest {
"/public/package-data/{}/{}/icon.{}", format!(
manifest.id, "/public/package-data/{}/{}/icon.{}",
manifest.version, manifest.id,
manifest.assets.icon_type() manifest.version,
), manifest.assets.icon_type()
manifest: Some(manifest), )
} else {
"/assets/img/package-icon.png".to_owned()
},
manifest,
}, },
); );
} }
@@ -842,13 +893,36 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
}) })
.collect(); .collect();
let current_dependents = { let current_dependents = {
// search required dependencies
let mut deps = BTreeMap::new(); let mut deps = BTreeMap::new();
for package in crate::db::DatabaseModel::new() for package in crate::db::DatabaseModel::new()
.package_data() .package_data()
.keys(&mut tx, true) .keys(&mut tx, true)
.await? .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() if let Some(dep) = crate::db::DatabaseModel::new()
.package_data() .package_data()
.idx_model(&package) .idx_model(&package)
@@ -877,6 +951,8 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
main: MainStatus::Stopped, main: MainStatus::Stopped,
dependency_errors: DependencyErrors::default(), dependency_errors: DependencyErrors::default(),
}, },
marketplace_url,
developer_key,
manifest: manifest.clone(), manifest: manifest.clone(),
last_backup: match &*pde { last_backup: match &*pde {
PackageDataEntry::Updating { PackageDataEntry::Updating {

View File

@@ -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 BUFFER_SIZE: usize = 1024;
pub const HOST_IP: [u8; 4] = [172, 18, 0, 1]; pub const HOST_IP: [u8; 4] = [172, 18, 0, 1];
@@ -78,7 +82,6 @@ pub fn main_api() -> Result<(), RpcError> {
shutdown::restart, shutdown::restart,
shutdown::rebuild, shutdown::rebuild,
update::update_system, update::update_system,
marketplace::set_eos_url,
))] ))]
pub fn server() -> Result<(), RpcError> { pub fn server() -> Result<(), RpcError> {
Ok(()) Ok(())
@@ -98,7 +101,6 @@ pub fn server() -> Result<(), RpcError> {
properties::properties, properties::properties,
dependencies::dependency, dependencies::dependency,
backup::package_backup, backup::package_backup,
marketplace::set_package_url,
))] ))]
pub fn package() -> Result<(), RpcError> { pub fn package() -> Result<(), RpcError> {
Ok(()) Ok(())

View File

@@ -1,43 +1,35 @@
use patch_db::{DbHandle, LockType}; use color_eyre::eyre::eyre;
use reqwest::Url; use reqwest::{StatusCode, Url};
use rpc_toolkit::command; use rpc_toolkit::command;
use serde_json::Value;
use crate::context::RpcContext; use crate::{Error, ResultExt};
use crate::util::display_none;
use crate::Error;
#[command(rename = "set-marketplace", display(display_none))] #[command(subcommands(get))]
pub async fn set_eos_url(#[context] ctx: RpcContext, #[arg] url: Url) -> Result<(), Error> { pub fn marketplace() -> 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?;
Ok(()) Ok(())
} }
#[command(rename = "set-marketplace", display(display_none))] #[command]
pub async fn set_package_url(#[context] ctx: RpcContext, #[arg] url: Url) -> Result<(), Error> { pub async fn get(#[arg] url: Url) -> Result<Value, Error> {
let mut db = ctx.db.handle(); let response = reqwest::get(url)
let mut tx = db.begin().await?; .await
crate::db::DatabaseModel::new() .with_kind(crate::ErrorKind::Network)?;
.server_info() let status = response.status();
.lock(&mut tx, LockType::Write) if status.is_success() {
.await?; response
crate::db::DatabaseModel::new() .json()
.server_info() .await
.package_marketplace() .with_kind(crate::ErrorKind::Deserialization)
.put(&mut tx, &Some(url)) } else {
.await?; let message = response.text().await.with_kind(crate::ErrorKind::Network)?;
ctx.set_nginx_conf(&mut tx).await?; Err(Error::new(
tx.commit(None).await?; eyre!("{}", message),
Ok(()) match status {
StatusCode::BAD_REQUEST => crate::ErrorKind::InvalidRequest,
StatusCode::NOT_FOUND => crate::ErrorKind::NotFound,
_ => crate::ErrorKind::Registry,
},
))
}
} }

View File

@@ -45,18 +45,6 @@ server {{
proxy_pass http://127.0.0.1:5961/; 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 / {{ location / {{
try_files $uri $uri/ =404; try_files $uri $uri/ =404;
}} }}
@@ -111,18 +99,6 @@ server {{
proxy_pass http://127.0.0.1:5961/; 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 / {{ location / {{
try_files $uri $uri/ =404; try_files $uri $uri/ =404;
}} }}

View File

@@ -4,6 +4,7 @@ use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use digest::Output; use digest::Output;
use ed25519_dalek::PublicKey;
use sha2::{Digest, Sha512}; use sha2::{Digest, Sha512};
use tokio::fs::File; use tokio::fs::File;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, ReadBuf, Take}; 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<R: AsyncRead + AsyncSeek + Unpin = File> { pub struct S9pkReader<R: AsyncRead + AsyncSeek + Unpin = File> {
hash: Option<Output<Sha512>>, hash: Option<Output<Sha512>>,
hash_string: Option<String>, hash_string: Option<String>,
developer_key: PublicKey,
toc: TableOfContents, toc: TableOfContents,
pos: u64, pos: u64,
rdr: R, rdr: R,
@@ -105,6 +107,7 @@ impl<R: AsyncRead + AsyncSeek + Unpin> S9pkReader<R> {
Ok(S9pkReader { Ok(S9pkReader {
hash_string, hash_string,
hash, hash,
developer_key: header.pubkey,
toc: header.table_of_contents, toc: header.table_of_contents,
pos, pos,
rdr, rdr,
@@ -119,6 +122,10 @@ impl<R: AsyncRead + AsyncSeek + Unpin> S9pkReader<R> {
self.hash_string.as_ref().map(|s| s.as_str()) 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> { pub async fn reset(&mut self) -> Result<(), Error> {
self.rdr.seek(SeekFrom::Start(0)).await?; self.rdr.seek(SeekFrom::Start(0)).await?;
Ok(()) Ok(())

View File

@@ -23,7 +23,7 @@ use tokio_stream::StreamExt;
use tracing::instrument; use tracing::instrument;
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::db::model::{ServerStatus, UpdateProgress}; use crate::db::model::UpdateProgress;
use crate::db::util::WithRevision; use crate::db::util::WithRevision;
use crate::disk::mount::filesystem::block_dev::BlockDev; use crate::disk::mount::filesystem::block_dev::BlockDev;
use crate::disk::mount::filesystem::FileSystem; use crate::disk::mount::filesystem::FileSystem;
@@ -48,6 +48,7 @@ lazy_static! {
#[instrument(skip(ctx))] #[instrument(skip(ctx))]
pub async fn update_system( pub async fn update_system(
#[context] ctx: RpcContext, #[context] ctx: RpcContext,
#[arg(rename = "marketplace-url")] marketplace_url: Url,
) -> Result<WithRevision<UpdateResult>, Error> { ) -> Result<WithRevision<UpdateResult>, Error> {
let noop = WithRevision { let noop = WithRevision {
response: UpdateResult::NoUpdates, response: UpdateResult::NoUpdates,
@@ -56,7 +57,7 @@ pub async fn update_system(
if UPDATED.load(Ordering::SeqCst) { if UPDATED.load(Ordering::SeqCst) {
return Ok(noop); return Ok(noop);
} }
match maybe_do_update(ctx).await? { match maybe_do_update(ctx, marketplace_url).await? {
None => Ok(noop), None => Ok(noop),
Some(r) => Ok(WithRevision { Some(r) => Ok(WithRevision {
response: UpdateResult::Updating, response: UpdateResult::Updating,
@@ -127,11 +128,14 @@ lazy_static! {
} }
#[instrument(skip(ctx))] #[instrument(skip(ctx))]
async fn maybe_do_update(ctx: RpcContext) -> Result<Option<Arc<Revision>>, Error> { async fn maybe_do_update(
ctx: RpcContext,
marketplace_url: Url,
) -> Result<Option<Arc<Revision>>, Error> {
let mut db = ctx.db.handle(); let mut db = ctx.db.handle();
let latest_version = reqwest::get(format!( let latest_version = reqwest::get(format!(
"{}/eos/latest?eos-version={}&arch={}", "{}/eos/latest?eos-version={}&arch={}",
ctx.eos_registry_url().await?, marketplace_url,
Current::new().semver(), Current::new().semver(),
platforms::TARGET_ARCH, platforms::TARGET_ARCH,
)) ))
@@ -154,67 +158,58 @@ async fn maybe_do_update(ctx: RpcContext) -> Result<Option<Arc<Revision>>, Error
return Ok(None); return Ok(None);
} }
let mut tx = db.begin().await?; let mut tx = db.begin().await?;
let mut info = crate::db::DatabaseModel::new() let mut status = crate::db::DatabaseModel::new()
.server_info() .server_info()
.status_info()
.get_mut(&mut tx) .get_mut(&mut tx)
.await?; .await?;
match &info.status { if status.update_progress.is_some() {
ServerStatus::Updating => { return Err(Error::new(
return Err(Error::new( eyre!("Server is already updating!"),
eyre!("Server is already updating!"), crate::ErrorKind::InvalidRequest,
crate::ErrorKind::InvalidRequest, ));
)) }
} if status.updated {
ServerStatus::Updated => { return Ok(None);
return Ok(None);
}
ServerStatus::BackingUp => {
return Err(Error::new(
eyre!("Server is backing up!"),
crate::ErrorKind::InvalidRequest,
))
}
ServerStatus::Running => (),
} }
let (new_label, _current_label) = query_mounted_label().await?; let (new_label, _current_label) = query_mounted_label().await?;
let (size, download) = download_file( let (size, download) = download_file(
ctx.db.handle(), ctx.db.handle(),
&EosUrl { &EosUrl {
base: info.eos_marketplace.clone(), base: marketplace_url,
version: latest_version.clone(), version: latest_version.clone(),
}, },
new_label, new_label,
) )
.await?; .await?;
info.status = ServerStatus::Updating; status.update_progress = Some(UpdateProgress {
info.update_progress = Some(UpdateProgress {
size, size,
downloaded: 0, downloaded: 0,
}); });
info.save(&mut tx).await?; status.save(&mut tx).await?;
let rev = tx.commit(None).await?; let rev = tx.commit(None).await?;
tokio::spawn(async move { tokio::spawn(async move {
let mut db = ctx.db.handle(); let mut db = ctx.db.handle();
let res = do_update(download, new_label).await; let res = do_update(download, new_label).await;
let mut info = crate::db::DatabaseModel::new() let mut status = crate::db::DatabaseModel::new()
.server_info() .server_info()
.status_info()
.get_mut(&mut db) .get_mut(&mut db)
.await .await
.expect("could not access status"); .expect("could not access status");
info.update_progress = None; status.update_progress = None;
match res { match res {
Ok(()) => { Ok(()) => {
info.status = ServerStatus::Updated; status.updated = true;
info.save(&mut db).await.expect("could not save status"); 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"); BEP.play().await.expect("could not bep");
BEP.play().await.expect("could not bep"); BEP.play().await.expect("could not bep");
} }
Err(e) => { Err(e) => {
info.status = ServerStatus::Running; status.save(&mut db).await.expect("could not save status");
info.save(&mut db).await.expect("could not save status");
ctx.notification_manager ctx.notification_manager
.notify( .notify(
&mut db, &mut db,
@@ -365,6 +360,7 @@ async fn write_stream_to_label<Db: DbHandle>(
last_progress_update = Instant::now(); last_progress_update = Instant::now();
crate::db::DatabaseModel::new() crate::db::DatabaseModel::new()
.server_info() .server_info()
.status_info()
.update_progress() .update_progress()
.put(db, &UpdateProgress { size, downloaded }) .put(db, &UpdateProgress { size, downloaded })
.await?; .await?;