mirror of
https://codeberg.org/icewind/haze.git
synced 2026-06-03 17:14:08 +02:00
245 lines
7.8 KiB
Rust
245 lines
7.8 KiB
Rust
use owo_colors::OwoColorize;
|
|
use crate::config::ProxyConfig;
|
|
use crate::database::Database;
|
|
use crate::image::{image_version, pull_image, ImageVersion};
|
|
use crate::network::ensure_network_exists;
|
|
use crate::service::Service;
|
|
use crate::service::ServiceTrait;
|
|
use bollard::config::{ContainerCreateBody, NetworkConnectRequest, NetworkingConfig};
|
|
use bollard::models::{EndpointSettings, HostConfig};
|
|
use bollard::query_parameters::CreateContainerOptions;
|
|
use bollard::Docker;
|
|
use itertools::Itertools;
|
|
use maplit::hashmap;
|
|
use miette::{IntoDiagnostic, Report, Result, WrapErr};
|
|
use reqwest::{Client, Url};
|
|
use std::net::{IpAddr, Ipv4Addr};
|
|
use std::str::FromStr;
|
|
use std::time::Duration;
|
|
use tokio::time::{sleep, timeout};
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq, Default)]
|
|
#[allow(dead_code)]
|
|
pub enum PhpVersion {
|
|
#[default]
|
|
Php85,
|
|
Php84,
|
|
Php83,
|
|
Php82,
|
|
Php81,
|
|
Php80,
|
|
}
|
|
|
|
pub const PHP_MEMORY_LIMIT: i64 = 2 * 1024 * 1024 * 1024;
|
|
|
|
impl FromStr for PhpVersion {
|
|
type Err = ();
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s {
|
|
"8" => Ok(PhpVersion::Php80),
|
|
"8.0" => Ok(PhpVersion::Php80),
|
|
"8.1" => Ok(PhpVersion::Php81),
|
|
"8.2" => Ok(PhpVersion::Php82),
|
|
"8.3" => Ok(PhpVersion::Php83),
|
|
"8.4" => Ok(PhpVersion::Php84),
|
|
"8.5" => Ok(PhpVersion::Php85),
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PhpVersion {
|
|
pub fn supported_versions() -> &'static [&'static str] {
|
|
&["8.1", "8.2", "8.3", "8.4"]
|
|
}
|
|
|
|
pub fn image(&self) -> &'static str {
|
|
match self {
|
|
PhpVersion::Php80 => "icewind1991/haze:8.0",
|
|
PhpVersion::Php81 => "icewind1991/haze:8.1",
|
|
PhpVersion::Php82 => "icewind1991/haze:8.2",
|
|
PhpVersion::Php83 => "icewind1991/haze:8.3",
|
|
PhpVersion::Php84 => "icewind1991/haze:8.4",
|
|
PhpVersion::Php85 => "icewind1991/haze:8.5",
|
|
}
|
|
}
|
|
|
|
pub fn name(&self) -> &'static str {
|
|
match self {
|
|
PhpVersion::Php80 => "8.0",
|
|
PhpVersion::Php81 => "8.1",
|
|
PhpVersion::Php82 => "8.2",
|
|
PhpVersion::Php83 => "8.3",
|
|
PhpVersion::Php84 => "8.4",
|
|
PhpVersion::Php85 => "8.4",
|
|
}
|
|
}
|
|
|
|
pub fn from_number(major: u8, minor: u8) -> Option<Self> {
|
|
match (major, minor) {
|
|
(8, 0) => Some(PhpVersion::Php80),
|
|
(8, 1) => Some(PhpVersion::Php81),
|
|
(8, 2) => Some(PhpVersion::Php82),
|
|
(8, 3) => Some(PhpVersion::Php83),
|
|
(8, 4) => Some(PhpVersion::Php84),
|
|
(8, 5) => Some(PhpVersion::Php85),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn max_minor(major: u8) -> u8 {
|
|
match major {
|
|
7 => 4,
|
|
8 => 5,
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
pub fn all() -> impl Iterator<Item = Self> {
|
|
[
|
|
PhpVersion::Php80,
|
|
PhpVersion::Php81,
|
|
PhpVersion::Php82,
|
|
PhpVersion::Php83,
|
|
PhpVersion::Php84,
|
|
PhpVersion::Php85,
|
|
]
|
|
.into_iter()
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn spawn(
|
|
&self,
|
|
docker: &Docker,
|
|
id: &str,
|
|
mut env: Vec<String>,
|
|
db: &Database,
|
|
network: &str,
|
|
volumes: Vec<String>,
|
|
host: &str,
|
|
services: &[Service],
|
|
proxy_config: &ProxyConfig,
|
|
version: Option<&str>,
|
|
) -> Result<String> {
|
|
ensure_network_exists(docker, "haze").await?;
|
|
pull_image(docker, self.image()).await?;
|
|
|
|
let image_version = image_version(&docker, self.image()).await;
|
|
let haze_version = ImageVersion::from_str(env!("CARGO_PKG_VERSION"));
|
|
if let (Some(image_version), Ok(haze_version)) = (image_version, haze_version) {
|
|
if image_version < haze_version {
|
|
eprintln!("{}: image version is out of date, run {} to update.", "Warning".red(), "haze update".blue());
|
|
eprintln!(" Haze version: {}", haze_version.bright_yellow());
|
|
eprintln!(" Image version: {}", image_version.bright_yellow());
|
|
}
|
|
}
|
|
|
|
let options = Some(CreateContainerOptions {
|
|
name: Some(id.to_string()),
|
|
..CreateContainerOptions::default()
|
|
});
|
|
let clean_id = id.strip_prefix("haze-").unwrap_or(id);
|
|
|
|
let proxy_base = &proxy_config.address;
|
|
let mut hosts = if proxy_base.is_empty() {
|
|
vec![]
|
|
} else {
|
|
let mut hosts = services
|
|
.iter()
|
|
.map(|service| service.name())
|
|
.map(|name| format!("{clean_id}-{name}.{proxy_base}:{host}"))
|
|
.collect::<Vec<_>>();
|
|
hosts.push(format!("{proxy_base}:{host}"));
|
|
hosts.push(format!("{clean_id}.{proxy_base}:{host}"));
|
|
hosts
|
|
};
|
|
hosts.push(format!("hazehost:{host}"));
|
|
env.push(format!(
|
|
"NEXTCLOUD_BASE_URL={}",
|
|
proxy_config.addr(id, IpAddr::V4(Ipv4Addr::LOCALHOST))
|
|
));
|
|
|
|
let mut labels = hashmap! {
|
|
"haze-type".to_string() => "cloud".to_string(),
|
|
"haze-db".to_string() => db.name().to_string(),
|
|
"haze-php".to_string() => self.name().to_string(),
|
|
"haze-cloud-id".to_string() => id.to_string(),
|
|
"haze-services".to_string() => services.iter().map(|s| s.name()).join(","),
|
|
};
|
|
if let Some(version) = version {
|
|
labels.insert("haze-version".to_string(), version.to_string());
|
|
}
|
|
|
|
let config = ContainerCreateBody {
|
|
image: Some(self.image().to_string()),
|
|
env: Some(env),
|
|
host_config: Some(HostConfig {
|
|
network_mode: Some(network.to_string()),
|
|
binds: Some(volumes),
|
|
extra_hosts: Some(hosts),
|
|
memory: Some(PHP_MEMORY_LIMIT),
|
|
nano_cpus: Some(2_000_000_000),
|
|
..Default::default()
|
|
}),
|
|
networking_config: Some(NetworkingConfig {
|
|
endpoints_config: Some(hashmap! {
|
|
network.to_string() => EndpointSettings {
|
|
aliases: Some(vec!["cloud".to_string()]),
|
|
..Default::default()
|
|
}
|
|
}),
|
|
}),
|
|
labels: Some(labels),
|
|
..Default::default()
|
|
};
|
|
|
|
let id = docker
|
|
.create_container(options, config)
|
|
.await
|
|
.into_diagnostic()?
|
|
.id;
|
|
|
|
if let Err(e) = docker.start_container(&id, None).await.into_diagnostic() {
|
|
docker.remove_container(&id, None).await.ok();
|
|
return Err(e);
|
|
}
|
|
|
|
if let Err(e) = docker
|
|
.connect_network(
|
|
"haze",
|
|
NetworkConnectRequest {
|
|
container: id.to_string(),
|
|
endpoint_config: Some(EndpointSettings {
|
|
aliases: Some(vec![id.to_string()]),
|
|
..Default::default()
|
|
}),
|
|
},
|
|
)
|
|
.await
|
|
.into_diagnostic()
|
|
{
|
|
docker.remove_container(&id, None).await.ok();
|
|
return Err(e);
|
|
}
|
|
|
|
Ok(id)
|
|
}
|
|
|
|
pub async fn wait_for_start(&self, ip: Option<IpAddr>) -> Result<()> {
|
|
let client = Client::new();
|
|
let url = Url::parse(&format!(
|
|
"http://{}/status.php",
|
|
ip.ok_or_else(|| Report::msg("Container not running"))?
|
|
))
|
|
.into_diagnostic()?;
|
|
timeout(Duration::from_secs(15), async {
|
|
while client.get(url.clone()).send().await.is_err() {
|
|
sleep(Duration::from_millis(100)).await
|
|
}
|
|
})
|
|
.await
|
|
.into_diagnostic()
|
|
.wrap_err("Timeout after 15 seconds")
|
|
}
|
|
}
|