mirror of
https://codeberg.org/icewind/haze.git
synced 2026-06-03 09:04:12 +02:00
allow using release sources
This commit is contained in:
parent
8941c697fb
commit
f569ca17e2
9 changed files with 1226 additions and 113 deletions
1104
Cargo.lock
generated
1104
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -15,12 +15,12 @@ tokio = { version = "1.38.0", features = ["fs", "macros", "signal", "rt-multi-th
|
|||
futures-util = "0.3.30"
|
||||
termion = "4.0.1"
|
||||
opener = "0.7.1"
|
||||
toml = "0.8.14"
|
||||
toml = "1.0.3"
|
||||
directories-next = "2.0.0"
|
||||
serde = "1.0.203"
|
||||
serde_json = "1.0.117"
|
||||
petname = "2.0.2"
|
||||
reqwest = { version = "0.12.4", default-features = false }
|
||||
reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls-native-roots"] }
|
||||
tar = "0.4.41"
|
||||
flate2 = "1.0.30"
|
||||
async-trait = "0.1.80"
|
||||
|
|
@ -35,6 +35,10 @@ itertools = { version = "0.14.0", features = ["use_alloc"] }
|
|||
local-ip-address = "0.6.5"
|
||||
strum = { version = "0.27.2", features = ["derive"] }
|
||||
owo-colors = { version = "4.2.2", features = ["supports-colors"] }
|
||||
zip = "8.1.0"
|
||||
sha2 = "0.11.0-rc.5"
|
||||
base16ct = { version = "1.0.0", features = ["alloc"] }
|
||||
indicatif = "0.18.4"
|
||||
|
||||
hyper-reverse-proxy = { version = "0.5.2-dev", git = "https://github.com/chpio/hyper-reverse-proxy", rev = "6934877eb74465204f605cc1c05ca5a9772db7c0" }
|
||||
hyper = "1.6.0"
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ See the [configuration section](#configuration) for more options.
|
|||
#### Start an instance
|
||||
|
||||
```bash
|
||||
haze start [database] [php-version] [services]
|
||||
haze start [database] [php-version] [services] [vX.Y.Z]
|
||||
```
|
||||
|
||||
Where `database` is one of `sqlite`, `mysql`, `mariadb`, `pgsql` or `oracle`
|
||||
|
|
@ -69,6 +69,9 @@ might be missing some newer features.
|
|||
Each php version also comes with a `-dbg` variant that has php compiled in debug
|
||||
mode and can be used for debugging php itself with gdb.
|
||||
|
||||
You can specify a version number (e.g. `v32.0.2`) to use the sources from a
|
||||
release instead of using the local sources.
|
||||
|
||||
Additionally, you can use the following options when starting an instance:
|
||||
|
||||
- `s3`: set up an S3 server and configure to Nextcloud to use it as primary
|
||||
|
|
|
|||
25
src/cloud.rs
25
src/cloud.rs
|
|
@ -5,6 +5,7 @@ use crate::mapping::{default_mappings, Mapping};
|
|||
use crate::php::{PhpVersion, PHP_MEMORY_LIMIT};
|
||||
use crate::service::Service;
|
||||
use crate::service::ServiceTrait;
|
||||
use crate::sources::download_nc;
|
||||
use bollard::container::{ListContainersOptions, RemoveContainerOptions, UpdateContainerOptions};
|
||||
use bollard::models::ContainerState;
|
||||
use bollard::network::CreateNetworkOptions;
|
||||
|
|
@ -57,6 +58,7 @@ pub struct CloudOptions {
|
|||
pub php: PhpVersion,
|
||||
pub services: Vec<Service>,
|
||||
pub app_packages: Vec<Utf8PathBuf>,
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
impl CloudOptions {
|
||||
|
|
@ -69,6 +71,7 @@ impl CloudOptions {
|
|||
db: Database::default(),
|
||||
services: vec![],
|
||||
app_packages: vec![],
|
||||
version: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +85,7 @@ impl CloudOptions {
|
|||
let mut name = None;
|
||||
let mut services = Vec::new();
|
||||
let mut app_package = Vec::new();
|
||||
let mut version = None;
|
||||
|
||||
while let Some(option) = args.peek() {
|
||||
if let Ok(db_option) = Database::from_str(option.as_ref()) {
|
||||
|
|
@ -96,6 +100,9 @@ impl CloudOptions {
|
|||
} else if option.as_ref().ends_with(".tar.gz") {
|
||||
app_package.push(option.to_string().into());
|
||||
let _ = args.next();
|
||||
} else if let Some(v) = option.as_ref().strip_prefix("v") {
|
||||
version = Some(v.into());
|
||||
let _ = args.next();
|
||||
} else if option.as_ref() == "--name" {
|
||||
let _ = args.next();
|
||||
name = args.next().map(|s| s.into());
|
||||
|
|
@ -112,6 +119,7 @@ impl CloudOptions {
|
|||
.unwrap_or_default(),
|
||||
services,
|
||||
app_packages: app_package,
|
||||
version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -245,6 +253,12 @@ impl Cloud {
|
|||
.wrap_err("Failed to create directory for app packages")?;
|
||||
}
|
||||
|
||||
let source_root = if let Some(version) = options.version.as_deref() {
|
||||
download_nc(config, version).await?
|
||||
} else {
|
||||
config.sources_root.clone()
|
||||
};
|
||||
|
||||
let app_volumes = options
|
||||
.app_packages
|
||||
.iter()
|
||||
|
|
@ -327,7 +341,7 @@ impl Cloud {
|
|||
];
|
||||
let volumes: Vec<String> = mappings
|
||||
.into_iter()
|
||||
.filter_map(|mapping| mapping.get_volume_arg(&id, config))
|
||||
.filter_map(|mapping| mapping.get_volume_arg(&id, config, &source_root))
|
||||
.collect();
|
||||
|
||||
if let Some(db_name) = options
|
||||
|
|
@ -382,6 +396,7 @@ impl Cloud {
|
|||
gateway,
|
||||
&options.services,
|
||||
&config.proxy,
|
||||
options.version.as_deref(),
|
||||
)
|
||||
.await
|
||||
.wrap_err("Failed to start php container")
|
||||
|
|
@ -620,6 +635,7 @@ impl Cloud {
|
|||
let labels = cloud.labels?;
|
||||
let db = labels.get("haze-db")?.parse().ok()?;
|
||||
let php = labels.get("haze-php")?.parse().ok()?;
|
||||
let version = labels.get("haze-version").cloned();
|
||||
|
||||
let found_services = labels
|
||||
.get("haze-services")?
|
||||
|
|
@ -665,6 +681,7 @@ impl Cloud {
|
|||
db,
|
||||
services: found_services,
|
||||
app_packages: vec![],
|
||||
version,
|
||||
},
|
||||
pinned,
|
||||
address,
|
||||
|
|
@ -788,7 +805,11 @@ impl Cloud {
|
|||
for mapping in mappings {
|
||||
if let Some(rel_path) = path.strip_prefix(mapping.target.as_str()) {
|
||||
let rel_path = rel_path.trim_matches('/');
|
||||
return Some(mapping.source(&self.id, config).join(rel_path));
|
||||
return Some(
|
||||
mapping
|
||||
.source(&self.id, config, &config.sources_root)
|
||||
.join(rel_path),
|
||||
);
|
||||
}
|
||||
}
|
||||
None
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ fn subcommand_help(command: &dyn SubCommand) {
|
|||
print!(" {}", "[php version]".green());
|
||||
print!(" {}", "[database type]".green());
|
||||
print!(" {}", "[services]".green());
|
||||
print!(" {}", "[vX.Y.Z]".green());
|
||||
}
|
||||
|
||||
let args = if let Some(args) = command.get_str("Args") {
|
||||
|
|
|
|||
|
|
@ -103,13 +103,18 @@ async fn main() -> Result<ExitCode> {
|
|||
services.push(cloud.db().name());
|
||||
let services = services.join(", ");
|
||||
let pin = if cloud.pinned { "*" } else { "" };
|
||||
let version = match cloud.options.version.as_ref() {
|
||||
Some(version) => format!(", v{version}"),
|
||||
None => String::new(),
|
||||
};
|
||||
println!(
|
||||
"Cloud {}{}, {}, {}, running on {}",
|
||||
"Cloud {}{}, {}, {}{}, running on {}",
|
||||
cloud.id,
|
||||
pin,
|
||||
cloud.php().name(),
|
||||
services,
|
||||
cloud.address
|
||||
version,
|
||||
cloud.address,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,20 +78,25 @@ impl<'a> Mapping<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn source(&self, id: &str, config: &HazeConfig) -> Utf8PathBuf {
|
||||
pub fn source(&self, id: &str, config: &HazeConfig, source_root: &Utf8Path) -> Utf8PathBuf {
|
||||
match self.source_type {
|
||||
MappingSourceType::WorkDir => config.work_dir.join(id).join(self.source),
|
||||
MappingSourceType::GlobalWorkDir => config.work_dir.join(self.source),
|
||||
MappingSourceType::Sources => config.sources_root.join(self.source),
|
||||
MappingSourceType::Sources => source_root.join(self.source),
|
||||
MappingSourceType::Absolute => self.source.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_volume_arg(&self, id: &str, config: &HazeConfig) -> Option<String> {
|
||||
pub fn get_volume_arg(
|
||||
&self,
|
||||
id: &str,
|
||||
config: &HazeConfig,
|
||||
source_root: &Utf8Path,
|
||||
) -> Option<String> {
|
||||
if !self.map {
|
||||
return None;
|
||||
}
|
||||
let source = self.source(id, config);
|
||||
let source = self.source(id, config, source_root);
|
||||
Some(if self.read_only {
|
||||
format!("{}:{}:ro", source, self.target)
|
||||
} else {
|
||||
|
|
|
|||
20
src/php.rs
20
src/php.rs
|
|
@ -164,6 +164,7 @@ impl PhpVersion {
|
|||
host: &str,
|
||||
services: &[Service],
|
||||
proxy_config: &ProxyConfig,
|
||||
version: Option<&str>,
|
||||
) -> Result<String> {
|
||||
ensure_network_exists(docker, "haze").await?;
|
||||
pull_image(docker, self.image()).await?;
|
||||
|
|
@ -192,6 +193,17 @@ impl PhpVersion {
|
|||
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 = Config {
|
||||
image: Some(self.image().to_string()),
|
||||
env: Some(env),
|
||||
|
|
@ -211,13 +223,7 @@ impl PhpVersion {
|
|||
}
|
||||
},
|
||||
}),
|
||||
labels: Some(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(","),
|
||||
}),
|
||||
labels: Some(labels),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
|
|||
124
src/sources.rs
124
src/sources.rs
|
|
@ -1,6 +1,17 @@
|
|||
use crate::config::HazeConfig;
|
||||
use camino::Utf8PathBuf;
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||
use miette::{Context, IntoDiagnostic, Report, Result};
|
||||
use reqwest::header::HeaderName;
|
||||
use reqwest::{Client, IntoUrl, Response};
|
||||
use sha2::{Digest, Sha512};
|
||||
use std::fs::read_to_string;
|
||||
use std::io::Cursor;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use tokio::fs::create_dir_all;
|
||||
use zip::read::root_dir_common_filter;
|
||||
use zip::ZipArchive;
|
||||
|
||||
pub struct Sources {
|
||||
#[allow(dead_code)]
|
||||
|
|
@ -49,3 +60,116 @@ impl Sources {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn download_nc(config: &HazeConfig, version: &str) -> Result<Utf8PathBuf> {
|
||||
if !version.chars().all(|c| c.is_ascii_digit() || c == '.') {
|
||||
return Err(Report::msg(format!("Invalid version: {version}")));
|
||||
}
|
||||
let root = config.work_dir.join("sources");
|
||||
create_dir_all(&root)
|
||||
.await
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to create parent directory for sources")?;
|
||||
let dest = root.join(version);
|
||||
if !dest.exists() {
|
||||
let progress = MultiProgress::new();
|
||||
let download_style = ProgressStyle::with_template("{spinner:.green} {msg} [{elapsed_precise}] [{bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})")
|
||||
.unwrap();
|
||||
let download_bar = ProgressBar::new(0)
|
||||
.with_message("Downloading")
|
||||
.with_style(download_style.clone());
|
||||
let download_bar = progress.add(download_bar);
|
||||
|
||||
let archive = download_url(
|
||||
format!("https://download.nextcloud.com/server/releases/nextcloud-{version}.zip"),
|
||||
|size| {
|
||||
download_bar.set_length(size);
|
||||
},
|
||||
|count| {
|
||||
download_bar.inc(count);
|
||||
},
|
||||
)
|
||||
.await
|
||||
.wrap_err_with(|| format!("Failed to download archive for {}", version))?;
|
||||
download_bar.finish();
|
||||
|
||||
let expected_hash = download_text(format!(
|
||||
"https://download.nextcloud.com/server/releases/nextcloud-{version}.zip.sha512"
|
||||
))
|
||||
.await
|
||||
.wrap_err_with(|| format!("Failed to download hash for {}", version))?;
|
||||
let expected_hash = expected_hash
|
||||
.split_once(' ')
|
||||
.map(|(hash, _)| hash)
|
||||
.unwrap_or(expected_hash.trim());
|
||||
|
||||
let hash_bar = ProgressBar::new(download_bar.length().unwrap_or_default())
|
||||
.with_message("Validating")
|
||||
.with_style(download_style.clone());
|
||||
let hash_bar = progress.add(hash_bar);
|
||||
let mut hasher = Sha512::new();
|
||||
for chunk in archive.chunks(1014 * 1024) {
|
||||
hash_bar.inc(chunk.len() as u64);
|
||||
hasher.update(chunk);
|
||||
}
|
||||
let hash = hasher.finalize();
|
||||
|
||||
let hash = base16ct::lower::encode_string(&hash);
|
||||
if expected_hash != hash {
|
||||
return Err(Report::msg(format!(
|
||||
"Invalid hash for downloaded: {version}, expected {expected_hash} but got {hash}"
|
||||
)));
|
||||
}
|
||||
hash_bar.finish();
|
||||
|
||||
let extract_bar = ProgressBar::new_spinner().with_message("Extracing");
|
||||
extract_bar.enable_steady_tick(Duration::from_millis(100));
|
||||
let extract_bar = progress.add(extract_bar);
|
||||
let mut archive = ZipArchive::new(Cursor::new(archive)).into_diagnostic()?;
|
||||
archive
|
||||
.extract_unwrapped_root_dir(&dest, root_dir_common_filter)
|
||||
.into_diagnostic()
|
||||
.wrap_err("Failed to extract archive")?;
|
||||
extract_bar.finish();
|
||||
}
|
||||
Ok(dest)
|
||||
}
|
||||
|
||||
async fn download_url<U: IntoUrl, SizeFN: FnOnce(u64), ProgressFN: Fn(u64)>(
|
||||
url: U,
|
||||
size: SizeFN,
|
||||
progress: ProgressFN,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut res = get_url(url).await?.error_for_status().into_diagnostic()?;
|
||||
let mut buff = Vec::new();
|
||||
|
||||
size(res.content_length().unwrap_or_default());
|
||||
while let Some(chunk) = res.chunk().await.into_diagnostic()? {
|
||||
progress(chunk.len() as u64);
|
||||
buff.extend(chunk);
|
||||
}
|
||||
Ok(buff)
|
||||
}
|
||||
async fn download_text<U: IntoUrl>(url: U) -> Result<String> {
|
||||
get_url(url)
|
||||
.await?
|
||||
.error_for_status()
|
||||
.into_diagnostic()?
|
||||
.text()
|
||||
.await
|
||||
.into_diagnostic()
|
||||
}
|
||||
|
||||
async fn get_url<U: IntoUrl>(url: U) -> Result<Response> {
|
||||
Client::builder()
|
||||
.build()
|
||||
.into_diagnostic()?
|
||||
.get(url)
|
||||
.header(
|
||||
HeaderName::from_static("user-agent"),
|
||||
format!("haze {}", env!("CARGO_PKG_VERSION")),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.into_diagnostic()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue