address bugs

This commit is contained in:
Aiden McClelland
2021-08-30 13:31:51 -06:00
committed by Aiden McClelland
parent c278e7fbc2
commit cdca5e1b67
8 changed files with 232 additions and 83 deletions

View File

@@ -8,9 +8,9 @@ if [ "$0" != "./build-prod.sh" ]; then
exit 1 exit 1
fi fi
alias 'rust-arm-builder'='docker run --rm -it -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src start9/rust-arm-cross:latest' alias 'rust-arm64-builder'='docker run --rm -it -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src start9/rust-arm-cross:aarch64'
cd .. cd ../..
rust-arm-builder sh -c "(cd appmgr && cargo build --release --features=production)" rust-arm64-builder sh -c "(cd embassy-os/appmgr && cargo +beta build --release --features=production)"
cd appmgr cd embassy-os/appmgr
rust-arm-builder arm-linux-gnueabi-strip target/armv7-unknown-linux-gnueabihf/release/appmgr #rust-arm64-builder aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/embassyd

View File

@@ -1,8 +1,18 @@
use embassy::Error; use embassy::Error;
async fn inner_main() -> Result<(), Error> { async fn inner_main() -> Result<(), Error> {
// os sync // host setup flow if needed
embassy::volume::disk::mount("/dev/sda", "/mnt/embassy-os-crypt").await?;
// mount disk
embassy::volume::disk::mount("/dev/sda", "/mnt/embassy-os-crypt").await?; // TODO: by uuid
// unlock disk
// mount /var/log/journal
// sync ssh
// sync wifi
// hostname-set // hostname-set
embassy::hostname::sync_hostname().await?; embassy::hostname::sync_hostname().await?;

View File

@@ -108,6 +108,7 @@ impl RpcContext {
revision_cache: RwLock::new(VecDeque::new()), revision_cache: RwLock::new(VecDeque::new()),
metrics_cache: RwLock::new(None), metrics_cache: RwLock::new(None),
}); });
// TODO: handle apps in bad / transient state
Ok(Self(seed)) Ok(Self(seed))
} }
pub async fn package_registry_url(&self) -> Result<Url, Error> { pub async fn package_registry_url(&self) -> Result<Url, Error> {

View File

@@ -1,23 +1,29 @@
use anyhow::anyhow; use anyhow::anyhow;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use indexmap::IndexMap; use indexmap::IndexMap;
use patch_db::DbHandle;
use rpc_toolkit::command; use rpc_toolkit::command;
use crate::context::EitherContext; use crate::context::EitherContext;
use crate::db::util::WithRevision;
use crate::s9pk::manifest::PackageId; use crate::s9pk::manifest::PackageId;
use crate::status::MainStatus; use crate::status::MainStatus;
use crate::util::display_none; use crate::util::display_none;
use crate::{Error, ResultExt}; use crate::{Error, ResultExt};
#[command(display(display_none))] #[command(display(display_none))]
pub async fn start(#[context] ctx: EitherContext, #[arg] id: PackageId) -> Result<(), Error> { pub async fn start(
#[context] ctx: EitherContext,
#[arg] id: PackageId,
) -> Result<WithRevision<()>, Error> {
let rpc_ctx = ctx.as_rpc().unwrap(); let rpc_ctx = ctx.as_rpc().unwrap();
let mut db = rpc_ctx.db.handle(); let mut db = rpc_ctx.db.handle();
let mut tx = db.begin().await?;
let installed = crate::db::DatabaseModel::new() let installed = crate::db::DatabaseModel::new()
.package_data() .package_data()
.idx_model(&id) .idx_model(&id)
.and_then(|pkg| pkg.installed()) .and_then(|pkg| pkg.installed())
.expect(&mut db) .expect(&mut tx)
.await .await
.with_ctx(|_| { .with_ctx(|_| {
( (
@@ -29,10 +35,10 @@ pub async fn start(#[context] ctx: EitherContext, #[arg] id: PackageId) -> Resul
.clone() .clone()
.manifest() .manifest()
.version() .version()
.get(&mut db, true) .get(&mut tx, true)
.await? .await?
.to_owned(); .to_owned();
let mut status = installed.status().main().get_mut(&mut db).await?; let mut status = installed.status().main().get_mut(&mut tx).await?;
*status = MainStatus::Running { *status = MainStatus::Running {
started: Utc::now(), started: Utc::now(),
@@ -45,20 +51,28 @@ pub async fn start(#[context] ctx: EitherContext, #[arg] id: PackageId) -> Resul
})?, })?,
) )
.await?; .await?;
status.save(&mut db).await?; status.save(&mut tx).await?;
Ok(()) Ok(WithRevision {
revision: tx.commit(None).await?,
response: (),
})
} }
#[command(display(display_none))] #[command(display(display_none))]
pub async fn stop(#[context] ctx: EitherContext, #[arg] id: PackageId) -> Result<(), Error> { pub async fn stop(
#[context] ctx: EitherContext,
#[arg] id: PackageId,
) -> Result<WithRevision<()>, Error> {
let rpc_ctx = ctx.as_rpc().unwrap(); let rpc_ctx = ctx.as_rpc().unwrap();
let mut db = rpc_ctx.db.handle(); let mut db = rpc_ctx.db.handle();
let mut tx = db.begin().await?;
let mut status = crate::db::DatabaseModel::new() let mut status = crate::db::DatabaseModel::new()
.package_data() .package_data()
.idx_model(&id) .idx_model(&id)
.and_then(|pkg| pkg.installed()) .and_then(|pkg| pkg.installed())
.expect(&mut db) .expect(&mut tx)
.await .await
.with_ctx(|_| { .with_ctx(|_| {
( (
@@ -68,11 +82,14 @@ pub async fn stop(#[context] ctx: EitherContext, #[arg] id: PackageId) -> Result
})? })?
.status() .status()
.main() .main()
.get_mut(&mut db) .get_mut(&mut tx)
.await?; .await?;
*status = MainStatus::Stopping; *status = MainStatus::Stopping;
status.save(&mut db).await?; status.save(&mut tx).await?;
Ok(()) Ok(WithRevision {
revision: tx.commit(None).await?,
response: (),
})
} }

View File

@@ -1,16 +1,19 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use anyhow::anyhow; use anyhow::anyhow;
use bollard::image::ListImagesOptions;
use bollard::Docker; use bollard::Docker;
use patch_db::DbHandle; use patch_db::{DbHandle, PatchDbHandle};
use tokio::process::Command;
use super::PKG_PUBLIC_DIR; use super::PKG_PUBLIC_DIR;
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::db::model::{InstalledPackageDataEntry, PackageDataEntry}; use crate::db::model::{InstalledPackageDataEntry, PackageDataEntry};
use crate::dependencies::DependencyError; use crate::dependencies::DependencyError;
use crate::s9pk::manifest::{Manifest, PackageId}; use crate::s9pk::manifest::{Manifest, PackageId};
use crate::util::Version; use crate::util::{Invoke, Version};
use crate::Error; use crate::Error;
pub async fn update_dependents<'a, Db: DbHandle, I: IntoIterator<Item = &'a PackageId>>( pub async fn update_dependents<'a, Db: DbHandle, I: IntoIterator<Item = &'a PackageId>>(
@@ -62,7 +65,35 @@ pub async fn update_dependents<'a, Db: DbHandle, I: IntoIterator<Item = &'a Pack
Ok(()) Ok(())
} }
pub async fn cleanup<Db: DbHandle>( pub async fn cleanup(ctx: &RpcContext, id: &PackageId, version: &Version) -> Result<(), Error> {
ctx.managers.remove(&(id.clone(), version.clone())).await;
// docker images start9/$APP_ID/*:$VERSION -q | xargs docker rmi
let images = ctx
.docker
.list_images(Some(ListImagesOptions {
all: false,
filters: {
let mut f = HashMap::new();
f.insert(
"reference".to_owned(),
vec![format!("start9/{}/*:{}", id, version)],
);
f
},
digests: false,
}))
.await?;
futures::future::try_join_all(images.into_iter().map(|image| async {
let image = image; // move into future
ctx.docker.remove_image(&image.id, None, None).await
}))
.await?;
// TODO: delete public dir if not a dependency
Ok(())
}
pub async fn cleanup_failed<Db: DbHandle>(
ctx: &RpcContext, ctx: &RpcContext,
db: &mut Db, db: &mut Db,
id: &PackageId, id: &PackageId,
@@ -76,25 +107,21 @@ pub async fn cleanup<Db: DbHandle>(
.get(db, true) .get(db, true)
.await? .await?
.to_owned(); .to_owned();
if let Some(manifest) = match &pde { if match &pde {
PackageDataEntry::Installing { manifest, .. } => Some(manifest), PackageDataEntry::Installing { .. } => true,
PackageDataEntry::Updating { manifest, .. } => { PackageDataEntry::Updating { manifest, .. } => {
if &manifest.version != version { if &manifest.version != version {
Some(manifest) true
} else { } else {
None false
} }
} }
_ => { _ => {
log::warn!("{}: Nothing to clean up!", id); log::warn!("{}: Nothing to clean up!", id);
None false
} }
} { } {
ctx.managers cleanup(ctx, id, version).await?;
.remove(&(manifest.id.clone(), manifest.version.clone()))
.await;
// docker images start9/$APP_ID/*:$VERSION -q | xargs docker rmi
let public_dir_path = Path::new(PKG_PUBLIC_DIR).join(id).join(version.as_str());
} }
match pde { match pde {
@@ -126,14 +153,39 @@ pub async fn cleanup<Db: DbHandle>(
_ => (), _ => (),
} }
Ok(()) // TODO
}
pub async fn uninstall<Db: DbHandle>(
ctx: &RpcContext,
db: &mut Db,
entry: InstalledPackageDataEntry,
) -> Result<(), Error> {
//TODO
Ok(()) Ok(())
} }
pub async fn uninstall(
ctx: &RpcContext,
db: &mut PatchDbHandle,
entry: &InstalledPackageDataEntry,
) -> Result<(), Error> {
cleanup(ctx, &entry.manifest.id, &entry.manifest.version).await?;
let mut tx = db.begin().await?;
crate::db::DatabaseModel::new()
.package_data()
.remove(&mut tx, &entry.manifest.id)
.await?;
update_dependents(&mut tx, &entry.manifest.id, entry.current_dependents.keys()).await?;
tx.commit(None).await?;
Ok(())
}
#[tokio::test]
async fn test() {
dbg!(
Docker::connect_with_socket_defaults()
.unwrap()
.list_images(Some(ListImagesOptions {
all: false,
filters: {
let mut f = HashMap::new();
f.insert("reference", vec!["start9/*:latest"]);
f
},
digests: false
}))
.await
);
}

View File

@@ -26,15 +26,16 @@ use sha2::{Digest, Sha256};
use tokio::fs::{File, OpenOptions}; use tokio::fs::{File, OpenOptions};
use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt}; use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt};
use self::cleanup::cleanup; use self::cleanup::cleanup_failed;
use self::progress::{InstallProgress, InstallProgressTracker};
use crate::context::{EitherContext, ExtendedContext, RpcContext}; use crate::context::{EitherContext, ExtendedContext, RpcContext};
use crate::db::model::{ use crate::db::model::{
CurrentDependencyInfo, InstalledPackageDataEntry, PackageDataEntry, StaticDependencyInfo, CurrentDependencyInfo, InstalledPackageDataEntry, PackageDataEntry, StaticDependencyInfo,
StaticFiles, StaticFiles,
}; };
use crate::db::util::WithRevision;
use crate::dependencies::update_current_dependents; use crate::dependencies::update_current_dependents;
use crate::install::cleanup::{uninstall, update_dependents}; use crate::install::cleanup::{cleanup, update_dependents};
use crate::install::progress::{InstallProgress, InstallProgressTracker};
use crate::s9pk::manifest::{Manifest, PackageId}; use crate::s9pk::manifest::{Manifest, PackageId};
use crate::s9pk::reader::S9pkReader; use crate::s9pk::reader::S9pkReader;
use crate::status::{DependencyErrors, MainStatus, Status}; use crate::status::{DependencyErrors, MainStatus, Status};
@@ -48,7 +49,10 @@ pub const PKG_CACHE: &'static str = "/mnt/embassy-os/cache/packages";
pub const PKG_PUBLIC_DIR: &'static str = "/mnt/embassy-os/public/package-data"; pub const PKG_PUBLIC_DIR: &'static str = "/mnt/embassy-os/public/package-data";
#[command(display(display_none))] #[command(display(display_none))]
pub async fn install(#[context] ctx: EitherContext, #[arg] id: String) -> Result<(), Error> { pub async fn install(
#[context] ctx: EitherContext,
#[arg] id: String,
) -> Result<WithRevision<()>, Error> {
let rpc_ctx = ctx.to_rpc().unwrap(); let rpc_ctx = ctx.to_rpc().unwrap();
let (pkg_id, version_str) = if let Some(split) = id.split_once("@") { let (pkg_id, version_str) = if let Some(split) = id.split_once("@") {
split split
@@ -68,14 +72,105 @@ pub async fn install(#[context] ctx: EitherContext, #[arg] id: String) -> Result
)) ))
) )
.with_kind(crate::ErrorKind::Registry)?; .with_kind(crate::ErrorKind::Registry)?;
let man = man_res.json().await.with_kind(crate::ErrorKind::Registry)?; let man: Manifest = man_res.json().await.with_kind(crate::ErrorKind::Registry)?;
let progress = InstallProgress::new(s9pk.content_length());
let static_files = StaticFiles::remote(&man.id, &man.version, man.assets.icon_type());
let mut db_handle = rpc_ctx.db.handle();
let mut tx = db_handle.begin().await?;
let mut pde = crate::db::DatabaseModel::new()
.package_data()
.idx_model(&man.id)
.get_mut(&mut tx)
.await?;
match pde.take() {
Some(PackageDataEntry::Installed {
installed,
manifest,
static_files,
}) => {
*pde = Some(PackageDataEntry::Updating {
install_progress: progress.clone(),
static_files,
installed,
manifest,
})
}
None => {
*pde = Some(PackageDataEntry::Installing {
install_progress: progress.clone(),
static_files,
manifest: man.clone(),
})
}
_ => {
return Err(Error::new(
anyhow!("Cannot install over an app in a transient state"),
crate::ErrorKind::InvalidRequest,
))
}
}
pde.save(&mut tx).await?;
let res = tx.commit(None).await?;
drop(db_handle);
tokio::spawn(async move { tokio::spawn(async move {
if let Err(e) = download_install_s9pk(&rpc_ctx, &man, s9pk).await { if let Err(e) = download_install_s9pk(&rpc_ctx, &man, s9pk).await {
log::error!("Install of {}@{} Failed: {}", man.id, man.version, e); log::error!("Install of {}@{} Failed: {}", man.id, man.version, e);
} }
}); });
Ok(()) Ok(WithRevision {
revision: res,
response: (),
})
}
#[command(display(display_none))]
pub async fn uninstall(
#[context] ctx: EitherContext,
#[arg] id: PackageId,
) -> Result<WithRevision<()>, Error> {
let mut handle = ctx.as_rpc().unwrap().db.handle();
let mut tx = handle.begin().await?;
let mut pde = crate::db::DatabaseModel::new()
.package_data()
.idx_model(&id)
.get_mut(&mut tx)
.await?;
let (manifest, static_files, installed) = match pde.take() {
Some(PackageDataEntry::Installed {
manifest,
static_files,
installed,
}) => (manifest, static_files, installed),
_ => {
return Err(Error::new(
anyhow!("Package is not installed."),
crate::ErrorKind::NotFound,
));
}
};
*pde = Some(PackageDataEntry::Removing {
manifest,
static_files,
});
pde.save(&mut tx).await?;
let res = tx.commit(None).await?;
drop(handle);
tokio::spawn(async move {
let rpc_ctx = ctx.as_rpc().unwrap();
if let Err(e) = cleanup::uninstall(rpc_ctx, &mut rpc_ctx.db.handle(), &installed).await {
log::error!("Uninstall of {} Failed: {}", id, e);
}
});
Ok(WithRevision {
revision: res,
response: (),
})
} }
pub async fn download_install_s9pk( pub async fn download_install_s9pk(
@@ -96,38 +191,6 @@ pub async fn download_install_s9pk(
let res = (|| async { let res = (|| async {
let progress = InstallProgress::new(s9pk.content_length()); let progress = InstallProgress::new(s9pk.content_length());
let static_files = StaticFiles::remote(pkg_id, version, temp_manifest.assets.icon_type());
let mut db_handle = ctx.db.handle();
let mut pde = pkg_data_entry.get_mut(&mut db_handle).await?;
match pde.take() {
Some(PackageDataEntry::Installed {
installed,
manifest,
static_files,
}) => {
*pde = Some(PackageDataEntry::Updating {
install_progress: progress.clone(),
static_files,
installed,
manifest,
})
}
None => {
*pde = Some(PackageDataEntry::Installing {
install_progress: progress.clone(),
static_files,
manifest: temp_manifest.clone(),
})
}
_ => {
return Err(Error::new(
anyhow!("Cannot install over an app in a transient state"),
crate::ErrorKind::InvalidRequest,
))
}
}
pde.save(&mut db_handle).await?;
drop(db_handle);
let progress_model = pkg_data_entry.and_then(|pde| pde.install_progress()); let progress_model = pkg_data_entry.and_then(|pde| pde.install_progress());
async fn check_cache( async fn check_cache(
@@ -242,7 +305,7 @@ pub async fn download_install_s9pk(
let mut handle = ctx.db.handle(); let mut handle = ctx.db.handle();
let mut tx = handle.begin().await?; let mut tx = handle.begin().await?;
if let Err(e) = cleanup(&ctx, &mut tx, pkg_id, version).await { if let Err(e) = cleanup_failed(&ctx, &mut tx, pkg_id, version).await {
log::error!( log::error!(
"Failed to clean up {}@{}: {}: Adding to broken packages", "Failed to clean up {}@{}: {}: Adding to broken packages",
pkg_id, pkg_id,
@@ -566,7 +629,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
configured &= res.configured; configured &= res.configured;
} }
if &prev.manifest.version != version { if &prev.manifest.version != version {
uninstall(ctx, &mut tx, prev).await?; cleanup(ctx, &prev.manifest.id, &prev.manifest.version).await?;
} }
if let Some(res) = manifest if let Some(res) = manifest
.migrations .migrations

View File

@@ -72,7 +72,13 @@ pub fn main_api(#[context] ctx: EitherContext) -> Result<EitherContext, RpcError
Ok(ctx) Ok(ctx)
} }
#[command(subcommands(install::install, config::config, control::start, control::stop))] #[command(subcommands(
install::install,
install::uninstall,
config::config,
control::start,
control::stop
))]
pub fn package(#[context] ctx: EitherContext) -> Result<EitherContext, RpcError> { pub fn package(#[context] ctx: EitherContext) -> Result<EitherContext, RpcError> {
Ok(ctx) Ok(ctx)
} }

View File

@@ -291,10 +291,10 @@ impl Manager {
.unwrap(); // recv is still in scope, cannot fail .unwrap(); // recv is still in scope, cannot fail
} }
Ok(Err(e)) => { Ok(Err(e)) => {
todo!("application crashed") log::error!("application crashed: {}: {}", e.0, e.1)
} }
Err(e) => { Err(e) => {
todo!("failed to start application: {}", e) log::error!("failed to start application: {}", e)
} }
} }
} }