diff --git a/Makefile b/Makefile index a12e65c6e..577c5ad35 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ PATCH_DB_CLIENT_SRC = $(shell find patch-db/client -not -path patch-db/client/di GIT_REFS := $(shell find .git/refs/heads) TMP_FILE := $(shell mktemp) +.DELETE_ON_ERROR: + all: eos.img gzip: eos.img diff --git a/appmgr/src/action/docker.rs b/appmgr/src/action/docker.rs index a65311423..2eae22fa1 100644 --- a/appmgr/src/action/docker.rs +++ b/appmgr/src/action/docker.rs @@ -3,7 +3,9 @@ use std::collections::BTreeMap; use std::ffi::{OsStr, OsString}; use std::net::Ipv4Addr; use std::path::PathBuf; +use std::time::Duration; +use bollard::container::StopContainerOptions; use serde::{Deserialize, Serialize}; use serde_json::Value; use tracing::instrument; @@ -46,17 +48,39 @@ impl DockerAction { volumes: &Volumes, input: Option, allow_inject: bool, + timeout: Option, ) -> Result, Error> { let mut cmd = tokio::process::Command::new("docker"); + let mut timeout_fut = + futures::future::Either::Left(futures::future::pending::>()); if self.inject && allow_inject { cmd.arg("exec"); } else { + let container_name = Self::container_name(pkg_id, name); cmd.arg("run") .arg("--rm") .arg("--network=start9") .arg(format!("--add-host=embassy:{}", Ipv4Addr::from(HOST_IP))) .arg("--name") - .arg(Self::container_name(pkg_id, name)); + .arg(&container_name); + if let Some(timeout) = timeout { + timeout_fut = futures::future::Either::Right(async move { + tokio::time::sleep(timeout).await; + + match ctx + .docker + .stop_container(&container_name, Some(StopContainerOptions { t: 30 })) + .await + { + Err(bollard::errors::Error::DockerResponseNotFoundError { .. }) + | Err(bollard::errors::Error::DockerResponseConflictError { .. }) + | Err(bollard::errors::Error::DockerResponseNotModifiedError { .. }) => (), // Already stopped + a => a?, + }; + + Ok(futures::future::pending().await) + }); + } } cmd.args( self.docker_args(ctx, pkg_id, pkg_version, volumes, allow_inject) @@ -85,10 +109,13 @@ impl DockerAction { .await .with_kind(crate::ErrorKind::Docker)?; } - let res = handle + let res = tokio::select! { + res = handle .wait_with_output() - .await - .with_kind(crate::ErrorKind::Docker)?; + => res + .with_kind(crate::ErrorKind::Docker)?, + res = timeout_fut => res?, + }; Ok(if res.status.success() || res.status.code() == Some(143) { Ok(if let Some(format) = &self.io_format { match format.from_slice(&res.stdout) { @@ -125,6 +152,7 @@ impl DockerAction { pkg_version: &Version, volumes: &Volumes, input: Option, + timeout: Option, ) -> Result, Error> { let mut cmd = tokio::process::Command::new("docker"); cmd.arg("run").arg("--rm").arg("--network=none"); diff --git a/appmgr/src/action/mod.rs b/appmgr/src/action/mod.rs index c35421b44..8950508d8 100644 --- a/appmgr/src/action/mod.rs +++ b/appmgr/src/action/mod.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use std::path::Path; use std::str::FromStr; +use std::time::Duration; use clap::ArgMatches; use color_eyre::eyre::eyre; @@ -131,6 +132,7 @@ impl Action { volumes, input, true, + None, ) .await? .map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::Action)) @@ -154,11 +156,21 @@ impl ActionImplementation { volumes: &Volumes, input: Option, allow_inject: bool, + timeout: Option, ) -> Result, Error> { match self { ActionImplementation::Docker(action) => { action - .execute(ctx, pkg_id, pkg_version, name, volumes, input, allow_inject) + .execute( + ctx, + pkg_id, + pkg_version, + name, + volumes, + input, + allow_inject, + timeout, + ) .await } } @@ -171,11 +183,12 @@ impl ActionImplementation { pkg_version: &Version, volumes: &Volumes, input: Option, + timeout: Option, ) -> Result, Error> { match self { ActionImplementation::Docker(action) => { action - .sandboxed(ctx, pkg_id, pkg_version, volumes, input) + .sandboxed(ctx, pkg_id, pkg_version, volumes, input, timeout) .await } } diff --git a/appmgr/src/backup/mod.rs b/appmgr/src/backup/mod.rs index 076051d01..560934163 100644 --- a/appmgr/src/backup/mod.rs +++ b/appmgr/src/backup/mod.rs @@ -89,6 +89,7 @@ impl BackupActions { &volumes, None, false, + None, ) .await? .map_err(|e| eyre!("{}", e.1)) @@ -167,6 +168,7 @@ impl BackupActions { &volumes, None, false, + None, ) .await? .map_err(|e| eyre!("{}", e.1)) diff --git a/appmgr/src/config/action.rs b/appmgr/src/config/action.rs index 43b86bb70..7ab325fc8 100644 --- a/appmgr/src/config/action.rs +++ b/appmgr/src/config/action.rs @@ -46,6 +46,7 @@ impl ConfigActions { volumes, None::<()>, false, + None, ) .await .and_then(|res| { @@ -73,6 +74,7 @@ impl ConfigActions { volumes, Some(input), false, + None, ) .await .and_then(|res| { diff --git a/appmgr/src/config/mod.rs b/appmgr/src/config/mod.rs index c0464e444..47eee77b8 100644 --- a/appmgr/src/config/mod.rs +++ b/appmgr/src/config/mod.rs @@ -23,9 +23,7 @@ use crate::dependencies::{ }; use crate::install::cleanup::remove_current_dependents; use crate::s9pk::manifest::{Manifest, PackageId}; -use crate::util::{ - display_none, display_serializable, parse_duration, parse_stdin_deserializable, IoFormat, -}; +use crate::util::{display_none, display_serializable, parse_stdin_deserializable, IoFormat}; use crate::{Error, ResultExt as _}; pub mod action; @@ -190,11 +188,11 @@ pub fn set( #[allow(unused_variables)] #[arg(long = "format")] format: Option, - #[arg(long = "timeout", parse(parse_duration))] timeout: Option, + #[arg(long = "timeout")] timeout: Option, #[arg(stdin, parse(parse_stdin_deserializable))] config: Option, #[arg(rename = "expire-id", long = "expire-id")] expire_id: Option, ) -> Result<(PackageId, Option, Option, Option), Error> { - Ok((id, config, timeout, expire_id)) + Ok((id, config, timeout.map(|d| *d), expire_id)) } #[command(rename = "dry", display(display_serializable))] diff --git a/appmgr/src/dependencies.rs b/appmgr/src/dependencies.rs index ba6cd3fa4..597d77dbc 100644 --- a/appmgr/src/dependencies.rs +++ b/appmgr/src/dependencies.rs @@ -421,6 +421,7 @@ impl DependencyConfig { dependent_version, dependent_volumes, Some(dependency_config), + None, ) .await? .map_err(|(_, e)| e)) @@ -440,6 +441,7 @@ impl DependencyConfig { dependent_version, dependent_volumes, Some(old), + None, ) .await? .map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure)) @@ -472,7 +474,7 @@ pub async fn configure_impl( &mut db, &dep_id, Some(new_config), - &Some(Duration::from_secs(3)), + &Some(Duration::from_secs(3).into()), false, &mut BTreeMap::new(), &mut BTreeMap::new(), @@ -598,7 +600,14 @@ pub async fn configure_logic( let new_config = dependency .auto_configure - .sandboxed(&ctx, &pkg_id, &pkg_version, &pkg_volumes, Some(&old_config)) + .sandboxed( + &ctx, + &pkg_id, + &pkg_version, + &pkg_volumes, + Some(&old_config), + None, + ) .await? .map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))?; diff --git a/appmgr/src/disk/util.rs b/appmgr/src/disk/util.rs index 1939b7013..ffc4ce878 100644 --- a/appmgr/src/disk/util.rs +++ b/appmgr/src/disk/util.rs @@ -83,7 +83,9 @@ pub async fn get_vendor>(path: P) -> Result, Error .join("device") .join("vendor"), ) - .await?; + .await? + .trim() + .to_owned(); Ok(if vendor.is_empty() { None } else { @@ -104,7 +106,9 @@ pub async fn get_model>(path: P) -> Result, Error> .join("device") .join("model"), ) - .await?; + .await? + .trim() + .to_owned(); Ok(if model.is_empty() { None } else { Some(model) }) } diff --git a/appmgr/src/manager/mod.rs b/appmgr/src/manager/mod.rs index 29b7f28b3..8ddbcad78 100644 --- a/appmgr/src/manager/mod.rs +++ b/appmgr/src/manager/mod.rs @@ -183,6 +183,7 @@ async fn run_main( &rt_state.manifest.volumes, None, false, + None, ) .await }); diff --git a/appmgr/src/migration.rs b/appmgr/src/migration.rs index 666fafa5b..875701835 100644 --- a/appmgr/src/migration.rs +++ b/appmgr/src/migration.rs @@ -44,6 +44,7 @@ impl Migrations { volumes, Some(version), false, + None, ) .await? .map_err(|e| { @@ -78,6 +79,7 @@ impl Migrations { volumes, Some(version), false, + None, ) .await? .map_err(|e| { diff --git a/appmgr/src/net/nginx.rs b/appmgr/src/net/nginx.rs index cf199e8d1..ca1a6a47f 100644 --- a/appmgr/src/net/nginx.rs +++ b/appmgr/src/net/nginx.rs @@ -92,6 +92,13 @@ impl NginxControllerInner { if lan_port_config.ssl { // these have already been written by the net controller let package_path = nginx_root.join(format!("ssl/{}", package)); + if tokio::fs::metadata(&package_path).await.is_err() { + tokio::fs::create_dir_all(&package_path) + .await + .with_ctx(|_| { + (ErrorKind::Filesystem, package_path.display().to_string()) + })?; + } let ssl_path_key = package_path.join(format!("{}.key.pem", id)); let ssl_path_cert = package_path.join(format!("{}.cert.pem", id)); let (key, chain) = ssl_manager.certificate_for(&meta.dns_base).await?; diff --git a/appmgr/src/properties.rs b/appmgr/src/properties.rs index 445374647..9b8d39bb4 100644 --- a/appmgr/src/properties.rs +++ b/appmgr/src/properties.rs @@ -39,6 +39,7 @@ pub async fn fetch_properties(ctx: RpcContext, id: PackageId) -> Result, } impl HealthCheck { #[instrument(skip(ctx))] @@ -94,6 +95,10 @@ impl HealthCheck { volumes, Some(Utc::now().signed_duration_since(started).num_milliseconds()), true, + Some( + self.timeout + .map_or(std::time::Duration::from_secs(30), |d| *d), + ), ) .await?; Ok(match res { diff --git a/appmgr/src/util/mod.rs b/appmgr/src/util/mod.rs index b58d3ad09..33eba023d 100644 --- a/appmgr/src/util/mod.rs +++ b/appmgr/src/util/mod.rs @@ -7,7 +7,6 @@ use std::path::{Path, PathBuf}; use std::process::{exit, Stdio}; use std::str::FromStr; use std::sync::Arc; -use std::time::Duration; use async_trait::async_trait; use clap::ArgMatches; @@ -651,30 +650,98 @@ pub fn parse_stdin_deserializable Deserialize<'de>>( format.from_reader(stdin) } -pub fn parse_duration(arg: &str, _: &ArgMatches<'_>) -> Result { - let units_idx = arg.find(|c: char| c.is_alphabetic()).ok_or_else(|| { - Error::new( - eyre!("Must specify units for duration"), - crate::ErrorKind::Deserialization, - ) - })?; - let (num, units) = arg.split_at(units_idx); - match units { - "d" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse::()? * 86400_f64)), - "d" => Ok(Duration::from_secs(num.parse::()? * 86400)), - "h" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse::()? * 3600_f64)), - "h" => Ok(Duration::from_secs(num.parse::()? * 3600)), - "m" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse::()? * 60_f64)), - "m" => Ok(Duration::from_secs(num.parse::()? * 60)), - "s" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse()?)), - "s" => Ok(Duration::from_secs(num.parse()?)), - "ms" => Ok(Duration::from_millis(num.parse()?)), - "us" => Ok(Duration::from_micros(num.parse()?)), - "ns" => Ok(Duration::from_nanos(num.parse()?)), - _ => Err(Error::new( - eyre!("Invalid units for duration"), - crate::ErrorKind::Deserialization, - )), +#[derive(Debug, Clone, Copy)] +pub struct Duration(std::time::Duration); +impl Deref for Duration { + type Target = std::time::Duration; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl From for Duration { + fn from(t: std::time::Duration) -> Self { + Duration(t) + } +} +impl std::str::FromStr for Duration { + type Err = Error; + fn from_str(s: &str) -> Result { + let units_idx = s.find(|c: char| c.is_alphabetic()).ok_or_else(|| { + Error::new( + eyre!("Must specify units for duration"), + crate::ErrorKind::Deserialization, + ) + })?; + let (num, units) = s.split_at(units_idx); + use std::time::Duration; + Ok(Duration(match units { + "d" if num.contains(".") => Duration::from_secs_f64(num.parse::()? * 86_400_f64), + "d" => Duration::from_secs(num.parse::()? * 86_400), + "h" if num.contains(".") => Duration::from_secs_f64(num.parse::()? * 3_600_f64), + "h" => Duration::from_secs(num.parse::()? * 3_600), + "m" if num.contains(".") => Duration::from_secs_f64(num.parse::()? * 60_f64), + "m" => Duration::from_secs(num.parse::()? * 60), + "s" if num.contains(".") => Duration::from_secs_f64(num.parse()?), + "s" => Duration::from_secs(num.parse()?), + "ms" if num.contains(".") => Duration::from_secs_f64(num.parse::()? / 1_000_f64), + "ms" => { + let millis: u128 = num.parse()?; + Duration::new((millis / 1_000) as u64, (millis % 1_000) as u32) + } + "us" | "µs" if num.contains(".") => { + Duration::from_secs_f64(num.parse::()? / 1_000_000_f64) + } + "us" | "µs" => { + let micros: u128 = num.parse()?; + Duration::new((micros / 1_000_000) as u64, (micros % 1_000_000) as u32) + } + "ns" if num.contains(".") => { + Duration::from_secs_f64(num.parse::()? / 1_000_000_000_f64) + } + "ns" => { + let nanos: u128 = num.parse()?; + Duration::new( + (nanos / 1_000_000_000) as u64, + (nanos % 1_000_000_000) as u32, + ) + } + _ => { + return Err(Error::new( + eyre!("Invalid units for duration"), + crate::ErrorKind::Deserialization, + )) + } + })) + } +} +impl std::fmt::Display for Duration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let nanos = self.as_nanos(); + match () { + _ if nanos % 86_400_000_000_000 == 0 => write!(f, "{}d", nanos / 86_400_000_000_000), + _ if nanos % 3_600_000_000_000 == 0 => write!(f, "{}h", nanos / 3_600_000_000_000), + _ if nanos % 60_000_000_000 == 0 => write!(f, "{}m", nanos / 60_000_000_000), + _ if nanos % 1_000_000_000 == 0 => write!(f, "{}s", nanos / 1_000_000_000), + _ if nanos % 1_000_000 == 0 => write!(f, "{}ms", nanos / 1_000_000), + _ if nanos % 1_000 == 0 => write!(f, "{}µs", nanos / 1_000), + _ => write!(f, "{}ns", nanos), + } + } +} +impl<'de> Deserialize<'de> for Duration { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_from_str(deserializer) + } +} +impl Serialize for Duration { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serialize_display(self, serializer) } } diff --git a/ui/package.json b/ui/package.json index c36a6ac7a..2bf7375a4 100644 --- a/ui/package.json +++ b/ui/package.json @@ -54,4 +54,4 @@ "tslint": "^6.1.3", "typescript": "4.3.5" } -} \ No newline at end of file +}