1
0
Fork 0
mirror of https://codeberg.org/icewind/haze.git synced 2026-06-03 09:04:12 +02:00

allow services and presets to set pre-install config options

This commit is contained in:
Robin Appelman 2024-06-14 17:05:21 +02:00
commit 60e797545e
9 changed files with 137 additions and 7 deletions

1
Cargo.lock generated
View file

@ -669,6 +669,7 @@ dependencies = [
"petname",
"reqwest",
"serde",
"serde_json",
"shell-words",
"tar",
"termion",

View file

@ -19,6 +19,7 @@ opener = "0.7.1"
toml = "0.8.14"
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 }
tar = "0.4.41"

View file

@ -139,6 +139,13 @@ in {
description = "Commands to run post setup when the preset is enabled";
default = [];
};
config = mkOption {
type = types.submodule {
freeformType = format.type;
};
description = "Configuration options to set before install";
default = {};
};
};
});
};

View file

@ -1,6 +1,6 @@
use crate::config::{HazeConfig, HazeVolumeConfig, Preset};
use crate::database::Database;
use crate::exec::{exec, exec_tty, ExitCode};
use crate::exec::{exec, exec_io, exec_tty, ExitCode};
use crate::mapping::{default_mappings, Mapping};
use crate::php::{PhpVersion, PHP_MEMORY_LIMIT};
use crate::service::Service;
@ -17,7 +17,7 @@ use petname::petname;
use std::collections::HashMap;
use std::fmt::Display;
use std::fs;
use std::io::{stdout, Write};
use std::io::{stdout, Cursor, Read, Write, Stdout};
use std::iter::Peekable;
use std::net::IpAddr;
use std::os::unix::fs::MetadataExt;
@ -27,6 +27,7 @@ use tokio::fs::create_dir_all;
use tokio::fs::remove_dir_all;
use tokio::task::spawn;
use tokio::time::sleep;
use toml::Value;
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct CloudOptions {
@ -152,6 +153,7 @@ fn test_option_parse() {
name: "mypreset".to_string(),
commands: Vec::new(),
apps: Vec::new(),
config: HashMap::default(),
}],
&mut args
)
@ -181,6 +183,7 @@ pub struct Cloud {
pub services: Vec<Service>,
pub pinned: bool,
pub address: String,
pub preset_config: HashMap<String, Value>,
}
impl Cloud {
@ -317,6 +320,11 @@ impl Cloud {
.await?;
containers.extend(service_containers.iter().flatten().cloned());
let mut preset_config = HashMap::new();
for service in &options.services {
preset_config.extend(service.config(docker, &id, config)?);
}
env.extend(
options
.services
@ -427,6 +435,7 @@ impl Cloud {
services: options.services,
pinned: false,
address,
preset_config,
})
}
@ -476,6 +485,34 @@ impl Cloud {
}
}
pub async fn write_file<C: AsRef<[u8]>>(
&self,
docker: &Docker,
path: &str,
contents: C,
) -> Result<()> {
self.exec_io(
docker,
vec!["tee", path],
Vec::<String>::default(),
Option::<Stdout>::None,
Some(Cursor::new(contents.as_ref())),
)
.await?;
Ok(())
}
pub async fn exec_io<S: Into<String>, Env: Into<String>>(
&self,
docker: &Docker,
cmd: Vec<S>,
env: Vec<Env>,
std_out: Option<impl Write>,
std_in: Option<impl Read>,
) -> Result<ExitCode> {
exec_io(docker, &self.id, "haze", cmd, env, std_out, std_in).await
}
pub async fn occ<'a, S: Into<String> + From<&'a str>, Env: Into<String>>(
&self,
docker: &Docker,
@ -581,6 +618,7 @@ impl Cloud {
services: found_services,
pinned,
address,
preset_config: HashMap::default(),
},
))
})

View file

@ -2,10 +2,12 @@ use camino::Utf8PathBuf;
use directories_next::ProjectDirs;
use miette::{IntoDiagnostic, Report, Result, WrapErr};
use serde::Deserialize;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::env::var;
use std::fs::read_to_string;
use std::net::IpAddr;
use toml::Value;
#[derive(Debug, Deserialize)]
#[serde(from = "RawHazeConfig")]
@ -209,6 +211,8 @@ impl HazeConfig {
pub struct Preset {
pub name: String,
#[serde(default)]
pub config: HashMap<String, Value>,
#[serde(default)]
pub apps: Vec<String>,
#[serde(default)]
pub commands: Vec<String>,

View file

@ -3,7 +3,7 @@ use bollard::exec::{CreateExecOptions, ResizeExecOptions, StartExecResults};
use bollard::Docker;
use futures_util::StreamExt;
use miette::{IntoDiagnostic, Report, Result, WrapErr};
use std::io::{stdout, Read, Write};
use std::io::{stdout, Read, Stdin, Write};
use std::time::Duration;
use termion::raw::IntoRawMode;
use termion::{async_stdin, is_tty, terminal_size};
@ -98,18 +98,40 @@ pub async fn exec_tty<S1: AsRef<str>, S2: Into<String>, Env: Into<String>>(
}
pub async fn exec<S1: AsRef<str>, S2: Into<String>, Env: Into<String>>(
docker: &Docker,
container: S1,
user: &str,
cmd: Vec<S2>,
env: Vec<Env>,
std_out: Option<impl Write>,
) -> Result<ExitCode> {
exec_io(
docker,
container,
user,
cmd,
env,
std_out,
Option::<Stdin>::None,
)
.await
}
pub async fn exec_io<S1: AsRef<str>, S2: Into<String>, Env: Into<String>>(
docker: &Docker,
container: S1,
user: &str,
cmd: Vec<S2>,
env: Vec<Env>,
mut std_out: Option<impl Write>,
std_in: Option<impl Read>,
) -> Result<ExitCode> {
let cmd = cmd.into_iter().map(S2::into).collect();
let env = env.into_iter().map(Env::into).collect();
let config = CreateExecOptions {
cmd: Some(cmd),
user: Some(user.to_string()),
attach_stdin: Some(std_in.is_some()),
attach_stdout: Some(true),
attach_stderr: Some(true),
env: Some(env),
@ -121,12 +143,26 @@ pub async fn exec<S1: AsRef<str>, S2: Into<String>, Env: Into<String>>(
.await
.into_diagnostic()
.wrap_err("Failed to setup exec")?;
if let StartExecResults::Attached { mut output, .. } = docker
if let StartExecResults::Attached {
mut output,
mut input,
} = docker
.start_exec(&message.id, None)
.await
.into_diagnostic()
.wrap_err("Failed to start exec")?
{
if let Some(mut std_in) = std_in {
let mut buff = [0; 4 * 1024];
loop {
let bytes = std_in.read(&mut buff).into_diagnostic()?;
if bytes == 0 {
break;
}
input.write_all(&buff[0..bytes]).await.into_diagnostic()?;
}
input.shutdown().await.into_diagnostic()?;
}
while let Some(Ok(line)) = output.next().await {
if let Some(std_out) = &mut std_out {
write!(std_out, "{}", line).into_diagnostic()?;

View file

@ -36,7 +36,6 @@ pub async fn pull_image(docker: &Docker, image: &str) -> Result<()> {
let info: CreateImageInfo = info
.into_diagnostic()
.wrap_err_with(|| format!("Error while pulling image {}", image))?;
// dbg!(&info);
if let (Some(id), Some(status), Some(progress)) = (info.id, info.status, info.progress)
{
match bars.get(&id) {

View file

@ -169,6 +169,17 @@ async fn main() -> Result<()> {
let cloud = Cloud::create(&docker, options, &config).await?;
println!("Waiting for servers to start");
cloud.wait_for_start(&docker).await?;
if !cloud.preset_config.is_empty() {
println!("Writing preset config");
let encoded_preset_config =
serde_json::to_string(&cloud.preset_config).into_diagnostic()?;
cloud
.write_file(&docker, "config/preset.config.json", encoded_preset_config)
.await?;
cloud.write_file(&docker, "config/preset.config.php", "<?php $CONFIG=json_decode(file_get_contents(__DIR__ . '/preset.config.json'), true);").await?;
}
println!("Installing");
if let Err(e) = cloud
.exec(
@ -370,6 +381,17 @@ async fn setup(docker: &Docker, options: CloudOptions, config: &HazeConfig) -> R
if config.auto_setup.enabled {
println!("Waiting for servers to start");
cloud.wait_for_start(docker).await?;
if !cloud.preset_config.is_empty() {
println!("Writing preset config");
let encoded_preset_config =
serde_json::to_string(&cloud.preset_config).into_diagnostic()?;
cloud
.write_file(docker, "config/preset.config.json", encoded_preset_config)
.await?;
cloud.write_file(docker, "config/preset.config.php", "<?php $CONFIG=json_decode(file_get_contents(__DIR__ . '/preset.config.json'), true);").await?;
}
println!(
"Installing with username {} and password {}",
config.auto_setup.username, config.auto_setup.password

View file

@ -3,6 +3,7 @@ mod dav;
mod imaginary;
mod kaspersky;
mod ldap;
mod mail;
mod objectstore;
mod oc;
mod office;
@ -10,7 +11,6 @@ mod onlyoffice;
mod push;
mod sftp;
mod smb;
mod mail;
use crate::config::{HazeConfig, Preset};
pub use crate::service::clam::{ClamIcap, ClamIcapTls};
@ -18,6 +18,7 @@ use crate::service::dav::Dav;
use crate::service::imaginary::Imaginary;
use crate::service::kaspersky::{Kaspersky, KasperskyIcap};
pub use crate::service::ldap::{Ldap, LdapAdmin};
use crate::service::mail::Mail;
pub use crate::service::objectstore::ObjectStore;
use crate::service::oc::Oc;
pub use crate::service::office::Office;
@ -29,10 +30,11 @@ use bollard::models::ContainerState;
use bollard::Docker;
use enum_dispatch::enum_dispatch;
use miette::{IntoDiagnostic, Report, Result, WrapErr};
use std::collections::HashMap;
use std::net::IpAddr;
use std::time::Duration;
use tokio::time::{sleep, timeout};
use crate::service::mail::Mail;
use toml::Value;
#[async_trait::async_trait]
#[enum_dispatch(Service)]
@ -82,6 +84,15 @@ pub trait ServiceTrait {
&[]
}
fn config(
&self,
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<HashMap<String, Value>> {
Ok(HashMap::default())
}
async fn post_setup(
&self,
_docker: &Docker,
@ -246,6 +257,17 @@ impl ServiceTrait for PresetService {
self.0.as_str()
}
fn config(
&self,
_docker: &Docker,
_cloud_id: &str,
config: &HazeConfig,
) -> Result<HashMap<String, Value>> {
let preset =
get_preset(&config.preset, &self.0).ok_or_else(|| Report::msg("invalid preset"))?;
Ok(preset.config.clone())
}
async fn post_setup(
&self,
_docker: &Docker,