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

allow configuring additional app directories and add a writable app directory

fixes #15
This commit is contained in:
Robin Appelman 2026-03-05 23:27:54 +01:00
commit b7ea4e9760
6 changed files with 130 additions and 34 deletions

View file

@ -303,6 +303,7 @@ options
```toml
sources_root = "/path/to/sources" # path of the nextcloud sources. required
app_directories = ["/path/to/sources/more_app"] # paths to additional app directories.
work_dir = "/path/to/temp/dir" # path to temporary directory. optional, defaults to "/tmp/haze"
[auto_setup] # optional

View file

@ -1,4 +1,12 @@
<?php $CONFIG=[
<?php
$extra_config = [];
if (file_exists(__DIR__ . '/nextcloud.json')) {
$extra_config = json_decode(file_get_contents(__DIR__ . '/nextcloud.json'), true);
}
$CONFIG = array_merge_recursive($extra_config, [
'debug' => true,
'appstoreenabled' => false,
'memcache.local' => '\\OC\\Memcache\\APCu',
@ -9,4 +17,4 @@
'profiling.secret' => 'haze',
'profiling.path' => '/tmp/profiling',
//PLACEHOLDER
];
]);

View file

@ -505,6 +505,7 @@ impl SubCommand for HazeCommand {
fn test_arg_parse() {
let config = HazeConfig {
sources_root: Default::default(),
app_directories: Default::default(),
work_dir: Default::default(),
auto_setup: Default::default(),
volume: vec![],

View file

@ -1,7 +1,7 @@
use crate::config::{HazeConfig, HazeVolumeConfig};
use crate::database::Database;
use crate::exec::{exec, exec_io, exec_tty, ExitCode};
use crate::mapping::{default_mappings, Mapping};
use crate::mapping::{for_config, Mapping};
use crate::php::{PhpVersion, PHP_MEMORY_LIMIT};
use crate::service::Service;
use crate::service::ServiceTrait;
@ -15,14 +15,14 @@ use flate2::read::GzDecoder;
use futures_util::future::try_join_all;
use miette::{IntoDiagnostic, Report, Result, WrapErr};
use petname::petname;
use serde_json::Value;
use serde_json::{Map, Value};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::Display;
use std::fs;
use std::fs::read_to_string;
use std::fs::{read_to_string, write};
use std::io::{stdout, Cursor, Read, Stdout, Write};
use std::iter::Peekable;
use std::iter::{once, Peekable};
use std::net::IpAddr;
use std::os::unix::fs::MetadataExt;
use std::str::FromStr;
@ -286,11 +286,8 @@ impl Cloud {
})
})
.collect::<Result<Vec<_>>>()?;
let mappings = config
.volume
.iter()
.map(Mapping::from)
.chain(default_mappings())
let mappings = for_config(config)
.chain(app_volumes.iter().map(Mapping::from))
.collect::<Vec<_>>();
for mapping in &mappings {
@ -300,6 +297,48 @@ impl Cloud {
.wrap_err_with(|| format!("Failed to setup work directory {}", mapping.source))?;
}
let mut nc_config = Value::Object(Map::new());
nc_config["apps_paths"] = Value::Array(
once("apps")
.chain(
config
.app_directories
.iter()
.filter_map(|dir| dir.file_name()),
)
.map(|name| {
[
(
String::from("path"),
Value::from(format!("/var/www/html/{}", name)),
),
(String::from("url"), Value::from(format!("/{}", name))),
(String::from("writable"), Value::from(false)),
]
.into_iter()
.collect()
})
.chain(once(
[
(
String::from("path"),
Value::from("/var/www/html/store_apps"),
),
(String::from("url"), Value::from("/store_apps")),
(String::from("writable"), Value::from(true)),
]
.into_iter()
.collect(),
))
.collect(),
);
write(
workdir.join("config/nextcloud.json"),
serde_json::to_string_pretty(&nc_config).unwrap(),
)
.into_diagnostic()
.wrap_err("Failed to write config json")?;
let network = docker
.create_network(NetworkCreateRequest {
name: id.clone(),
@ -500,10 +539,7 @@ impl Cloud {
pub async fn destroy(self, docker: &Docker) -> Result<()> {
for container in self.containers {
docker
.kill_container(
container.trim_start_matches('/'),
None,
)
.kill_container(container.trim_start_matches('/'), None)
.await
.into_diagnostic()
.wrap_err("Failed to kill container")?;
@ -802,12 +838,7 @@ impl Cloud {
format!("/var/www/html/{path}").into()
};
let mut mappings = config
.volume
.iter()
.map(Mapping::from)
.chain(default_mappings())
.collect::<Vec<_>>();
let mut mappings = for_config(config).collect::<Vec<_>>();
mappings.sort_by_key(|mapping| usize::MAX - mapping.target.as_str().len());
for mapping in mappings {

View file

@ -4,7 +4,7 @@ use miette::{IntoDiagnostic, Report, Result, WrapErr};
use serde::Deserialize;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::env::var;
use std::env::home_dir;
use std::fs::read_to_string;
use std::net::IpAddr;
use toml::Value;
@ -13,6 +13,7 @@ use toml::Value;
#[serde(from = "RawHazeConfig")]
pub struct HazeConfig {
pub sources_root: Utf8PathBuf,
pub app_directories: Vec<Utf8PathBuf>,
pub work_dir: Utf8PathBuf,
pub auto_setup: HazeAutoSetupConfig,
pub volume: Vec<HazeVolumeConfig>,
@ -27,6 +28,8 @@ pub struct RawHazeConfig {
#[serde(default = "default_work_dir")]
pub work_dir: Utf8PathBuf,
#[serde(default)]
pub app_directories: Vec<Utf8PathBuf>,
#[serde(default)]
pub auto_setup: HazeAutoSetupConfig,
#[serde(default)]
pub volume: Vec<HazeVolumeConfig>,
@ -42,7 +45,11 @@ impl From<RawHazeConfig> for HazeConfig {
fn from(raw: RawHazeConfig) -> Self {
fn normalize_path(path: Utf8PathBuf) -> Utf8PathBuf {
if path.starts_with("~") {
let home = var("HOME").expect("HOME not set");
let home = home_dir().expect("can't detect home directory");
let home = home
.into_os_string()
.into_string()
.expect("non-utf8 home directory");
format!("{}{}", home, &path.as_str()[1..]).into()
} else {
path
@ -51,6 +58,11 @@ impl From<RawHazeConfig> for HazeConfig {
HazeConfig {
sources_root: normalize_path(raw.sources_root),
app_directories: raw
.app_directories
.into_iter()
.map(normalize_path)
.collect(),
work_dir: normalize_path(raw.work_dir),
auto_setup: raw.auto_setup,
volume: raw.volume,

View file

@ -1,13 +1,14 @@
use crate::config::{HazeConfig, HazeVolumeConfig};
use camino::{Utf8Path, Utf8PathBuf};
use miette::{IntoDiagnostic, Result};
use std::borrow::Cow;
use tokio::fs::{create_dir_all, write};
#[derive(Debug)]
pub struct Mapping<'a> {
source_type: MappingSourceType,
pub source: &'a Utf8Path,
pub target: &'a Utf8Path,
pub source: Cow<'a, Utf8Path>,
pub target: Cow<'a, Utf8Path>,
mapping_type: MappingType,
read_only: bool,
map: bool,
@ -23,6 +24,26 @@ impl<'a> Mapping<'a> {
where
Target: Into<&'a Utf8Path>,
Source: Into<&'a Utf8Path>,
{
Mapping {
source_type,
source: Cow::Borrowed(source.into()),
target: Cow::Borrowed(target.into()),
mapping_type: MappingType::Folder,
read_only: false,
map: true,
create: true,
}
}
pub fn owned<Source, Target>(
source_type: MappingSourceType,
source: Source,
target: Target,
) -> Self
where
Target: Into<Cow<'a, Utf8Path>>,
Source: Into<Cow<'a, Utf8Path>>,
{
Mapping {
source_type,
@ -65,10 +86,10 @@ impl<'a> Mapping<'a> {
return Ok(());
}
let source = match self.source_type {
MappingSourceType::WorkDir => config.work_dir.join(id).join(self.source),
MappingSourceType::GlobalWorkDir => config.work_dir.join(self.source),
MappingSourceType::WorkDir => config.work_dir.join(id).join(self.source.as_ref()),
MappingSourceType::GlobalWorkDir => config.work_dir.join(self.source.as_ref()),
MappingSourceType::Sources => return Ok(()),
MappingSourceType::Absolute => self.source.into(),
MappingSourceType::Absolute => self.source.as_ref().into(),
};
match self.mapping_type {
MappingType::Folder => create_dir_all(source).await.into_diagnostic()?,
@ -80,10 +101,10 @@ impl<'a> Mapping<'a> {
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 => source_root.join(self.source),
MappingSourceType::Absolute => self.source.into(),
MappingSourceType::WorkDir => config.work_dir.join(id).join(self.source.as_ref()),
MappingSourceType::GlobalWorkDir => config.work_dir.join(self.source.as_ref()),
MappingSourceType::Sources => source_root.join(self.source.as_ref()),
MappingSourceType::Absolute => self.source.as_ref().into(),
}
}
@ -112,6 +133,7 @@ pub fn default_mappings<'a>() -> impl IntoIterator<Item = Mapping<'a>> {
Mapping::new(Sources, "", "/var/www/html"),
Mapping::new(WorkDir, "data", "/var/www/html/data"),
Mapping::new(WorkDir, "config", "/var/www/html/config"),
Mapping::new(WorkDir, "store_app", "/var/www/html/store_app"),
Mapping::new(WorkDir, "data-autotest", "/var/www/html/data-autotest"),
Mapping::new(WorkDir, "skeleton", "/var/www/html/core/skeleton"),
Mapping::new(
@ -168,9 +190,30 @@ pub fn default_mappings<'a>() -> impl IntoIterator<Item = Mapping<'a>> {
Mapping::new(WorkDir, "profiling", "/tmp/profiling"),
Mapping::new(WorkDir, "php-config", "/config"),
];
IntoIterator::into_iter(mappings)
}
pub fn for_config<'a>(config: &'a HazeConfig) -> impl Iterator<Item = Mapping<'a>> {
let app_dir_mappings = config.app_directories.iter().map(|dir| {
Mapping::owned(
MappingSourceType::Absolute,
dir.as_path(),
Cow::Owned(Utf8PathBuf::from(format!(
"/var/www/html/{}",
dir.file_name().unwrap()
))),
)
});
config
.volume
.iter()
.map(Mapping::from)
.chain(app_dir_mappings)
.chain(default_mappings())
}
#[derive(Debug, Copy, Clone)]
pub enum MappingSourceType {
Sources,
@ -194,8 +237,8 @@ impl<'a> From<&'a HazeVolumeConfig> for Mapping<'a> {
};
Mapping {
source_type: MappingSourceType::Absolute,
source: config.source.as_path(),
target: config.target.as_path(),
source: Cow::Borrowed(config.source.as_path()),
target: Cow::Borrowed(config.target.as_path()),
mapping_type: ty,
read_only: config.read_only,
map: true,