mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
switch to managers: wip
This commit is contained in:
committed by
Aiden McClelland
parent
e2b77b23f8
commit
34e4c12af3
@@ -7,12 +7,13 @@ use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::id::ImageId;
|
||||
use crate::net::host::Hosts;
|
||||
use crate::id::{Id, ImageId};
|
||||
use crate::s9pk::manifest::{PackageId, SYSTEM_PACKAGE_ID};
|
||||
use crate::util::{Invoke, IoFormat, Version};
|
||||
use crate::util::{IoFormat, Version};
|
||||
use crate::volume::{VolumeId, Volumes};
|
||||
use crate::{Error, ResultExt};
|
||||
use crate::{Error, ResultExt, HOST_IP};
|
||||
|
||||
pub const NET_TLD: &'static str = "embassy";
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
@@ -33,33 +34,12 @@ pub struct DockerAction {
|
||||
pub shm_size_mb: Option<usize>, // TODO: use postfix sizing? like 1k vs 1m vs 1g
|
||||
}
|
||||
impl DockerAction {
|
||||
pub async fn create(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
volumes: &Volumes,
|
||||
ip: Ipv4Addr,
|
||||
) -> Result<(), Error> {
|
||||
tokio::process::Command::new("docker")
|
||||
.arg("create")
|
||||
.arg("--net")
|
||||
.arg("start9")
|
||||
.arg("--ip")
|
||||
.arg(format!("{}", ip))
|
||||
.arg("--name")
|
||||
.arg(Self::container_name(pkg_id, pkg_version))
|
||||
.args(self.docker_args(pkg_id, pkg_version, volumes, false))
|
||||
.invoke(crate::ErrorKind::Docker)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn execute<I: Serialize, O: for<'de> Deserialize<'de>>(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
name: Option<&str>,
|
||||
volumes: &Volumes,
|
||||
hosts: &Hosts,
|
||||
input: Option<I>,
|
||||
allow_inject: bool,
|
||||
) -> Result<Result<O, (i32, String)>, Error> {
|
||||
@@ -67,8 +47,12 @@ impl DockerAction {
|
||||
if self.inject && allow_inject {
|
||||
cmd.arg("exec");
|
||||
} else {
|
||||
cmd.arg("run").arg("--rm");
|
||||
cmd.args(hosts.docker_args());
|
||||
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));
|
||||
}
|
||||
cmd.args(self.docker_args(pkg_id, pkg_version, volumes, allow_inject));
|
||||
let input_buf = if let (Some(input), Some(format)) = (&input, &self.io_format) {
|
||||
@@ -126,8 +110,7 @@ impl DockerAction {
|
||||
input: Option<I>,
|
||||
) -> Result<Result<O, (i32, String)>, Error> {
|
||||
let mut cmd = tokio::process::Command::new("docker");
|
||||
cmd.arg("run").arg("--rm");
|
||||
cmd.arg("--network=none");
|
||||
cmd.arg("run").arg("--rm").arg("--network=none");
|
||||
cmd.args(self.docker_args(pkg_id, pkg_version, &Volumes::default(), false));
|
||||
let input_buf = if let (Some(input), Some(format)) = (&input, &self.io_format) {
|
||||
cmd.stdin(std::process::Stdio::piped());
|
||||
@@ -177,15 +160,22 @@ impl DockerAction {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn container_name(pkg_id: &PackageId, version: &Version) -> String {
|
||||
format!("service_{}_{}", pkg_id, version)
|
||||
pub fn container_name(pkg_id: &PackageId, name: Option<&str>) -> String {
|
||||
if let Some(name) = name {
|
||||
format!("{}_{}.{}", pkg_id, name, NET_TLD)
|
||||
} else {
|
||||
format!("{}.{}", pkg_id, NET_TLD)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uncontainer_name(name: &str) -> Option<(&str, Version)> {
|
||||
name.trim_start_matches("/")
|
||||
.strip_prefix("service_")
|
||||
.and_then(|name| name.split_once("_"))
|
||||
.and_then(|(id, version)| Some((id, version.parse().ok()?)))
|
||||
pub fn uncontainer_name<'a>(name: &'a str) -> Option<(PackageId<&'a str>, Option<&'a str>)> {
|
||||
let (pre_tld, _) = name.split_once(".")?;
|
||||
if pre_tld.contains("_") {
|
||||
let (pkg, name) = name.split_once("_")?;
|
||||
Some((Id::try_from(pkg).ok()?.into(), Some(name)))
|
||||
} else {
|
||||
Some((Id::try_from(pre_tld).ok()?.into(), None))
|
||||
}
|
||||
}
|
||||
|
||||
fn docker_args<'a>(
|
||||
@@ -208,6 +198,10 @@ impl DockerAction {
|
||||
continue;
|
||||
};
|
||||
let src = volume.path_for(pkg_id, pkg_version, volume_id);
|
||||
if !src.exists() {
|
||||
// TODO: this is a blocking call, make this async?
|
||||
continue;
|
||||
}
|
||||
res.push(OsStr::new("--mount").into());
|
||||
res.push(
|
||||
dbg!(OsString::from(format!(
|
||||
@@ -224,7 +218,7 @@ impl DockerAction {
|
||||
res.push(OsString::from(format!("{}m", shm_size_mb)).into());
|
||||
}
|
||||
if self.inject && allow_inject {
|
||||
res.push(OsString::from(Self::container_name(pkg_id, pkg_version)).into());
|
||||
res.push(OsString::from(Self::container_name(pkg_id, None)).into());
|
||||
res.push(OsStr::new(&self.entrypoint).into());
|
||||
} else {
|
||||
res.push(OsStr::new("--entrypoint").into());
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::anyhow;
|
||||
@@ -9,7 +8,6 @@ use serde::{Deserialize, Serialize};
|
||||
use self::docker::DockerAction;
|
||||
use crate::config::{Config, ConfigSpec};
|
||||
use crate::id::Id;
|
||||
use crate::net::host::Hosts;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::{ValuePrimative, Version};
|
||||
use crate::volume::Volumes;
|
||||
@@ -95,14 +93,20 @@ impl Action {
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
volumes: &Volumes,
|
||||
hosts: &Hosts,
|
||||
input: Config,
|
||||
) -> Result<ActionResult, Error> {
|
||||
self.input_spec
|
||||
.matches(&input)
|
||||
.with_kind(crate::ErrorKind::ConfigSpecViolation)?;
|
||||
self.implementation
|
||||
.execute(pkg_id, pkg_version, volumes, hosts, Some(input), true)
|
||||
.execute(
|
||||
pkg_id,
|
||||
pkg_version,
|
||||
Some(&format!("{}Action", self.name)),
|
||||
volumes,
|
||||
Some(input),
|
||||
true,
|
||||
)
|
||||
.await?
|
||||
.map_err(|e| Error::new(anyhow!("{}", e.1), crate::ErrorKind::Action))
|
||||
}
|
||||
@@ -115,32 +119,19 @@ pub enum ActionImplementation {
|
||||
Docker(DockerAction),
|
||||
}
|
||||
impl ActionImplementation {
|
||||
pub async fn install(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
volumes: &Volumes,
|
||||
ip: Ipv4Addr,
|
||||
) -> Result<(), Error> {
|
||||
match self {
|
||||
ActionImplementation::Docker(action) => {
|
||||
action.create(pkg_id, pkg_version, volumes, ip).await
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn execute<I: Serialize, O: for<'de> Deserialize<'de>>(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
name: Option<&str>,
|
||||
volumes: &Volumes,
|
||||
hosts: &Hosts,
|
||||
input: Option<I>,
|
||||
allow_inject: bool,
|
||||
) -> Result<Result<O, (i32, String)>, Error> {
|
||||
match self {
|
||||
ActionImplementation::Docker(action) => {
|
||||
action
|
||||
.execute(pkg_id, pkg_version, volumes, hosts, input, allow_inject)
|
||||
.execute(pkg_id, pkg_version, name, volumes, input, allow_inject)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use patch_db::HasModel;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::action::ActionImplementation;
|
||||
use crate::net::host::Hosts;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::Version;
|
||||
use crate::volume::{Volume, VolumeId, Volumes};
|
||||
@@ -15,17 +14,23 @@ pub struct BackupActions {
|
||||
pub restore: ActionImplementation,
|
||||
}
|
||||
impl BackupActions {
|
||||
pub async fn backup(
|
||||
pub async fn create(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
volumes: &Volumes,
|
||||
hosts: &Hosts,
|
||||
) -> Result<(), Error> {
|
||||
let mut volumes = volumes.to_readonly();
|
||||
volumes.insert(VolumeId::Backup, Volume::Backup { readonly: false });
|
||||
self.create
|
||||
.execute(pkg_id, pkg_version, &volumes, hosts, None::<()>, false)
|
||||
.execute(
|
||||
pkg_id,
|
||||
pkg_version,
|
||||
Some("CreateBackup"),
|
||||
&volumes,
|
||||
None::<()>,
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.map_err(|e| anyhow!("{}", e.1))
|
||||
.with_kind(crate::ErrorKind::Backup)?;
|
||||
@@ -37,12 +42,18 @@ impl BackupActions {
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
volumes: &Volumes,
|
||||
hosts: &Hosts,
|
||||
) -> Result<(), Error> {
|
||||
let mut volumes = volumes.clone();
|
||||
volumes.insert(VolumeId::Backup, Volume::Backup { readonly: true });
|
||||
self.restore
|
||||
.execute(pkg_id, pkg_version, &volumes, hosts, None::<()>, false)
|
||||
.execute(
|
||||
pkg_id,
|
||||
pkg_version,
|
||||
Some("RestoreBackup"),
|
||||
&volumes,
|
||||
None::<()>,
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.map_err(|e| anyhow!("{}", e.1))
|
||||
.with_kind(crate::ErrorKind::Restore)?;
|
||||
|
||||
@@ -7,7 +7,6 @@ use serde::{Deserialize, Serialize};
|
||||
use super::{Config, ConfigSpec};
|
||||
use crate::action::ActionImplementation;
|
||||
use crate::dependencies::Dependencies;
|
||||
use crate::net::host::Hosts;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::status::health_check::HealthCheckId;
|
||||
use crate::util::Version;
|
||||
@@ -32,10 +31,16 @@ impl ConfigActions {
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
volumes: &Volumes,
|
||||
hosts: &Hosts,
|
||||
) -> Result<ConfigRes, Error> {
|
||||
self.get
|
||||
.execute(pkg_id, pkg_version, volumes, hosts, None::<()>, false)
|
||||
.execute(
|
||||
pkg_id,
|
||||
pkg_version,
|
||||
Some("GetConfig"),
|
||||
volumes,
|
||||
None::<()>,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.and_then(|res| {
|
||||
res.map_err(|e| Error::new(anyhow!("{}", e.1), crate::ErrorKind::ConfigGen))
|
||||
@@ -48,12 +53,18 @@ impl ConfigActions {
|
||||
pkg_version: &Version,
|
||||
dependencies: &Dependencies,
|
||||
volumes: &Volumes,
|
||||
hosts: &Hosts,
|
||||
input: &Config,
|
||||
) -> Result<SetResult, Error> {
|
||||
let res: SetResult = self
|
||||
.set
|
||||
.execute(pkg_id, pkg_version, volumes, hosts, Some(input), false)
|
||||
.execute(
|
||||
pkg_id,
|
||||
pkg_version,
|
||||
Some("SetConfig"),
|
||||
volumes,
|
||||
Some(input),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.and_then(|res| {
|
||||
res.map_err(|e| {
|
||||
|
||||
@@ -18,7 +18,6 @@ use crate::context::{EitherContext, ExtendedContext};
|
||||
use crate::db::model::{CurrentDependencyInfo, InstalledPackageDataEntryModel};
|
||||
use crate::db::util::WithRevision;
|
||||
use crate::dependencies::{BreakageRes, DependencyError, TaggedDependencyError};
|
||||
use crate::net::host::Hosts;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::{
|
||||
display_none, display_serializable, parse_duration, parse_stdin_deserializable, IoFormat,
|
||||
@@ -173,14 +172,7 @@ pub async fn get(
|
||||
})?;
|
||||
let version = pkg_model.clone().manifest().version().get(&mut db).await?;
|
||||
let volumes = pkg_model.manifest().volumes().get(&mut db).await?;
|
||||
let hosts = crate::db::DatabaseModel::new()
|
||||
.network()
|
||||
.hosts()
|
||||
.get(&mut db)
|
||||
.await?;
|
||||
action
|
||||
.get(ctx.extension(), &*version, &*volumes, &*hosts)
|
||||
.await
|
||||
action.get(ctx.extension(), &*version, &*volumes).await
|
||||
}
|
||||
|
||||
#[command(subcommands(self(set_impl(async)), set_dry), display(display_none))]
|
||||
@@ -209,17 +201,11 @@ pub async fn set_dry(
|
||||
let (ctx, (id, config, timeout, _)) = ctx.split();
|
||||
let rpc_ctx = ctx.as_rpc().unwrap();
|
||||
let mut db = rpc_ctx.db.handle();
|
||||
let hosts = crate::db::DatabaseModel::new()
|
||||
.network()
|
||||
.hosts()
|
||||
.get(&mut db)
|
||||
.await?;
|
||||
let mut tx = db.begin().await?;
|
||||
let mut breakages = IndexMap::new();
|
||||
configure(
|
||||
&mut tx,
|
||||
&rpc_ctx.docker,
|
||||
&*hosts,
|
||||
&id,
|
||||
config,
|
||||
&timeout,
|
||||
@@ -255,17 +241,11 @@ pub async fn set_impl(
|
||||
let (ctx, (id, config, timeout, expire_id)) = ctx.split();
|
||||
let rpc_ctx = ctx.as_rpc().unwrap();
|
||||
let mut db = rpc_ctx.db.handle();
|
||||
let hosts = crate::db::DatabaseModel::new()
|
||||
.network()
|
||||
.hosts()
|
||||
.get(&mut db)
|
||||
.await?;
|
||||
let mut tx = db.begin().await?;
|
||||
let mut breakages = IndexMap::new();
|
||||
configure(
|
||||
&mut tx,
|
||||
&rpc_ctx.docker,
|
||||
&*hosts,
|
||||
&id,
|
||||
config,
|
||||
&timeout,
|
||||
@@ -295,7 +275,6 @@ pub async fn set_impl(
|
||||
pub fn configure<'a, Db: DbHandle>(
|
||||
db: &'a mut Db,
|
||||
docker: &'a Docker,
|
||||
hosts: &'a Hosts,
|
||||
id: &'a PackageId,
|
||||
config: Option<Config>,
|
||||
timeout: &'a Option<Duration>,
|
||||
@@ -330,7 +309,7 @@ pub fn configure<'a, Db: DbHandle>(
|
||||
let ConfigRes {
|
||||
config: old_config,
|
||||
spec,
|
||||
} = action.get(id, &*version, &*volumes, &*hosts).await?;
|
||||
} = action.get(id, &*version, &*volumes).await?;
|
||||
|
||||
// determine new config to use
|
||||
let mut config = if let Some(config) = config.or_else(|| old_config.clone()) {
|
||||
@@ -379,7 +358,7 @@ pub fn configure<'a, Db: DbHandle>(
|
||||
let signal = if !dry_run {
|
||||
// run config action
|
||||
let res = action
|
||||
.set(id, &*version, &*dependencies, &*volumes, hosts, &config)
|
||||
.set(id, &*version, &*dependencies, &*volumes, &config)
|
||||
.await?;
|
||||
|
||||
// track dependencies with no pointers
|
||||
@@ -539,8 +518,7 @@ pub fn configure<'a, Db: DbHandle>(
|
||||
if let PackagePointerSpecVariant::Config { selector, multi } = ptr {
|
||||
if selector.select(*multi, &next) != selector.select(*multi, &prev) {
|
||||
if let Err(e) = configure(
|
||||
db, docker, hosts, dependent, None, timeout, dry_run, overrides,
|
||||
breakages,
|
||||
db, docker, dependent, None, timeout, dry_run, overrides, breakages,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -575,7 +553,7 @@ pub fn configure<'a, Db: DbHandle>(
|
||||
if let Some(signal) = signal {
|
||||
docker
|
||||
.kill_container(
|
||||
&DockerAction::container_name(id, &*version),
|
||||
&DockerAction::container_name(id, None),
|
||||
Some(KillContainerOptions {
|
||||
signal: signal.to_string(),
|
||||
}),
|
||||
@@ -586,6 +564,7 @@ pub fn configure<'a, Db: DbHandle>(
|
||||
if matches!(
|
||||
e,
|
||||
bollard::errors::Error::DockerResponseConflictError { .. }
|
||||
| bollard::errors::Error::DockerResponseNotFoundError { .. }
|
||||
) {
|
||||
Ok(())
|
||||
} else {
|
||||
|
||||
@@ -1562,9 +1562,7 @@ impl PackagePointerSpec {
|
||||
.package_data()
|
||||
.idx_model(&self.package_id)
|
||||
.and_then(|pde| pde.installed())
|
||||
.and_then(|installed| {
|
||||
installed.interface_info().addresses().idx_model(interface)
|
||||
})
|
||||
.and_then(|installed| installed.interface_addresses().idx_model(interface))
|
||||
.and_then(|addresses| addresses.tor_address())
|
||||
.get(db)
|
||||
.await
|
||||
@@ -1576,9 +1574,7 @@ impl PackagePointerSpec {
|
||||
.package_data()
|
||||
.idx_model(&self.package_id)
|
||||
.and_then(|pde| pde.installed())
|
||||
.and_then(|installed| {
|
||||
installed.interface_info().addresses().idx_model(interface)
|
||||
})
|
||||
.and_then(|installed| installed.interface_addresses().idx_model(interface))
|
||||
.and_then(|addresses| addresses.lan_address())
|
||||
.get(db)
|
||||
.await
|
||||
@@ -1612,17 +1608,11 @@ impl PackagePointerSpec {
|
||||
.get(db)
|
||||
.await
|
||||
.map_err(|e| ConfigurationError::SystemError(Error::from(e)))?;
|
||||
let hosts = crate::db::DatabaseModel::new()
|
||||
.network()
|
||||
.hosts()
|
||||
.get(db)
|
||||
.await
|
||||
.map_err(|e| ConfigurationError::SystemError(Error::from(e)))?;
|
||||
if let (Some(version), Some(cfg_actions), Some(volumes)) =
|
||||
(&*version, &*cfg_actions, &*volumes)
|
||||
{
|
||||
let cfg_res = cfg_actions
|
||||
.get(&self.package_id, version, volumes, &*hosts)
|
||||
.get(&self.package_id, version, volumes)
|
||||
.await
|
||||
.map_err(|e| ConfigurationError::SystemError(Error::from(e)))?;
|
||||
if let Some(cfg) = cfg_res.config {
|
||||
|
||||
@@ -12,10 +12,9 @@ use serde::Deserialize;
|
||||
use sqlx::SqlitePool;
|
||||
use tokio::fs::File;
|
||||
|
||||
#[cfg(feature = "avahi")]
|
||||
use crate::net::mdns::MdnsController;
|
||||
use crate::net::tor::TorController;
|
||||
use crate::util::{from_toml_async_reader, AsyncFileExt, Container};
|
||||
use crate::manager::ManagerMap;
|
||||
use crate::net::NetController;
|
||||
use crate::util::{from_toml_async_reader, AsyncFileExt};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
@@ -34,9 +33,8 @@ pub struct RpcContextSeed {
|
||||
pub db: PatchDb,
|
||||
pub secret_store: SqlitePool,
|
||||
pub docker: Docker,
|
||||
pub tor_controller: TorController,
|
||||
#[cfg(feature = "avahi")]
|
||||
pub mdns_controller: MdnsController,
|
||||
pub net_controller: Arc<NetController>,
|
||||
pub managers: ManagerMap,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -67,24 +65,27 @@ impl RpcContext {
|
||||
.display()
|
||||
))
|
||||
.await?;
|
||||
let mut db_handle = db.handle();
|
||||
let tor_controller = TorController::init(
|
||||
base.tor_control.unwrap_or(([127, 0, 0, 1], 9051).into()),
|
||||
&mut db_handle,
|
||||
let net_controller = Arc::new(
|
||||
NetController::init(
|
||||
base.tor_control
|
||||
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
let managers = ManagerMap::init(
|
||||
&mut db.handle(),
|
||||
&mut secret_store.acquire().await?,
|
||||
&*net_controller,
|
||||
)
|
||||
.await?;
|
||||
#[cfg(feature = "avahi")]
|
||||
let mdns_controller = MdnsController::init(&mut db_handle).await?;
|
||||
let seed = Arc::new(RpcContextSeed {
|
||||
bind_rpc: base.bind_rpc.unwrap_or(([127, 0, 0, 1], 5959).into()),
|
||||
bind_ws: base.bind_ws.unwrap_or(([127, 0, 0, 1], 5960).into()),
|
||||
db,
|
||||
secret_store,
|
||||
docker: Docker::connect_with_unix_defaults()?,
|
||||
tor_controller,
|
||||
#[cfg(feature = "avahi")]
|
||||
mdns_controller,
|
||||
net_controller,
|
||||
managers,
|
||||
});
|
||||
Ok(Self(seed))
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ use serde_json::Value;
|
||||
use crate::config::spec::{PackagePointerSpecVariant, SystemPointerSpec};
|
||||
use crate::install::progress::InstallProgress;
|
||||
use crate::net::interface::InterfaceId;
|
||||
use crate::net::Network;
|
||||
use crate::s9pk::manifest::{Manifest, ManifestModel, PackageId};
|
||||
use crate::status::health_check::HealthCheckId;
|
||||
use crate::status::Status;
|
||||
@@ -26,8 +25,6 @@ pub struct Database {
|
||||
#[model]
|
||||
pub package_data: AllPackageData,
|
||||
pub broken_packages: Vec<PackageId>,
|
||||
#[model]
|
||||
pub network: Network,
|
||||
pub ui: Value,
|
||||
}
|
||||
impl Database {
|
||||
@@ -48,7 +45,6 @@ impl Database {
|
||||
},
|
||||
package_data: AllPackageData::default(),
|
||||
broken_packages: Vec::new(),
|
||||
network: Network::default(),
|
||||
ui: Value::Object(Default::default()),
|
||||
}
|
||||
}
|
||||
@@ -160,7 +156,7 @@ pub struct InstalledPackageDataEntry {
|
||||
#[model]
|
||||
pub current_dependencies: IndexMap<PackageId, CurrentDependencyInfo>,
|
||||
#[model]
|
||||
pub interface_info: InterfaceInfo,
|
||||
pub interface_addresses: InterfaceAddressMap,
|
||||
}
|
||||
impl InstalledPackageDataEntryModel {
|
||||
pub fn manifest(self) -> ManifestModel {
|
||||
@@ -177,14 +173,6 @@ pub struct CurrentDependencyInfo {
|
||||
pub health_checks: IndexSet<HealthCheckId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct InterfaceInfo {
|
||||
pub ip: Ipv4Addr,
|
||||
#[model]
|
||||
pub addresses: InterfaceAddressMap,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct InterfaceAddressMap(pub IndexMap<InterfaceId, InterfaceAddresses>);
|
||||
impl Map for InterfaceAddressMap {
|
||||
|
||||
@@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::action::ActionImplementation;
|
||||
use crate::config::{Config, ConfigSpec};
|
||||
use crate::net::host::Hosts;
|
||||
use crate::net::interface::InterfaceId;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::status::health_check::{HealthCheckId, HealthCheckResult, HealthCheckResultVariant};
|
||||
@@ -137,38 +136,31 @@ impl DepInfo {
|
||||
dependent_id: &PackageId,
|
||||
dependent_version: &Version,
|
||||
) -> Result<Result<(), DependencyError>, Error> {
|
||||
let dependency = crate::db::DatabaseModel::new()
|
||||
let (manifest, info) = if let Some(dep_model) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(dependency_id)
|
||||
.and_then(|pde| pde.installed())
|
||||
.get(db)
|
||||
.await?;
|
||||
let info = if let Some(info) = &*dependency {
|
||||
info
|
||||
.check(db)
|
||||
.await?
|
||||
{
|
||||
(
|
||||
dep_model.clone().manifest().get(db).await?,
|
||||
dep_model.get(db).await?,
|
||||
)
|
||||
} else {
|
||||
return Ok(Err(DependencyError::NotInstalled));
|
||||
};
|
||||
if !&info.manifest.version.satisfies(&self.version) {
|
||||
if !&manifest.version.satisfies(&self.version) {
|
||||
return Ok(Err(DependencyError::IncorrectVersion {
|
||||
expected: self.version.clone(),
|
||||
received: info.manifest.version.clone(),
|
||||
received: manifest.version.clone(),
|
||||
}));
|
||||
}
|
||||
let hosts = crate::db::DatabaseModel::new()
|
||||
.network()
|
||||
.hosts()
|
||||
.get(db)
|
||||
.await?;
|
||||
let dependency_config = if let Some(cfg) = dependency_config {
|
||||
cfg
|
||||
} else if let Some(cfg_info) = &info.manifest.config {
|
||||
} else if let Some(cfg_info) = &manifest.config {
|
||||
cfg_info
|
||||
.get(
|
||||
dependency_id,
|
||||
&info.manifest.version,
|
||||
&info.manifest.volumes,
|
||||
&hosts,
|
||||
)
|
||||
.get(dependency_id, &manifest.version, &manifest.volumes)
|
||||
.await?
|
||||
.config
|
||||
.unwrap_or_default()
|
||||
|
||||
@@ -46,6 +46,7 @@ pub enum ErrorKind {
|
||||
InvalidRequest = 38,
|
||||
MigrationFailed = 39,
|
||||
Uninitialized = 40,
|
||||
ParseNetAddress = 41,
|
||||
}
|
||||
impl ErrorKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
@@ -91,6 +92,7 @@ impl ErrorKind {
|
||||
InvalidRequest => "Invalid Request",
|
||||
MigrationFailed => "Migration Failed",
|
||||
Uninitialized => "Uninitialized",
|
||||
ParseNetAddress => "Net Address Parsing Error",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,6 +182,11 @@ impl From<torut::control::ConnError> for Error {
|
||||
Error::new(anyhow!("{:?}", e), ErrorKind::Tor)
|
||||
}
|
||||
}
|
||||
impl From<std::net::AddrParseError> for Error {
|
||||
fn from(e: std::net::AddrParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNetAddress)
|
||||
}
|
||||
}
|
||||
impl From<Error> for RpcError {
|
||||
fn from(e: Error) -> Self {
|
||||
let mut data_object = serde_json::Map::with_capacity(2);
|
||||
|
||||
@@ -364,23 +364,12 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
let mut tx = db.begin().await?;
|
||||
let mut sql_tx = ctx.secret_store.begin().await?;
|
||||
|
||||
let mut network = crate::db::DatabaseModel::new()
|
||||
.network()
|
||||
.get_mut(&mut tx)
|
||||
.await?;
|
||||
|
||||
log::info!("Install {}@{}: Installing main", pkg_id, version);
|
||||
let ip = network.register_host(&manifest.id)?;
|
||||
manifest
|
||||
.main
|
||||
.install(pkg_id, version, &manifest.volumes, ip)
|
||||
.await?;
|
||||
let hosts = network.hosts.clone();
|
||||
network.save(&mut tx).await?;
|
||||
log::info!("Install {}@{}: Installed main", pkg_id, version);
|
||||
log::info!("Install {}@{}: Creating manager", pkg_id, version);
|
||||
todo!("create manager");
|
||||
log::info!("Install {}@{}: Created manager", pkg_id, version);
|
||||
|
||||
log::info!("Install {}@{}: Installing interfaces", pkg_id, version);
|
||||
let interface_info = manifest.interfaces.install(&mut sql_tx, pkg_id, ip).await?;
|
||||
let interface_addresses = manifest.interfaces.install(&mut sql_tx, pkg_id).await?;
|
||||
log::info!("Install {}@{}: Installed interfaces", pkg_id, version);
|
||||
|
||||
let static_files = StaticFiles::local(pkg_id, version, manifest.assets.icon_type());
|
||||
@@ -429,7 +418,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
deps
|
||||
},
|
||||
current_dependencies,
|
||||
interface_info,
|
||||
interface_addresses,
|
||||
};
|
||||
let mut pde = model.get_mut(&mut tx).await?;
|
||||
let prev = std::mem::replace(
|
||||
@@ -455,7 +444,6 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
pkg_id,
|
||||
&prev_manifest.version,
|
||||
&prev_manifest.volumes,
|
||||
&hosts,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
@@ -464,13 +452,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
// cleanup(pkg_id, Some(prev)).await?;
|
||||
if let Some(res) = manifest
|
||||
.migrations
|
||||
.from(
|
||||
&prev_manifest.version,
|
||||
pkg_id,
|
||||
version,
|
||||
&manifest.volumes,
|
||||
&hosts,
|
||||
)
|
||||
.from(&prev_manifest.version, pkg_id, version, &manifest.volumes)
|
||||
.await?
|
||||
{
|
||||
configured &= res.configured;
|
||||
@@ -479,7 +461,6 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
crate::config::configure(
|
||||
&mut tx,
|
||||
&ctx.docker,
|
||||
&hosts,
|
||||
pkg_id,
|
||||
None,
|
||||
&None,
|
||||
@@ -492,16 +473,6 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Install {}@{}: Syncing Tor", pkg_id, version);
|
||||
ctx.tor_controller.sync(&mut tx, &mut sql_tx).await?;
|
||||
log::info!("Install {}@{}: Synced Tor", pkg_id, version);
|
||||
#[cfg(feature = "avahi")]
|
||||
{
|
||||
log::info!("Install {}@{}: Syncing MDNS", pkg_id, version);
|
||||
ctx.mdns_controller.sync(&mut tx).await?;
|
||||
log::info!("Install {}@{}: Synced MDNS", pkg_id, version);
|
||||
}
|
||||
|
||||
tx.commit(None).await?;
|
||||
|
||||
log::info!("Install {}@{}: Complete", pkg_id, version);
|
||||
|
||||
@@ -27,6 +27,7 @@ pub mod error;
|
||||
pub mod id;
|
||||
pub mod inspect;
|
||||
pub mod install;
|
||||
pub mod manager;
|
||||
pub mod migration;
|
||||
pub mod net;
|
||||
pub mod registry;
|
||||
|
||||
236
appmgr/src/manager/mod.rs
Normal file
236
appmgr/src/manager/mod.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use bollard::Docker;
|
||||
use patch_db::{DbHandle, PatchDbHandle};
|
||||
use sqlx::{Executor, Sqlite};
|
||||
use tokio::sync::watch::error::RecvError;
|
||||
use tokio::sync::watch::{channel, Receiver, Sender};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
|
||||
use crate::action::docker::DockerAction;
|
||||
use crate::context::RpcContext;
|
||||
use crate::net::interface::InterfaceId;
|
||||
use crate::net::mdns::MdnsController;
|
||||
use crate::net::tor::TorController;
|
||||
use crate::net::NetController;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::util::Version;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
pub struct ManagerMap(RwLock<HashMap<(PackageId, Version), Arc<Manager>>>);
|
||||
impl ManagerMap {
|
||||
pub async fn init<Db: DbHandle, Ex>(
|
||||
db: &mut Db,
|
||||
secrets: &mut Ex,
|
||||
net_ctl: &NetController,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn add(
|
||||
&self,
|
||||
docker: Docker,
|
||||
net_ctl: Arc<NetController>,
|
||||
manifest: Manifest,
|
||||
tor_keys: HashMap<InterfaceId, TorSecretKeyV3>,
|
||||
) -> Result<(), Error> {
|
||||
let mut lock = self.0.write().await;
|
||||
let id = (manifest.id.clone(), manifest.version.clone());
|
||||
if lock.contains_key(&id) {
|
||||
return Ok(());
|
||||
}
|
||||
lock.insert(
|
||||
id,
|
||||
Arc::new(Manager::create(docker, net_ctl, manifest, tor_keys).await?),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove(&self, id: &(PackageId, Version)) {
|
||||
self.0.write().await.remove(id);
|
||||
}
|
||||
|
||||
pub async fn get(&self, id: &(PackageId, Version)) -> Option<Arc<Manager>> {
|
||||
self.0.read().await.get(id).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Manager {
|
||||
on_stop: Sender<OnStop>,
|
||||
thread: JoinHandle<()>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum OnStop {
|
||||
Restart,
|
||||
Sleep,
|
||||
Exit,
|
||||
}
|
||||
|
||||
async fn run_main(
|
||||
docker: &Docker,
|
||||
net_ctl: &NetController,
|
||||
manifest: &Manifest,
|
||||
tor_keys: &HashMap<InterfaceId, TorSecretKeyV3>,
|
||||
) -> Result<Result<(), (i32, String)>, Error> {
|
||||
let rt_manifest = manifest.clone();
|
||||
let mut runtime = tokio::spawn(async move {
|
||||
rt_manifest
|
||||
.main
|
||||
.execute::<(), ()>(
|
||||
&rt_manifest.id,
|
||||
&rt_manifest.version,
|
||||
None,
|
||||
&rt_manifest.volumes,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
});
|
||||
let mut ip = None::<Ipv4Addr>;
|
||||
loop {
|
||||
match docker
|
||||
.inspect_container(&DockerAction::container_name(&manifest.id, None), None)
|
||||
.await
|
||||
{
|
||||
Ok(res) => {
|
||||
ip = res
|
||||
.network_settings
|
||||
.and_then(|ns| ns.networks)
|
||||
.and_then(|mut n| n.remove("start9"))
|
||||
.and_then(|es| es.ip_address)
|
||||
.map(|ip| ip.parse())
|
||||
.transpose()?;
|
||||
break;
|
||||
}
|
||||
Err(bollard::errors::Error::DockerResponseNotFoundError { .. }) => (),
|
||||
Err(e) => Err(e)?,
|
||||
}
|
||||
match futures::poll!(&mut runtime) {
|
||||
Poll::Ready(res) => {
|
||||
return res
|
||||
.map_err(|e| {
|
||||
Error::new(
|
||||
anyhow!("Manager runtime panicked!"),
|
||||
crate::ErrorKind::Docker,
|
||||
)
|
||||
})
|
||||
.and_then(|a| a)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let ip = ip.ok_or_else(|| {
|
||||
Error::new(
|
||||
anyhow!("inspect did not return ip"),
|
||||
crate::ErrorKind::Docker,
|
||||
)
|
||||
})?;
|
||||
|
||||
net_ctl
|
||||
.add(
|
||||
&manifest.id,
|
||||
ip,
|
||||
manifest
|
||||
.interfaces
|
||||
.0
|
||||
.iter()
|
||||
.map(|(id, info)| {
|
||||
Ok((
|
||||
id.clone(),
|
||||
info,
|
||||
tor_keys
|
||||
.get(id)
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
anyhow!("interface {} missing key", id),
|
||||
crate::ErrorKind::Tor,
|
||||
)
|
||||
})?
|
||||
.clone(),
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?,
|
||||
)
|
||||
.await?;
|
||||
let res = runtime
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::new(
|
||||
anyhow!("Manager runtime panicked!"),
|
||||
crate::ErrorKind::Docker,
|
||||
)
|
||||
})
|
||||
.and_then(|a| a);
|
||||
net_ctl.remove(&manifest.id, manifest.interfaces.0.keys().cloned());
|
||||
res
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
async fn create(
|
||||
docker: Docker,
|
||||
net_ctl: Arc<NetController>,
|
||||
manifest: Manifest,
|
||||
tor_keys: HashMap<InterfaceId, TorSecretKeyV3>,
|
||||
) -> Result<Self, Error> {
|
||||
let (on_stop, mut recv) = channel(OnStop::Sleep);
|
||||
let thread = tokio::spawn(async move {
|
||||
loop {
|
||||
fn handle_stop_action<'a>(
|
||||
recv: &'a mut Receiver<OnStop>,
|
||||
) -> (
|
||||
OnStop,
|
||||
Option<impl Future<Output = Result<(), RecvError>> + 'a>,
|
||||
) {
|
||||
let val = *recv.borrow_and_update();
|
||||
match val {
|
||||
OnStop::Sleep => (OnStop::Sleep, Some(recv.changed())),
|
||||
a => (a, None),
|
||||
}
|
||||
}
|
||||
let (stop_action, fut) = handle_stop_action(&mut recv);
|
||||
match stop_action {
|
||||
OnStop::Sleep => {
|
||||
if let Some(fut) = fut {
|
||||
fut.await.unwrap();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
OnStop::Exit => {
|
||||
break;
|
||||
}
|
||||
OnStop::Restart => (),
|
||||
}
|
||||
match run_main(&docker, &*net_ctl, &manifest, &tor_keys).await {
|
||||
Ok(Ok(())) => break,
|
||||
Ok(Err(e)) => {
|
||||
todo!("application crashed")
|
||||
}
|
||||
Err(e) => {
|
||||
todo!("failed to start application")
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(Manager { on_stop, thread })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Manager {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.on_stop.send(OnStop::Exit);
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,7 @@ use patch_db::HasModel;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::action::ActionImplementation;
|
||||
use crate::net::host::Hosts;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::status::health_check::HealthCheckId;
|
||||
use crate::util::Version;
|
||||
use crate::volume::Volumes;
|
||||
use crate::Error;
|
||||
@@ -25,7 +23,6 @@ impl Migrations {
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
volumes: &Volumes,
|
||||
hosts: &Hosts,
|
||||
) -> Result<Option<MigrationRes>, Error> {
|
||||
Ok(
|
||||
if let Some((_, migration)) = self
|
||||
@@ -35,7 +32,14 @@ impl Migrations {
|
||||
{
|
||||
Some(
|
||||
migration
|
||||
.execute(pkg_id, pkg_version, volumes, hosts, Some(version), false)
|
||||
.execute(
|
||||
pkg_id,
|
||||
pkg_version,
|
||||
Some("Migration"), // Migrations cannot be executed concurrently
|
||||
volumes,
|
||||
Some(version),
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.map_err(|e| {
|
||||
Error::new(anyhow!("{}", e.1), crate::ErrorKind::MigrationFailed)
|
||||
@@ -52,7 +56,6 @@ impl Migrations {
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
volumes: &Volumes,
|
||||
hosts: &Hosts,
|
||||
) -> Result<Option<MigrationRes>, Error> {
|
||||
Ok(
|
||||
if let Some((_, migration)) =
|
||||
@@ -60,7 +63,14 @@ impl Migrations {
|
||||
{
|
||||
Some(
|
||||
migration
|
||||
.execute(pkg_id, pkg_version, volumes, hosts, Some(version), false)
|
||||
.execute(
|
||||
pkg_id,
|
||||
pkg_version,
|
||||
Some("Migration"),
|
||||
volumes,
|
||||
Some(version),
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.map_err(|e| {
|
||||
Error::new(anyhow!("{}", e.1), crate::ErrorKind::MigrationFailed)
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use patch_db::DbHandle;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::{Error, HOST_IP};
|
||||
|
||||
pub const TLD: &'static str = "embassy";
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Hosts(pub IndexMap<PackageId, Ipv4Addr>);
|
||||
impl Hosts {
|
||||
pub fn docker_args(&self) -> Vec<OsString> {
|
||||
let mut res = Vec::with_capacity(self.0.len() + 1);
|
||||
res.push(format!("--add-host={}:{}", TLD, Ipv4Addr::from(HOST_IP)).into());
|
||||
for (id, ip) in &self.0 {
|
||||
res.push(format!("--add-host={}.{}:{}", id, TLD, ip).into());
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::Path;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
@@ -6,7 +5,7 @@ use serde::{Deserialize, Deserializer, Serialize};
|
||||
use sqlx::{Executor, Sqlite};
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
|
||||
use crate::db::model::{InterfaceAddressMap, InterfaceAddresses, InterfaceInfo};
|
||||
use crate::db::model::{InterfaceAddressMap, InterfaceAddresses};
|
||||
use crate::id::Id;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::Port;
|
||||
@@ -20,15 +19,11 @@ impl Interfaces {
|
||||
&self,
|
||||
secrets: &mut Ex,
|
||||
package_id: &PackageId,
|
||||
ip: Ipv4Addr,
|
||||
) -> Result<InterfaceInfo, Error>
|
||||
) -> Result<InterfaceAddressMap, Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
let mut interface_info = InterfaceInfo {
|
||||
ip,
|
||||
addresses: InterfaceAddressMap(IndexMap::new()),
|
||||
};
|
||||
let mut interface_addresses = InterfaceAddressMap(IndexMap::new());
|
||||
for (id, iface) in &self.0 {
|
||||
let mut addrs = InterfaceAddresses {
|
||||
tor_address: None,
|
||||
@@ -54,9 +49,9 @@ impl Interfaces {
|
||||
Some(format!("{}.local", onion.get_address_without_dot_onion()));
|
||||
}
|
||||
}
|
||||
interface_info.addresses.0.insert(id.clone(), addrs);
|
||||
interface_addresses.0.insert(id.clone(), addrs);
|
||||
}
|
||||
Ok(interface_info)
|
||||
Ok(interface_addresses)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,93 +1,77 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use avahi_sys::{
|
||||
self, avahi_client_free, avahi_entry_group_commit, avahi_entry_group_free,
|
||||
avahi_entry_group_reset, avahi_free, AvahiClient, AvahiEntryGroup,
|
||||
self, avahi_entry_group_commit, avahi_entry_group_free, avahi_entry_group_reset, avahi_free,
|
||||
AvahiEntryGroup,
|
||||
};
|
||||
use libc::c_void;
|
||||
use patch_db::{DbHandle, OptionModel};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::Mutex;
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
|
||||
use super::interface::InterfaceId;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::Apply;
|
||||
use crate::Error;
|
||||
|
||||
pub struct MdnsController(RwLock<MdnsControllerInner>);
|
||||
pub struct MdnsController(Mutex<MdnsControllerInner>);
|
||||
impl MdnsController {
|
||||
pub async fn init<Db: DbHandle>(db: &mut Db) -> Result<Self, Error> {
|
||||
Ok(MdnsController(RwLock::new(
|
||||
MdnsControllerInner::init(db).await?,
|
||||
)))
|
||||
pub fn init() -> Self {
|
||||
MdnsController(Mutex::new(MdnsControllerInner::init()))
|
||||
}
|
||||
pub async fn sync<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> {
|
||||
self.0.write().await.sync(db).await
|
||||
pub async fn add<'a, I: IntoIterator<Item = (InterfaceId, TorSecretKeyV3)>>(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
interfaces: I,
|
||||
) {
|
||||
self.0.lock().await.add(pkg_id, interfaces)
|
||||
}
|
||||
pub async fn remove<I: IntoIterator<Item = InterfaceId>>(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
interfaces: I,
|
||||
) {
|
||||
self.0.lock().await.remove(pkg_id, interfaces)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MdnsControllerInner {
|
||||
hostname: Vec<u8>,
|
||||
entry_group: *mut AvahiEntryGroup,
|
||||
services: HashMap<(PackageId, InterfaceId), TorSecretKeyV3>,
|
||||
}
|
||||
unsafe impl Send for MdnsControllerInner {}
|
||||
unsafe impl Sync for MdnsControllerInner {}
|
||||
|
||||
impl MdnsControllerInner {
|
||||
async fn load_services<Db: DbHandle>(&mut self, db: &mut Db) -> Result<(), Error> {
|
||||
fn load_services(&mut self) {
|
||||
unsafe {
|
||||
for app_id in crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.keys(db)
|
||||
.await?
|
||||
{
|
||||
let iface_model = if let Some(model) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&app_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.map(|i| i.interface_info().addresses())
|
||||
.apply(OptionModel::from)
|
||||
.check(db)
|
||||
.await?
|
||||
{
|
||||
model
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
for iface in iface_model.keys(db).await? {
|
||||
let lan_address = if let Some(addr) = iface_model
|
||||
.clone()
|
||||
.idx_model(&iface)
|
||||
.expect(db)
|
||||
.await?
|
||||
.lan_address()
|
||||
.get(db)
|
||||
.await?
|
||||
.to_owned()
|
||||
{
|
||||
addr
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let lan_address_ptr = std::ffi::CString::new(lan_address)
|
||||
.expect("Could not cast lan address to c string");
|
||||
let _ = avahi_sys::avahi_entry_group_add_record(
|
||||
self.entry_group,
|
||||
avahi_sys::AVAHI_IF_UNSPEC,
|
||||
avahi_sys::AVAHI_PROTO_UNSPEC,
|
||||
avahi_sys::AvahiPublishFlags_AVAHI_PUBLISH_USE_MULTICAST
|
||||
| avahi_sys::AvahiPublishFlags_AVAHI_PUBLISH_ALLOW_MULTIPLE,
|
||||
lan_address_ptr.as_ptr(),
|
||||
avahi_sys::AVAHI_DNS_CLASS_IN as u16,
|
||||
avahi_sys::AVAHI_DNS_TYPE_CNAME as u16,
|
||||
avahi_sys::AVAHI_DEFAULT_TTL,
|
||||
self.hostname.as_ptr().cast(),
|
||||
self.hostname.len(),
|
||||
);
|
||||
log::info!("Published {:?}", lan_address_ptr);
|
||||
}
|
||||
for key in self.services.values() {
|
||||
let lan_address = key
|
||||
.public()
|
||||
.get_onion_address()
|
||||
.get_address_without_dot_onion()
|
||||
+ ".local";
|
||||
let lan_address_ptr = std::ffi::CString::new(lan_address)
|
||||
.expect("Could not cast lan address to c string");
|
||||
let _ = avahi_sys::avahi_entry_group_add_record(
|
||||
self.entry_group,
|
||||
avahi_sys::AVAHI_IF_UNSPEC,
|
||||
avahi_sys::AVAHI_PROTO_UNSPEC,
|
||||
avahi_sys::AvahiPublishFlags_AVAHI_PUBLISH_USE_MULTICAST
|
||||
| avahi_sys::AvahiPublishFlags_AVAHI_PUBLISH_ALLOW_MULTIPLE,
|
||||
lan_address_ptr.as_ptr(),
|
||||
avahi_sys::AVAHI_DNS_CLASS_IN as u16,
|
||||
avahi_sys::AVAHI_DNS_TYPE_CNAME as u16,
|
||||
avahi_sys::AVAHI_DEFAULT_TTL,
|
||||
self.hostname.as_ptr().cast(),
|
||||
self.hostname.len(),
|
||||
);
|
||||
log::info!("Published {:?}", lan_address_ptr);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn init<Db: DbHandle>(db: &mut Db) -> Result<Self, Error> {
|
||||
fn init() -> Self {
|
||||
unsafe {
|
||||
// let app_list = crate::apps::list_info().await?;
|
||||
|
||||
@@ -118,22 +102,39 @@ impl MdnsControllerInner {
|
||||
hostname_buf[0] = (buflen - 8) as u8; // set the prefix length to len - 8 (leading byte, .local, nul) for the main address
|
||||
hostname_buf[buflen - 7] = 5; // set the prefix length to 5 for "local"
|
||||
|
||||
let mut ctrl = MdnsControllerInner {
|
||||
avahi_entry_group_commit(group);
|
||||
|
||||
MdnsControllerInner {
|
||||
hostname: hostname_buf,
|
||||
entry_group: group,
|
||||
};
|
||||
ctrl.load_services(db).await?;
|
||||
avahi_entry_group_commit(group);
|
||||
Ok(ctrl)
|
||||
services: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn sync<Db: DbHandle>(&mut self, db: &mut Db) -> Result<(), Error> {
|
||||
fn sync(&mut self) {
|
||||
unsafe {
|
||||
avahi_entry_group_reset(self.entry_group);
|
||||
self.load_services(db).await?;
|
||||
self.load_services();
|
||||
avahi_entry_group_commit(self.entry_group);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn add<'a, I: IntoIterator<Item = (InterfaceId, TorSecretKeyV3)>>(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
interfaces: I,
|
||||
) {
|
||||
self.services.extend(
|
||||
interfaces
|
||||
.into_iter()
|
||||
.map(|(interface_id, key)| ((pkg_id.clone(), interface_id), key)),
|
||||
);
|
||||
self.sync();
|
||||
}
|
||||
fn remove<I: IntoIterator<Item = InterfaceId>>(&self, pkg_id: &PackageId, interfaces: I) {
|
||||
for interface_id in interfaces {
|
||||
self.services.remove(&(pkg_id.clone(), interface_id));
|
||||
}
|
||||
self.sync();
|
||||
}
|
||||
}
|
||||
impl Drop for MdnsControllerInner {
|
||||
|
||||
@@ -1,63 +1,72 @@
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use id_pool::IdPool;
|
||||
use patch_db::HasModel;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
|
||||
use self::host::Hosts;
|
||||
use self::interface::{Interface, InterfaceId};
|
||||
#[cfg(feature = "avahi")]
|
||||
use self::mdns::MdnsController;
|
||||
use self::tor::TorController;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
pub mod host;
|
||||
pub mod interface;
|
||||
#[cfg(feature = "avahi")]
|
||||
pub mod mdns;
|
||||
pub mod tor;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct IpPool(IdPool);
|
||||
impl IpPool {
|
||||
pub fn new() -> Self {
|
||||
let pool = IdPool::new();
|
||||
IpPool(pool)
|
||||
pub struct NetController {
|
||||
tor: TorController,
|
||||
#[cfg(feature = "avahi")]
|
||||
mdns: MdnsController,
|
||||
// nginx: NginxController, // TODO
|
||||
}
|
||||
impl NetController {
|
||||
pub async fn init(tor_control: SocketAddr) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
tor: TorController::init(tor_control).await?,
|
||||
#[cfg(feature = "avahi")]
|
||||
mdns: MdnsController::init(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&mut self) -> Option<Ipv4Addr> {
|
||||
let id = self.0.request_id()?;
|
||||
let ip = u32::from_be_bytes(crate::HOST_IP) + id as u32;
|
||||
Some(ip.into())
|
||||
pub async fn add<
|
||||
'a,
|
||||
I: IntoIterator<Item = (InterfaceId, &'a Interface, TorSecretKeyV3)> + Clone,
|
||||
>(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
ip: Ipv4Addr,
|
||||
interfaces: I,
|
||||
) -> Result<(), Error> {
|
||||
let (tor_res, _) = tokio::join!(self.tor.add(pkg_id, ip, interfaces.clone()), {
|
||||
#[cfg(feature = "avahi")]
|
||||
let mdns_fut = self.mdns.add(
|
||||
pkg_id,
|
||||
interfaces
|
||||
.into_iter()
|
||||
.map(|(interface_id, _, key)| (interface_id, key)),
|
||||
);
|
||||
#[cfg(not(feature = "avahi"))]
|
||||
let mdns_fut = futures::future::ready(());
|
||||
mdns_fut
|
||||
},);
|
||||
tor_res?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn put(&mut self, ip: Ipv4Addr) {
|
||||
let ip = u32::from_be_bytes(ip.octets());
|
||||
let id = ip - u32::from_be_bytes(crate::HOST_IP);
|
||||
let _ = self.0.return_id(id as u16);
|
||||
}
|
||||
}
|
||||
impl Default for IpPool {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
|
||||
pub struct Network {
|
||||
pub ip_pool: IpPool,
|
||||
pub hosts: Hosts,
|
||||
}
|
||||
impl Network {
|
||||
pub fn register_host(&mut self, id: &PackageId) -> Result<Ipv4Addr, Error> {
|
||||
if let Some(exists) = self.hosts.0.get(id) {
|
||||
Ok(*exists)
|
||||
} else {
|
||||
let ip = self
|
||||
.ip_pool
|
||||
.get()
|
||||
.ok_or_else(|| anyhow!("No available IP addresses"))
|
||||
.with_kind(crate::ErrorKind::Network)?;
|
||||
self.hosts.0.insert(id.clone(), ip);
|
||||
Ok(ip)
|
||||
}
|
||||
pub async fn remove<I: IntoIterator<Item = InterfaceId> + Clone>(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
interfaces: I,
|
||||
) -> Result<(), Error> {
|
||||
let (tor_res, _) = tokio::join!(self.tor.remove(pkg_id, interfaces.clone()), {
|
||||
#[cfg(feature = "avahi")]
|
||||
let mdns_fut = self.mdns.remove(pkg_id, interfaces);
|
||||
#[cfg(not(feature = "avahi"))]
|
||||
let mdns_fut = futures::future::ready(());
|
||||
mdns_fut
|
||||
});
|
||||
tor_res?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
use std::collections::HashMap;
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::FutureExt;
|
||||
use indexmap::IndexMap;
|
||||
use patch_db::DbHandle;
|
||||
use sqlx::{Executor, Sqlite};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::Mutex;
|
||||
use torut::control::{AsyncEvent, AuthenticatedConn, ConnError};
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
|
||||
use super::interface::TorConfig;
|
||||
use super::interface::{Interface, InterfaceId, TorConfig};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::{Error, ResultExt as _};
|
||||
|
||||
fn event_handler(event: AsyncEvent<'static>) -> BoxFuture<'static, Result<(), ConnError>> {
|
||||
async move { Ok(()) }.boxed()
|
||||
}
|
||||
|
||||
pub struct TorController(RwLock<TorControllerInner>);
|
||||
pub struct TorController(Mutex<TorControllerInner>);
|
||||
impl TorController {
|
||||
pub async fn init<Db: DbHandle, Ex>(
|
||||
tor_cp: SocketAddr,
|
||||
db: &mut Db,
|
||||
secrets: &mut Ex,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
Ok(TorController(RwLock::new(
|
||||
TorControllerInner::init(tor_cp, db, secrets).await?,
|
||||
pub async fn init(tor_control: SocketAddr) -> Result<Self, Error> {
|
||||
Ok(TorController(Mutex::new(
|
||||
TorControllerInner::init(tor_control).await?,
|
||||
)))
|
||||
}
|
||||
|
||||
pub async fn sync<Db: DbHandle, Ex>(&self, db: &mut Db, secrets: &mut Ex) -> Result<(), Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
let new = TorControllerInner::get_services(db, secrets).await?;
|
||||
if &new != &self.0.read().await.services {
|
||||
self.0.write().await.sync(new).await?;
|
||||
}
|
||||
Ok(())
|
||||
pub async fn add<
|
||||
'a,
|
||||
I: IntoIterator<Item = (InterfaceId, &'a Interface, TorSecretKeyV3)> + Clone,
|
||||
>(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
ip: Ipv4Addr,
|
||||
interfaces: I,
|
||||
) -> Result<(), Error> {
|
||||
self.0.lock().await.add(pkg_id, ip, interfaces).await
|
||||
}
|
||||
|
||||
pub async fn remove<I: IntoIterator<Item = InterfaceId> + Clone>(
|
||||
&self,
|
||||
pkg_id: &PackageId,
|
||||
interfaces: I,
|
||||
) -> Result<(), Error> {
|
||||
self.0.lock().await.remove(pkg_id, interfaces).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,149 +59,70 @@ struct HiddenServiceConfig {
|
||||
|
||||
pub struct TorControllerInner {
|
||||
connection: AuthenticatedConnection,
|
||||
services: IndexMap<[u8; 64], HiddenServiceConfig>,
|
||||
services: HashMap<(PackageId, InterfaceId), TorSecretKeyV3>,
|
||||
}
|
||||
impl TorControllerInner {
|
||||
async fn get_services<Db: DbHandle, Ex>(
|
||||
db: &mut Db,
|
||||
secrets: &mut Ex,
|
||||
) -> Result<IndexMap<[u8; 64], HiddenServiceConfig>, Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
let pkg_ids = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.keys(db)
|
||||
.await?;
|
||||
let mut services = IndexMap::new();
|
||||
for pkg_id in pkg_ids {
|
||||
if let Some(installed) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&pkg_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.check(db)
|
||||
.await?
|
||||
{
|
||||
let ifaces = installed
|
||||
.clone()
|
||||
.manifest()
|
||||
.interfaces()
|
||||
.get(db)
|
||||
.await?
|
||||
.to_owned();
|
||||
for (iface_id, cfgs) in ifaces.0 {
|
||||
if let Some(tor_cfg) = cfgs.tor_config {
|
||||
if let Some(key) = sqlx::query!(
|
||||
"SELECT key FROM tor WHERE package = ? AND interface = ?",
|
||||
*pkg_id,
|
||||
*iface_id,
|
||||
)
|
||||
.fetch_optional(&mut *secrets)
|
||||
.await?
|
||||
{
|
||||
if key.key.len() != 64 {
|
||||
return Err(Error::new(
|
||||
anyhow!("Invalid key length"),
|
||||
crate::ErrorKind::Database,
|
||||
));
|
||||
}
|
||||
let mut buf = [0; 64];
|
||||
buf.clone_from_slice(&key.key);
|
||||
services.insert(
|
||||
buf,
|
||||
HiddenServiceConfig {
|
||||
ip: installed
|
||||
.clone()
|
||||
.interface_info()
|
||||
.ip()
|
||||
.get(db)
|
||||
.await?
|
||||
.to_owned(),
|
||||
cfg: tor_cfg,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(services)
|
||||
}
|
||||
|
||||
async fn add_svc(
|
||||
async fn add<'a, I: IntoIterator<Item = (InterfaceId, &'a Interface, TorSecretKeyV3)>>(
|
||||
&mut self,
|
||||
key: &TorSecretKeyV3,
|
||||
config: &HiddenServiceConfig,
|
||||
pkg_id: &PackageId,
|
||||
ip: Ipv4Addr,
|
||||
interfaces: I,
|
||||
) -> Result<(), Error> {
|
||||
self.connection
|
||||
.add_onion_v3(
|
||||
key,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
&mut config
|
||||
.cfg
|
||||
.port_mapping
|
||||
.iter()
|
||||
.map(|(external, internal)| {
|
||||
(external.0, SocketAddr::from((config.ip, internal.0)))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.iter(),
|
||||
)
|
||||
.await?;
|
||||
for (interface_id, interface, key) in interfaces {
|
||||
let id = (pkg_id.clone(), interface_id);
|
||||
match self.services.get(&id) {
|
||||
Some(k) if k != &key => {
|
||||
self.remove(pkg_id, std::iter::once(id.1.clone())).await?;
|
||||
}
|
||||
Some(_) => return Ok(()),
|
||||
None => (),
|
||||
}
|
||||
if let Some(tor_cfg) = &interface.tor_config {
|
||||
self.connection
|
||||
.add_onion_v3(
|
||||
&key,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
&mut tor_cfg
|
||||
.port_mapping
|
||||
.iter()
|
||||
.map(|(external, internal)| {
|
||||
(external.0, SocketAddr::from((ip, internal.0)))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.iter(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
self.services.insert(id, key);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn sync(
|
||||
async fn remove<I: IntoIterator<Item = InterfaceId>>(
|
||||
&mut self,
|
||||
services: IndexMap<[u8; 64], HiddenServiceConfig>,
|
||||
pkg_id: &PackageId,
|
||||
interfaces: I,
|
||||
) -> Result<(), Error> {
|
||||
for (key, new) in &services {
|
||||
let tor_key = TorSecretKeyV3::from(key.clone());
|
||||
if let Some(old) = self.services.remove(&key[..]) {
|
||||
if new != &old {
|
||||
self.connection
|
||||
.del_onion(
|
||||
&tor_key
|
||||
.public()
|
||||
.get_onion_address()
|
||||
.get_address_without_dot_onion(),
|
||||
)
|
||||
.await?;
|
||||
self.add_svc(&tor_key, new).await?;
|
||||
}
|
||||
} else {
|
||||
self.add_svc(&tor_key, new).await?;
|
||||
for interface_id in interfaces {
|
||||
if let Some(key) = self.services.remove(&(pkg_id.clone(), interface_id)) {
|
||||
self.connection
|
||||
.del_onion(
|
||||
&key.public()
|
||||
.get_onion_address()
|
||||
.get_address_without_dot_onion(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
for (key, _) in self.services.drain(..) {
|
||||
self.connection
|
||||
.del_onion(
|
||||
&TorSecretKeyV3::from(key)
|
||||
.public()
|
||||
.get_onion_address()
|
||||
.get_address_without_dot_onion(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
self.services = services;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn init<Db: DbHandle, Ex>(
|
||||
tor_cp: SocketAddr,
|
||||
db: &mut Db,
|
||||
secrets: &mut Ex,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
async fn init(tor_control: SocketAddr) -> Result<Self, Error> {
|
||||
let mut conn = torut::control::UnauthenticatedConn::new(
|
||||
TcpStream::connect(tor_cp).await?, // TODO
|
||||
TcpStream::connect(tor_control).await?, // TODO
|
||||
);
|
||||
let auth = conn
|
||||
.load_protocol_info()
|
||||
@@ -212,12 +133,10 @@ impl TorControllerInner {
|
||||
conn.authenticate(&auth).await?;
|
||||
let mut connection: AuthenticatedConnection = conn.into_authenticated().await;
|
||||
connection.set_async_event_handler(Some(event_handler));
|
||||
let mut res = TorControllerInner {
|
||||
Ok(TorControllerInner {
|
||||
connection,
|
||||
services: IndexMap::new(),
|
||||
};
|
||||
res.sync(Self::get_services(db, secrets).await?).await?;
|
||||
Ok(res)
|
||||
services: HashMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,9 @@ use url::Url;
|
||||
use crate::action::{ActionImplementation, Actions};
|
||||
use crate::backup::BackupActions;
|
||||
use crate::config::action::ConfigActions;
|
||||
use crate::db::model::InterfaceInfo;
|
||||
use crate::dependencies::Dependencies;
|
||||
use crate::id::{Id, InvalidId, SYSTEM_ID};
|
||||
use crate::migration::Migrations;
|
||||
use crate::net::host::Hosts;
|
||||
use crate::net::interface::Interfaces;
|
||||
use crate::status::health_check::{HealthCheckResult, HealthChecks};
|
||||
use crate::util::Version;
|
||||
@@ -33,6 +31,11 @@ impl FromStr for PackageId {
|
||||
Ok(PackageId(Id::try_from(s.to_owned())?))
|
||||
}
|
||||
}
|
||||
impl<S: AsRef<str>> From<Id<S>> for PackageId<S> {
|
||||
fn from(id: Id<S>) -> Self {
|
||||
PackageId(id)
|
||||
}
|
||||
}
|
||||
impl<S: AsRef<str>> std::ops::Deref for PackageId<S> {
|
||||
type Target = S;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
||||
@@ -6,7 +6,6 @@ use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::action::ActionImplementation;
|
||||
use crate::id::Id;
|
||||
use crate::net::host::Hosts;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::Version;
|
||||
use crate::volume::Volumes;
|
||||
@@ -51,13 +50,12 @@ impl HealthChecks {
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
volumes: &Volumes,
|
||||
hosts: &Hosts,
|
||||
) -> Result<IndexMap<HealthCheckId, HealthCheckResult>, Error> {
|
||||
let res = futures::future::try_join_all(self.0.iter().map(|(id, check)| async move {
|
||||
Ok::<_, Error>((
|
||||
id.clone(),
|
||||
check
|
||||
.check(started, pkg_id, pkg_version, volumes, hosts)
|
||||
.check(id, started, pkg_id, pkg_version, volumes)
|
||||
.await?,
|
||||
))
|
||||
}))
|
||||
@@ -75,15 +73,22 @@ pub struct HealthCheck {
|
||||
impl HealthCheck {
|
||||
pub async fn check(
|
||||
&self,
|
||||
id: &HealthCheckId,
|
||||
started: &DateTime<Utc>,
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
volumes: &Volumes,
|
||||
hosts: &Hosts,
|
||||
) -> Result<HealthCheckResult, Error> {
|
||||
let res = self
|
||||
.implementation
|
||||
.execute(pkg_id, pkg_version, volumes, hosts, Some(started), true)
|
||||
.execute(
|
||||
pkg_id,
|
||||
pkg_version,
|
||||
Some(&format!("{}Health", id)),
|
||||
volumes,
|
||||
Some(started),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
Ok(HealthCheckResult {
|
||||
time: Utc::now(),
|
||||
|
||||
@@ -18,7 +18,6 @@ use crate::db::model::{
|
||||
CurrentDependencyInfo, InstalledPackageDataEntryModel, PackageDataEntryModel,
|
||||
};
|
||||
use crate::dependencies::{Dependencies, DependencyError};
|
||||
use crate::net::host::Hosts;
|
||||
use crate::net::interface::InterfaceId;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::status::health_check::HealthCheckResultVariant;
|
||||
@@ -46,7 +45,7 @@ pub async fn synchronize_all(ctx: &RpcContext) -> Result<(), Error> {
|
||||
.get(&mut db)
|
||||
.await?
|
||||
{
|
||||
container_names.push(DockerAction::container_name(id.as_ref(), version));
|
||||
container_names.push(DockerAction::container_name(id.as_ref(), None));
|
||||
} else {
|
||||
pkg_ids.remove(&id);
|
||||
}
|
||||
@@ -168,12 +167,11 @@ pub async fn check_all(ctx: &RpcContext) -> Result<(), Error> {
|
||||
async fn main_status<Db: DbHandle>(
|
||||
status_model: StatusModel,
|
||||
manifest: Arc<ModelData<Manifest>>,
|
||||
hosts: Arc<Hosts>,
|
||||
mut db: Db,
|
||||
) -> Result<MainStatus, Error> {
|
||||
let mut status = status_model.get_mut(&mut db).await?;
|
||||
|
||||
status.main.check(&*manifest, &*hosts).await?;
|
||||
status.main.check(&*manifest).await?;
|
||||
|
||||
let res = status.main.clone();
|
||||
|
||||
@@ -192,7 +190,7 @@ pub async fn check_all(ctx: &RpcContext) -> Result<(), Error> {
|
||||
.for_each_concurrent(None, move |(((status, manifest), id), hosts)| {
|
||||
let status_sender = status_sender.clone();
|
||||
async move {
|
||||
match tokio::spawn(main_status(status, manifest, hosts, ctx.db.handle()))
|
||||
match tokio::spawn(main_status(status, manifest, ctx.db.handle()))
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
@@ -292,7 +290,7 @@ impl MainStatus {
|
||||
.and_then(|s| s.status)
|
||||
== Some(ContainerStateStatusEnum::RUNNING))
|
||||
}
|
||||
let name = DockerAction::container_name(&manifest.id, &manifest.version);
|
||||
let name = DockerAction::container_name(&manifest.id, None);
|
||||
let state = summary.state.as_ref().map(|s| s.as_str());
|
||||
match state {
|
||||
Some("created") | Some("exited") => match self {
|
||||
@@ -343,18 +341,12 @@ impl MainStatus {
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
pub async fn check(&mut self, manifest: &Manifest, hosts: &Hosts) -> Result<(), Error> {
|
||||
pub async fn check(&mut self, manifest: &Manifest) -> Result<(), Error> {
|
||||
match self {
|
||||
MainStatus::Running { started, health } => {
|
||||
*health = manifest
|
||||
.health_checks
|
||||
.check_all(
|
||||
started,
|
||||
&manifest.id,
|
||||
&manifest.version,
|
||||
&manifest.volumes,
|
||||
hosts,
|
||||
)
|
||||
.check_all(started, &manifest.id, &manifest.version, &manifest.volumes)
|
||||
.await?;
|
||||
for (check, res) in health {
|
||||
if matches!(
|
||||
|
||||
@@ -878,6 +878,7 @@ impl<'de> Deserialize<'de> for Port {
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
//TODO: if number, be permissive
|
||||
deserialize_from_str(deserializer).map(Port)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user