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

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")
}
}