1
0
Fork 0
mirror of https://codeberg.org/icewind/haze.git synced 2026-06-03 17:14:08 +02:00

sharding option

This commit is contained in:
Robin Appelman 2024-07-03 16:20:07 +02:00
commit ce0d3ff3e4
19 changed files with 248 additions and 121 deletions

View file

@ -14,6 +14,7 @@ use flate2::read::GzDecoder;
use futures_util::future::try_join_all; use futures_util::future::try_join_all;
use miette::{IntoDiagnostic, Report, Result, WrapErr}; use miette::{IntoDiagnostic, Report, Result, WrapErr};
use petname::petname; use petname::petname;
use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use std::fs; use std::fs;
@ -27,15 +28,14 @@ use tokio::fs::create_dir_all;
use tokio::fs::remove_dir_all; use tokio::fs::remove_dir_all;
use tokio::task::spawn; use tokio::task::spawn;
use tokio::time::sleep; use tokio::time::sleep;
use toml::Value;
#[derive(Clone, Default, Debug, Eq, PartialEq)] #[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct CloudOptions { pub struct CloudOptions {
name: Option<String>, pub name: Option<String>,
db: Database, pub db: Database,
php: PhpVersion, pub php: PhpVersion,
services: Vec<Service>, pub services: Vec<Service>,
app_packages: Vec<Utf8PathBuf>, pub app_packages: Vec<Utf8PathBuf>,
} }
impl CloudOptions { impl CloudOptions {
@ -176,11 +176,9 @@ pub struct Cloud {
pub id: String, pub id: String,
pub network: String, pub network: String,
pub containers: Vec<String>, pub containers: Vec<String>,
pub php: PhpVersion,
pub db: Database,
pub ip: Option<IpAddr>, pub ip: Option<IpAddr>,
pub workdir: Utf8PathBuf, pub workdir: Utf8PathBuf,
pub services: Vec<Service>, pub options: CloudOptions,
pub pinned: bool, pub pinned: bool,
pub address: String, pub address: String,
pub preset_config: HashMap<String, Value>, pub preset_config: HashMap<String, Value>,
@ -194,6 +192,7 @@ impl Cloud {
) -> Result<Self> { ) -> Result<Self> {
let id = options let id = options
.name .name
.as_deref()
.map(|name| format!("haze-{}", name)) .map(|name| format!("haze-{}", name))
.unwrap_or_else(|| format!("haze-{}", petname(2, "-").unwrap())); .unwrap_or_else(|| format!("haze-{}", petname(2, "-").unwrap()));
@ -209,12 +208,12 @@ impl Cloud {
let app_volumes = options let app_volumes = options
.app_packages .app_packages
.into_iter() .iter()
.map(|app_package| { .map(|app_package| {
let app_name = app_package.file_name().unwrap().trim_end_matches(".tar.gz"); let app_name = app_package.file_name().unwrap().trim_end_matches(".tar.gz");
let app_dir = app_package_dir.join(app_name); let app_dir = app_package_dir.join(app_name);
let app_package_file = fs::File::open(&app_package) let app_package_file = fs::File::open(app_package)
.into_diagnostic() .into_diagnostic()
.wrap_err_with(|| format!("Failed to open app bundle {}", app_package))?; .wrap_err_with(|| format!("Failed to open app bundle {}", app_package))?;
if app_package.metadata().into_diagnostic()?.len() > 1024 * 1024 { if app_package.metadata().into_diagnostic()?.len() > 1024 * 1024 {
@ -296,7 +295,7 @@ impl Cloud {
if let Some(db_name) = options if let Some(db_name) = options
.db .db
.spawn(docker, &id, &network) .spawn(docker, &id, &network, "")
.await .await
.wrap_err("Failed to start database")? .wrap_err("Failed to start database")?
{ {
@ -315,7 +314,7 @@ impl Cloud {
options options
.services .services
.iter() .iter()
.map(|service| service.spawn(docker, &id, &network, config)), .map(|service| service.spawn(docker, &id, &network, config, &options)),
) )
.await?; .await?;
containers.extend(service_containers.iter().flatten().cloned()); containers.extend(service_containers.iter().flatten().cloned());
@ -395,21 +394,20 @@ impl Cloud {
containers.push(container); containers.push(container);
let services_clone = options.services.clone(); let options_clone = options.clone();
let cloud_id = id.clone(); let cloud_id = id.clone();
let docker_clone = docker.clone(); let docker_clone = docker.clone();
spawn(async move { spawn(async move {
if let Err(e) = try_join_all( if let Err(e) =
services_clone try_join_all(options_clone.services.iter().map(|service| {
.iter() service.wait_for_start(&docker_clone, &cloud_id, &options_clone)
.map(|service| service.wait_for_start(&docker_clone, &cloud_id)), }))
) .await
.await
{ {
println!("{:#}", e); println!("{:#}", e);
return; return;
} }
for service in services_clone { for service in options_clone.services {
match service.start_message(&docker_clone, &cloud_id).await { match service.start_message(&docker_clone, &cloud_id).await {
Ok(Some(msg)) => { Ok(Some(msg)) => {
println!("{}", msg); println!("{}", msg);
@ -428,11 +426,9 @@ impl Cloud {
id, id,
network, network,
containers, containers,
php: options.php,
db: options.db,
ip: Some(ip), ip: Some(ip),
workdir, workdir,
services: options.services, options,
pinned: false, pinned: false,
address, address,
preset_config, preset_config,
@ -610,12 +606,16 @@ impl Cloud {
Cloud { Cloud {
id, id,
network, network,
db,
php,
containers: service_ids, containers: service_ids,
ip, ip,
workdir, workdir,
services: found_services, options: CloudOptions {
name: None,
php,
db,
services: found_services,
app_packages: vec![],
},
pinned, pinned,
address, address,
preset_config: HashMap::default(), preset_config: HashMap::default(),
@ -645,18 +645,19 @@ impl Cloud {
} }
pub async fn wait_for_start(&self, docker: &Docker) -> Result<()> { pub async fn wait_for_start(&self, docker: &Docker) -> Result<()> {
self.php self.options
.php
.wait_for_start(self.ip) .wait_for_start(self.ip)
.await .await
.wrap_err("Failed to wait for php container")?; .wrap_err("Failed to wait for php container")?;
self.db self.options
.db
.wait_for_start(docker, &self.id) .wait_for_start(docker, &self.id)
.await .await
.wrap_err("Failed to wait for database container")?; .wrap_err("Failed to wait for database container")?;
try_join_all( try_join_all(
self.services self.services()
.iter() .map(|service| service.wait_for_start(docker, &self.id, &self.options)),
.map(|service| service.wait_for_start(docker, &self.id)),
) )
.await .await
.wrap_err("Failed to wait for service containers")?; .wrap_err("Failed to wait for service containers")?;
@ -706,4 +707,16 @@ impl Cloud {
.await .await
.into_diagnostic() .into_diagnostic()
} }
pub fn services(&self) -> impl Iterator<Item = &Service> {
self.options.services.iter()
}
pub fn db(&self) -> &Database {
&self.options.db
}
pub fn php(&self) -> &PhpVersion {
&self.options.php
}
} }

View file

@ -177,6 +177,7 @@ impl Database {
docker: &Docker, docker: &Docker,
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
postfix: &str,
) -> Result<Option<String>> { ) -> Result<Option<String>> {
if matches!(self, Database::Sqlite) { if matches!(self, Database::Sqlite) {
return Ok(None); return Ok(None);
@ -191,7 +192,7 @@ impl Database {
.wrap_err("Failed to pull database image")?; .wrap_err("Failed to pull database image")?;
} }
let options = Some(CreateContainerOptions { let options = Some(CreateContainerOptions {
name: format!("{}-db", cloud_id), name: format!("{}-db{}", cloud_id, postfix),
..CreateContainerOptions::default() ..CreateContainerOptions::default()
}); });
let config = Config { let config = Config {
@ -208,7 +209,10 @@ impl Database {
networking_config: Some(NetworkingConfig { networking_config: Some(NetworkingConfig {
endpoints_config: hashmap! { endpoints_config: hashmap! {
network => EndpointSettings { network => EndpointSettings {
aliases: Some(vec![self.name().to_string()]), aliases: Some(vec![
format!("{}{}", self.name(), postfix),
format!("db{}", postfix),
]),
..Default::default() ..Default::default()
} }
}, },
@ -320,7 +324,7 @@ impl Database {
}; };
timeout(Duration::from_secs(time), async { timeout(Duration::from_secs(time), async {
while !self.is_healthy(docker, cloud_id).await? { while !self.is_healthy(docker, cloud_id, "").await? {
sleep(Duration::from_millis(250)).await sleep(Duration::from_millis(250)).await
} }
Result::<(), Report>::Ok(()) Result::<(), Report>::Ok(())
@ -348,14 +352,14 @@ impl Database {
} }
} }
async fn is_healthy(&self, docker: &Docker, cloud_id: &str) -> Result<bool> { pub async fn is_healthy(&self, docker: &Docker, cloud_id: &str, postfix: &str) -> Result<bool> {
match self.family() { match self.family() {
DatabaseFamily::Sqlite => Ok(true), DatabaseFamily::Sqlite => Ok(true),
DatabaseFamily::Mysql | DatabaseFamily::MariaDB => { DatabaseFamily::Mysql | DatabaseFamily::MariaDB => {
let mut output = Vec::new(); let mut output = Vec::new();
exec( exec(
docker, docker,
format!("{}-db", cloud_id), format!("{}-db{}", cloud_id, postfix),
"root", "root",
vec!["mysql", "-u", "haze", "-phaze", "-e", "SELECT 1"], vec!["mysql", "-u", "haze", "-phaze", "-e", "SELECT 1"],
Vec::<String>::default(), Vec::<String>::default(),
@ -368,7 +372,7 @@ impl Database {
DatabaseFamily::Postgres => { DatabaseFamily::Postgres => {
let is_ready_status = exec( let is_ready_status = exec(
docker, docker,
format!("{}-db", cloud_id), format!("{}-db{}", cloud_id, postfix),
"root", "root",
vec!["pg_isready", "-U", "haze", "-q"], vec!["pg_isready", "-U", "haze", "-q"],
Vec::<String>::default(), Vec::<String>::default(),
@ -378,7 +382,7 @@ impl Database {
if is_ready_status == 0 { if is_ready_status == 0 {
let connect_status = exec( let connect_status = exec(
docker, docker,
format!("{}-db", cloud_id), format!("{}-db{}", cloud_id, postfix),
"root", "root",
vec!["psql", "-U", "haze", "-qtA", "-c", ""], vec!["psql", "-U", "haze", "-qtA", "-c", ""],
Vec::<String>::default(), Vec::<String>::default(),
@ -394,7 +398,7 @@ impl Database {
let mut output = Vec::new(); let mut output = Vec::new();
exec( exec(
docker, docker,
format!("{}-db", cloud_id), format!("{}-db{}", cloud_id, postfix),
"root", "root",
vec!["sh", "-c", r#"echo "show user" | sqlplus -S system/haze"#], vec!["sh", "-c", r#"echo "show user" | sqlplus -S system/haze"#],
Vec::<String>::default(), Vec::<String>::default(),

View file

@ -64,15 +64,15 @@ async fn main() -> Result<()> {
HazeArgs::List { filter } => { HazeArgs::List { filter } => {
let list = Cloud::list(&docker, filter, &config).await?; let list = Cloud::list(&docker, filter, &config).await?;
for cloud in list { for cloud in list {
let mut services: Vec<_> = cloud.services.iter().map(Service::name).collect(); let mut services: Vec<_> = cloud.services().map(Service::name).collect();
services.push(cloud.db.name()); services.push(cloud.db().name());
let services = services.join(", "); let services = services.join(", ");
let pin = if cloud.pinned { "*" } else { "" }; let pin = if cloud.pinned { "*" } else { "" };
println!( println!(
"Cloud {}{}, {}, {}, running on {}", "Cloud {}{}, {}, {}, running on {}",
cloud.id, cloud.id,
pin, pin,
cloud.php.name(), cloud.php().name(),
services, services,
cloud.address cloud.address
); );
@ -124,7 +124,7 @@ async fn main() -> Result<()> {
} }
Some(ExecService::Db) => { Some(ExecService::Db) => {
cloud cloud
.db .db()
.exec_sh( .exec_sh(
&docker, &docker,
&cloud.id, &cloud.id,
@ -156,7 +156,7 @@ async fn main() -> Result<()> {
} }
HazeArgs::Db { filter, root } => { HazeArgs::Db { filter, root } => {
let cloud = Cloud::get_by_filter(&docker, filter, &config).await?; let cloud = Cloud::get_by_filter(&docker, filter, &config).await?;
cloud.db.exec(&docker, &cloud.id, root).await?; cloud.db().exec(&docker, &cloud.id, root).await?;
} }
HazeArgs::Open { filter } => { HazeArgs::Open { filter } => {
let cloud = Cloud::get_by_filter(&docker, filter, &config).await?; let cloud = Cloud::get_by_filter(&docker, filter, &config).await?;
@ -342,7 +342,7 @@ async fn main() -> Result<()> {
let ip = cloud let ip = cloud
.ip .ip
.ok_or_else(|| Report::msg(format!("{} is not running", cloud.id)))?; .ok_or_else(|| Report::msg(format!("{} is not running", cloud.id)))?;
let db_type = match cloud.db.family() { let db_type = match cloud.db().family() {
DatabaseFamily::Sqlite => { DatabaseFamily::Sqlite => {
return Err(Report::msg("sqlite is not supported with `haze env`")) return Err(Report::msg("sqlite is not supported with `haze env`"))
} }
@ -353,7 +353,7 @@ async fn main() -> Result<()> {
DatabaseFamily::Postgres => "postgresql", DatabaseFamily::Postgres => "postgresql",
}; };
let db_ip = cloud let db_ip = cloud
.db .db()
.ip(&docker, &cloud.id) .ip(&docker, &cloud.id)
.await .await
.ok_or_else(|| Report::msg(format!("{}-db is not running", cloud.id)))?; .ok_or_else(|| Report::msg(format!("{}-db is not running", cloud.id)))?;
@ -459,7 +459,7 @@ async fn setup(docker: &Docker, options: CloudOptions, config: &HazeConfig) -> R
.await?; .await?;
} }
for service in &cloud.services { for service in cloud.services() {
for app in service.apps() { for app in service.apps() {
cloud cloud
.exec( .exec(
@ -471,7 +471,7 @@ async fn setup(docker: &Docker, options: CloudOptions, config: &HazeConfig) -> R
.await?; .await?;
} }
} }
for service in &cloud.services { for service in cloud.services() {
for cmd in service.post_setup(docker, &cloud.id, config).await? { for cmd in service.post_setup(docker, &cloud.id, config).await? {
cloud cloud
.exec( .exec(

View file

@ -53,8 +53,7 @@ impl ActiveInstances {
.await .await
.ok()?; .ok()?;
let service = cloud let service = cloud
.services .services()
.iter()
.find(|service| service.name() == service_name)?; .find(|service| service.name() == service_name)?;
let ip = service.get_ip(&self.docker, &cloud.id).await.ok()??; let ip = service.get_ip(&self.docker, &cloud.id).await.ok()??;
SocketAddr::new(ip, service.proxy_port()) SocketAddr::new(ip, service.proxy_port())

View file

@ -10,8 +10,11 @@ mod office;
mod onlyoffice; mod onlyoffice;
mod push; mod push;
mod sftp; mod sftp;
// mod sharding;
mod sharded;
mod smb; mod smb;
use crate::cloud::CloudOptions;
use crate::config::{HazeConfig, Preset}; use crate::config::{HazeConfig, Preset};
pub use crate::service::clam::{ClamIcap, ClamIcapTls}; pub use crate::service::clam::{ClamIcap, ClamIcapTls};
use crate::service::dav::Dav; use crate::service::dav::Dav;
@ -25,16 +28,17 @@ pub use crate::service::office::Office;
pub use crate::service::onlyoffice::OnlyOffice; pub use crate::service::onlyoffice::OnlyOffice;
pub use crate::service::push::NotifyPush; pub use crate::service::push::NotifyPush;
use crate::service::sftp::Sftp; use crate::service::sftp::Sftp;
use crate::service::sharded::Sharding;
use crate::service::smb::Smb; use crate::service::smb::Smb;
use bollard::models::ContainerState; use bollard::models::ContainerState;
use bollard::Docker; use bollard::Docker;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use miette::{IntoDiagnostic, Report, Result, WrapErr}; use miette::{IntoDiagnostic, Report, Result, WrapErr};
use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
use std::net::IpAddr; use std::net::IpAddr;
use std::time::Duration; use std::time::Duration;
use tokio::time::{sleep, timeout}; use tokio::time::{sleep, timeout};
use toml::Value;
#[async_trait::async_trait] #[async_trait::async_trait]
#[enum_dispatch(Service)] #[enum_dispatch(Service)]
@ -51,25 +55,18 @@ pub trait ServiceTrait {
_cloud_id: &str, _cloud_id: &str,
_network: &str, _network: &str,
_config: &HazeConfig, _config: &HazeConfig,
) -> Result<Option<String>> { _options: &CloudOptions,
Ok(None) ) -> Result<Vec<String>> {
Ok(Vec::new())
} }
async fn is_healthy(&self, docker: &Docker, cloud_id: &str) -> Result<bool> { async fn is_healthy(
let Some(container) = self.container_name(cloud_id) else { &self,
return Ok(true); _docker: &Docker,
}; _cloud_id: &str,
let info = docker _options: &CloudOptions,
.inspect_container(&container, None) ) -> Result<bool> {
.await Ok(true)
.into_diagnostic()?;
Ok(matches!(
info.state,
Some(ContainerState {
running: Some(true),
..
})
))
} }
fn container_name(&self, _cloud_id: &str) -> Option<String> { fn container_name(&self, _cloud_id: &str) -> Option<String> {
@ -189,6 +186,7 @@ pub enum Service {
Push(NotifyPush), Push(NotifyPush),
Smb(Smb), Smb(Smb),
Dav(Dav), Dav(Dav),
Sharding(Sharding),
Sftp(Sftp), Sftp(Sftp),
Kaspersky(Kaspersky), Kaspersky(Kaspersky),
KasperskyIcap(KasperskyIcap), KasperskyIcap(KasperskyIcap),
@ -212,6 +210,8 @@ impl Service {
"office" => Some(vec![Service::Office(Office)]), "office" => Some(vec![Service::Office(Office)]),
"push" => Some(vec![Service::Push(NotifyPush)]), "push" => Some(vec![Service::Push(NotifyPush)]),
"smb" => Some(vec![Service::Smb(Smb)]), "smb" => Some(vec![Service::Smb(Smb)]),
"sharded" => Some(vec![Service::Sharding(Sharding)]),
"sharding" => Some(vec![Service::Sharding(Sharding)]),
"dav" => Some(vec![Service::Dav(Dav)]), "dav" => Some(vec![Service::Dav(Dav)]),
"sftp" => Some(vec![Service::Sftp(Sftp)]), "sftp" => Some(vec![Service::Sftp(Sftp)]),
"oc" => Some(vec![Service::Oc(Oc)]), "oc" => Some(vec![Service::Oc(Oc)]),
@ -231,9 +231,14 @@ impl Service {
} }
} }
pub async fn wait_for_start(&self, docker: &Docker, cloud_id: &str) -> Result<()> { pub async fn wait_for_start(
&self,
docker: &Docker,
cloud_id: &str,
options: &CloudOptions,
) -> Result<()> {
timeout(Duration::from_secs(30), async { timeout(Duration::from_secs(30), async {
while !self.is_healthy(docker, cloud_id).await? { while !self.is_healthy(docker, cloud_id, options).await? {
sleep(Duration::from_millis(100)).await sleep(Duration::from_millis(100)).await
} }
Ok(()) Ok(())
@ -265,7 +270,12 @@ impl ServiceTrait for PresetService {
) -> Result<HashMap<String, Value>> { ) -> Result<HashMap<String, Value>> {
let preset = let preset =
get_preset(&config.preset, &self.0).ok_or_else(|| Report::msg("invalid preset"))?; get_preset(&config.preset, &self.0).ok_or_else(|| Report::msg("invalid preset"))?;
Ok(preset.config.clone()) let config = preset
.config
.iter()
.map(|(k, v)| Ok((k.clone(), serde_json::to_value(v).into_diagnostic()?)))
.collect::<Result<HashMap<_, _>>>()?;
Ok(config)
} }
async fn post_setup( async fn post_setup(

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::exec::exec; use crate::exec::exec;
use crate::image::pull_image; use crate::image::pull_image;
@ -34,6 +35,7 @@ impl ServiceTrait for ClamIcap {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "ghcr.io/icewind1991/icap-clamav-service-tls"; let image = "ghcr.io/icewind1991/icap-clamav-service-tls";
pull_image(docker, image).await?; pull_image(docker, image).await?;
@ -122,6 +124,7 @@ impl ServiceTrait for ClamIcapTls {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "ghcr.io/icewind1991/icap-clamav-service-tls"; let image = "ghcr.io/icewind1991/icap-clamav-service-tls";
pull_image(docker, image).await?; pull_image(docker, image).await?;

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
@ -23,6 +24,7 @@ impl ServiceTrait for Dav {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "ugeek/webdav:amd64"; let image = "ugeek/webdav:amd64";
pull_image(docker, image).await?; pull_image(docker, image).await?;
@ -71,11 +73,6 @@ impl ServiceTrait for Dav {
&["files_external"] &["files_external"]
} }
// no need to wait for dav, as it won't be used until the user logs in
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup( async fn post_setup(
&self, &self,
_docker: &Docker, _docker: &Docker,

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
@ -23,6 +24,7 @@ impl ServiceTrait for Imaginary {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "nextcloud/aio-imaginary:latest"; let image = "nextcloud/aio-imaginary:latest";
pull_image(docker, image).await?; pull_image(docker, image).await?;
@ -66,11 +68,6 @@ impl ServiceTrait for Imaginary {
Some(format!("{}-imaginary", cloud_id)) Some(format!("{}-imaginary", cloud_id))
} }
// no need to wait for imaginary, as it won't be used until the user logs in
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup( async fn post_setup(
&self, &self,
_docker: &Docker, _docker: &Docker,

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::exec::exec; use crate::exec::exec;
use crate::image::{image_exists, pull_image}; use crate::image::{image_exists, pull_image};
@ -29,6 +30,7 @@ impl ServiceTrait for Kaspersky {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "kaspersky"; let image = "kaspersky";
if !image_exists(docker, image).await { if !image_exists(docker, image).await {
@ -71,7 +73,12 @@ impl ServiceTrait for Kaspersky {
Ok(vec![id]) Ok(vec![id])
} }
async fn is_healthy(&self, docker: &Docker, cloud_id: &str) -> Result<bool> { async fn is_healthy(
&self,
docker: &Docker,
cloud_id: &str,
_options: &CloudOptions,
) -> Result<bool> {
let exit = exec( let exit = exec(
docker, docker,
self.container_name(cloud_id).unwrap(), self.container_name(cloud_id).unwrap(),
@ -130,6 +137,7 @@ impl ServiceTrait for KasperskyIcap {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "kaspersky-icap"; let image = "kaspersky-icap";
if !image_exists(docker, image).await { if !image_exists(docker, image).await {
@ -172,19 +180,6 @@ impl ServiceTrait for KasperskyIcap {
Ok(vec![id]) Ok(vec![id])
} }
// async fn is_healthy(&self, docker: &Docker, cloud_id: &str) -> Result<bool> {
// let exit = exec(
// docker,
// self.container_name(cloud_id),
// "root",
// vec!["curl", "localhost/licenseinfo"],
// vec![],
// Option::<Stdout>::None,
// )
// .await?;
// Ok(exit.to_result().is_ok())
// }
fn container_name(&self, cloud_id: &str) -> Option<String> { fn container_name(&self, cloud_id: &str) -> Option<String> {
Some(format!("{}-kaspersky-icap", cloud_id)) Some(format!("{}-kaspersky-icap", cloud_id))
} }

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
@ -27,6 +28,7 @@ impl ServiceTrait for Ldap {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "icewind1991/haze-ldap"; let image = "icewind1991/haze-ldap";
pull_image(docker, image).await?; pull_image(docker, image).await?;
@ -75,6 +77,15 @@ impl ServiceTrait for Ldap {
fn apps(&self) -> &'static [&'static str] { fn apps(&self) -> &'static [&'static str] {
&["user_ldap"] &["user_ldap"]
} }
async fn is_healthy(
&self,
docker: &Docker,
cloud_id: &str,
_options: &CloudOptions,
) -> Result<bool> {
self.is_running(docker, cloud_id).await
}
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -96,6 +107,7 @@ impl ServiceTrait for LdapAdmin {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "osixia/phpldapadmin"; let image = "osixia/phpldapadmin";
pull_image(docker, image).await?; pull_image(docker, image).await?;

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
@ -23,6 +24,7 @@ impl ServiceTrait for Mail {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "rnwood/smtp4dev"; let image = "rnwood/smtp4dev";
pull_image(docker, image).await?; pull_image(docker, image).await?;
@ -66,11 +68,6 @@ impl ServiceTrait for Mail {
Some(format!("{}-mail", cloud_id)) Some(format!("{}-mail", cloud_id))
} }
// no need to wait for mail, as it won't be used until the user logs in
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup( async fn post_setup(
&self, &self,
_docker: &Docker, _docker: &Docker,

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::exec::exec; use crate::exec::exec;
use crate::image::pull_image; use crate::image::pull_image;
@ -77,6 +78,7 @@ impl ServiceTrait for ObjectStore {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
pull_image(docker, self.image()).await?; pull_image(docker, self.image()).await?;
let options = Some(CreateContainerOptions { let options = Some(CreateContainerOptions {
@ -117,7 +119,15 @@ impl ServiceTrait for ObjectStore {
Ok(vec![id]) Ok(vec![id])
} }
async fn is_healthy(&self, docker: &Docker, cloud_id: &str) -> Result<bool> { async fn is_healthy(
&self,
docker: &Docker,
cloud_id: &str,
_options: &CloudOptions,
) -> Result<bool> {
if !self.is_running(docker, cloud_id).await? {
return Ok(false);
}
match self { match self {
ObjectStore::S3 | ObjectStore::S3mb => { ObjectStore::S3 | ObjectStore::S3mb => {
let mut output = Vec::new(); let mut output = Vec::new();

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::exec::exec; use crate::exec::exec;
use crate::image::pull_image; use crate::image::pull_image;
@ -27,6 +28,7 @@ impl ServiceTrait for Oc {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
config: &HazeConfig, config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "owncloud/server:10.12.2"; let image = "owncloud/server:10.12.2";
pull_image(docker, image).await?; pull_image(docker, image).await?;
@ -78,11 +80,6 @@ impl ServiceTrait for Oc {
Some(format!("{}-oc", cloud_id)) Some(format!("{}-oc", cloud_id))
} }
// no need to wait for oc
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup( async fn post_setup(
&self, &self,
docker: &Docker, docker: &Docker,

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
@ -27,6 +28,7 @@ impl ServiceTrait for Office {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
config: &HazeConfig, config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "collabora/code"; let image = "collabora/code";
pull_image(docker, image).await?; pull_image(docker, image).await?;

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
@ -27,6 +28,7 @@ impl ServiceTrait for OnlyOffice {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "onlyoffice/documentserver"; let image = "onlyoffice/documentserver";
pull_image(docker, image).await?; pull_image(docker, image).await?;

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
@ -26,6 +27,7 @@ impl ServiceTrait for NotifyPush {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
config: &HazeConfig, config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "icewind1991/notify_push"; let image = "icewind1991/notify_push";
pull_image(docker, image).await?; pull_image(docker, image).await?;
@ -79,10 +81,6 @@ impl ServiceTrait for NotifyPush {
&["notify_push"] &["notify_push"]
} }
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup( async fn post_setup(
&self, &self,
docker: &Docker, docker: &Docker,

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
@ -23,6 +24,7 @@ impl ServiceTrait for Sftp {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "atmoz/sftp:alpine"; let image = "atmoz/sftp:alpine";
pull_image(docker, image).await?; pull_image(docker, image).await?;
@ -71,11 +73,6 @@ impl ServiceTrait for Sftp {
&["files_external"] &["files_external"]
} }
// no need to wait for dav, as it won't be used until the user logs in
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup( async fn post_setup(
&self, &self,
_docker: &Docker, _docker: &Docker,

97
src/service/sharded.rs Normal file
View file

@ -0,0 +1,97 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::database::DatabaseFamily;
use crate::service::ServiceTrait;
use crate::Result;
use bollard::Docker;
use futures_util::future::try_join_all;
use maplit::hashmap;
use miette::Report;
use serde_json::json;
use serde_json::Value;
use std::collections::HashMap;
use std::convert::identity;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Sharding;
const SHARDS: &[&str] = &["-1", "-2"];
#[async_trait::async_trait]
impl ServiceTrait for Sharding {
fn name(&self) -> &str {
"sharding"
}
async fn spawn(
&self,
docker: &Docker,
cloud_id: &str,
network: &str,
_config: &HazeConfig,
options: &CloudOptions,
) -> Result<Vec<String>> {
if options.db.family() == DatabaseFamily::Sqlite {
return Err(Report::msg("Sharding is not supported with sqlite"));
}
let containers = try_join_all(
SHARDS
.iter()
.copied()
.map(|postfix| options.db.spawn(docker, cloud_id, network, postfix)),
)
.await?;
Ok(containers.into_iter().flatten().collect())
}
async fn is_healthy(
&self,
docker: &Docker,
cloud_id: &str,
options: &CloudOptions,
) -> Result<bool> {
let running = try_join_all(
SHARDS
.iter()
.copied()
.map(|postfix| options.db.is_healthy(docker, cloud_id, postfix)),
)
.await?;
Ok(running.iter().copied().all(identity))
}
fn config(
&self,
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<HashMap<String, Value>> {
let shard_config = json!({
"filecache": {
"table": "filecache",
"primary_key": "fileid",
"shard_key": "storage",
"companion_tables": ["filecache_extended"],
"shards": [
{
"name": "haze",
"host": "db-1",
"tableprefix": "oc_",
"user": "haze",
"password": "haze",
},
{
"name": "haze",
"host": "db-2",
"tableprefix": "oc_",
"user": "haze",
"password": "haze",
}
],
}
});
Ok(hashmap! {String::from("db.sharding") => shard_config})
}
}

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
@ -23,6 +24,7 @@ impl ServiceTrait for Smb {
cloud_id: &str, cloud_id: &str,
network: &str, network: &str,
_config: &HazeConfig, _config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
let image = "ghcr.io/servercontainers/samba:smbd-only-a3.18.0-s4.18.2-r0"; let image = "ghcr.io/servercontainers/samba:smbd-only-a3.18.0-s4.18.2-r0";
pull_image(docker, image).await?; pull_image(docker, image).await?;
@ -75,11 +77,6 @@ impl ServiceTrait for Smb {
&["files_external"] &["files_external"]
} }
// no need to wait for smb, as it won't be used until the user logs in
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup( async fn post_setup(
&self, &self,
_docker: &Docker, _docker: &Docker,