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

Compare commits

...

20 commits

Author SHA1 Message Date
a3f2355dea create smb/sftp accounts for ldaptest 2026-05-28 18:48:40 +02:00
d852f9db4f remove php-smbclient for now as it's broken 2026-05-28 18:48:19 +02:00
92fbc74a5b fix ldap 2026-05-28 18:40:21 +02:00
204fb676d6 add sftp with key authentication service 2026-05-26 20:49:19 +02:00
f99238121b nix cleanup 2026-05-09 16:45:55 +02:00
cd9740675f clippy fixes 2026-05-08 22:23:40 +02:00
39ba7a2a53 mention webhook tester in readme 2026-05-08 22:22:42 +02:00
ad999702aa improve websocket proxying 2026-05-08 20:41:19 +02:00
b977cd9dfa parallelize git pull
fixes #21
2026-05-08 19:09:43 +02:00
8771e7dc5f switch to multiprogress for git pull 2026-05-08 18:57:19 +02:00
373ce0f3fd add php-imagick
fixes #22
2026-05-07 18:59:05 +02:00
9e080f0d54 fix using '--' flags in test and integration commands 2026-05-07 18:40:30 +02:00
512b669a7c sqlite table mode 2026-04-30 19:08:13 +02:00
ea3f89bb04 fpm logs 2026-04-28 18:55:12 +02:00
b4a77997ab fpm status page 2026-04-28 18:54:48 +02:00
87f6907778 autosetup for ldap
fixes #19
2026-04-17 22:22:33 +02:00
9de626a905 typo 2026-04-16 16:30:09 +02:00
53e30a94aa fix generated urls in integration tests 2026-04-15 23:28:46 +02:00
948d01600e readme typo 2026-04-14 14:52:46 +02:00
19e60217ea 127.0.0.1 now works for federation 2026-04-14 14:05:02 +02:00
34 changed files with 693 additions and 221 deletions

95
Cargo.lock generated
View file

@ -147,6 +147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
dependencies = [
"axum-core",
"axum-macros",
"bytes",
"form_urlencoded",
"futures-util",
@ -192,6 +193,17 @@ dependencies = [
"tracing",
]
[[package]]
name = "axum-macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "backtrace"
version = "0.3.74"
@ -506,6 +518,31 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
version = "0.1.7"
@ -896,25 +933,6 @@ dependencies = [
"url",
]
[[package]]
name = "h2"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
@ -932,7 +950,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "haze"
version = "2.2.1"
version = "2.2.2"
dependencies = [
"async-trait",
"atty",
@ -956,6 +974,7 @@ dependencies = [
"opener",
"owo-colors",
"petname",
"rayon",
"reqwest",
"serde",
"serde_json",
@ -965,6 +984,7 @@ dependencies = [
"tar",
"termion",
"tokio",
"tokio-stream",
"toml",
"tracing",
"tracing-subscriber",
@ -1072,7 +1092,6 @@ dependencies = [
"bytes",
"futures-channel",
"futures-core",
"h2",
"http",
"http-body",
"httparse",
@ -1103,8 +1122,9 @@ dependencies = [
[[package]]
name = "hyper-reverse-proxy"
version = "0.5.2-dev"
source = "git+https://github.com/chpio/hyper-reverse-proxy?rev=6934877eb74465204f605cc1c05ca5a9772db7c0#6934877eb74465204f605cc1c05ca5a9772db7c0"
source = "git+https://code.betamike.com/micropelago/hyper-reverse-proxy.git?rev=d5a6f799189360d9449ae47ab3cdde511f02cf39#d5a6f799189360d9449ae47ab3cdde511f02cf39"
dependencies = [
"http-body-util",
"hyper",
"hyper-util",
"tokio",
@ -2002,6 +2022,26 @@ dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "rayon"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.5.10"
@ -2695,6 +2735,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.13"

View file

@ -1,6 +1,6 @@
[package]
name = "haze"
version = "2.2.1"
version = "2.2.2"
authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2021"
repository = "https://codeberg.org/icewind/haze"
@ -12,6 +12,7 @@ bollard = "0.20.1"
maplit = "1.0.2"
camino = { version = "1.2.2", features = ["serde1"] }
tokio = { version = "1.49.0", features = ["fs", "macros", "signal", "rt-multi-thread"] }
tokio-stream = { version = "0.1.18", features = ["net"] }
futures-util = "0.3.32"
termion = "4.0.6"
opener = "0.8.4"
@ -39,11 +40,12 @@ zip = "8.1.0"
sha2 = "0.11.0-rc.5"
base16ct = { version = "1.0.0", features = ["alloc"] }
indicatif = "0.18.4"
rayon = "1.12.0"
hyper-reverse-proxy = { version = "0.5.2-dev", git = "https://github.com/chpio/hyper-reverse-proxy", rev = "6934877eb74465204f605cc1c05ca5a9772db7c0" }
hyper-reverse-proxy = { version = "0.5.2-dev", git = "https://code.betamike.com/micropelago/hyper-reverse-proxy.git", rev = "d5a6f799189360d9449ae47ab3cdde511f02cf39" }
hyper = "1.8.1"
hyper-util = "0.1.20"
axum = { version = "0.8.8", features = ["tokio"] }
axum = { version = "0.8.8", features = ["tokio", "macros"] }
[profile.release]
lto = true

View file

@ -81,6 +81,8 @@ Additionally, you can use the following options when starting an instance:
- `smb`: set up a samba server for external storage use.
- `dav`: set up a WebDAV server for external storage use.
- `sftp`: set up a SFTP server for external storage use.
- `sftp-key`: set up a SFTP server for external storage use with public key
authentication.
- `kaspersky`: set up a kaspersky scan engine server in http mode. ( Requires
[manually setting up the image](https://github.com/icewind1991/kaspersky-docker))
- `kaspersky-icap`: setup a kaspersky scan engine server in ICAP mode.
@ -93,6 +95,8 @@ Additionally, you can use the following options when starting an instance:
generation.
- `mail`: start a [smtp4dev](https://github.com/rnwood/smtp4dev) server and
configure it the mail server.
- `webhook` start a
[webhook tester](https://github.com/tarampampam/webhook-tester)
- `redis`: start a separate container for redis.
- `redis-tls`: connect to redis over TLS.
- `<path to app.tar.gz>`: by specifying the path to an app package this package
@ -274,8 +278,7 @@ proxy to allow using a wildcard domain.
### Setup
- Set a DNS record for `*.haze.exmaple.com` and `haze.example.com` pointing to
your development machine. Pointing it to `127.0.0.1` will also work, but comes
with limitations like federation not being supported.
your development machine.
- Set the `proxy` configuration with your domain and desired listen endpoint.
- Set up a service to run `haze proxy` in the background as your own user. A
systemd user service is recommended (see [haze.service](./haze.service) for an
@ -296,7 +299,7 @@ will automatically point to the last created instance.
Additionally, the proxy allows access to the server containers trough either
`<instance id>-<service id>.haze.example.com` for a specific instance, or
`<service-id>.haze.example.com` for the last created instead. For example
`<service-id>.haze.example.com` for the last created instance. For example
`rolling-bees-mail.haze.example.com` will give access to the smtp4dev web
interface of the `rolling-bees` instance.

38
certificates/sftp/id_rsa Normal file
View file

@ -0,0 +1,38 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
Y6CAchnyhHO0BTAAAACWhhemVAaGF6ZQE=
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDfbdpaofpjBEtsIE73hQo6R+cnabpmyJzR0y3dOYOeMtTQnOk63vC3n6YxmsQpBOl5+jPJQejfP4eSaWUsAY66k3LcD6MbY73nlRSDDR0Z+7qPo45vroO72Omq3wFDqCvdwRgBKmU8jsHzz39xWknmXUeIFSDJHtnXq/XmoIO2akVUgQIB0gISY+ix+Zm623dhiX3EtrLlYKZTO1ZL9qY9wazQM4QatZ4K13EYpZPdPLB0ErJDQ9t1gxqaAMhPslXVlLNk9qXLiwHsT52lLkCAEVVeZnrXXnUo3VMnyDKhSMWFjty1gQimdxmbJPg9i/Ii/FGxsm28RGJ40fwOhfkPC2pzFuAcrQ7Bi12XjJ1FdOhijeX6oiJ1/6DwANV5r+ZuR9M96B1Oijcs65OFc7ATEOW6SkzyhsUOnPViXUvqtEkBU07UqYdaGfq2jbeO1iMGFzMDaeR6ZBdBtVKNz+Ei2NAMgU0w6wtbfc/5jaT0uSkjRMvT2iA7E4VnMZmna00= haze@haze

View file

@ -42,6 +42,7 @@ if ((getent group $HAZE_GID | length) > 0) {
useradd -u $HAZE_UID -g $HAZE_GID haze
}
chown -R $"haze:($HAZE_GID)" /home/haze
ls -af /etc/home | each {|file| ln -s $file.name $"/home/haze/($file.name | path basename)" }
if ("/var/run/docker.sock" | path exists) {
let dockerGid = stat --format "%g" /var/run/docker.sock

View file

@ -0,0 +1 @@
.mode table

View file

@ -109,5 +109,16 @@ http {
expires 7d; # Cache-Control policy borrowed from `.htaccess`
access_log off; # Optional: Don't log access to assets
}
location /fpm-status {
include /conf/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass php-handler;
fastcgi_read_timeout 3600;
proxy_request_buffering off;
fastcgi_request_buffering off;
fastcgi_buffering off;
}
}
}

View file

@ -1,11 +1,11 @@
[global]
error_log = /proc/self/fd/2
error_log = /var/log/php-fpm-error.log
daemonize = no
[www]
access.log = /proc/self/fd/2
access.log =/var/log/php-fpm-access.log
user = haze
group = haze
@ -21,6 +21,7 @@ pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.status_path = /fpm-status
clear_env = no

View file

@ -35,9 +35,10 @@ in
gmp
apcu
ffi
imagick
]
++ optionals (!debug) [
smbclient # this breaks the build for no apparent reason
# smbclient # this breaks the build for no apparent reason
]
++ optionals withBlackfire [
blackfire

View file

@ -2,27 +2,23 @@
lib,
php,
debug ? false,
}: let
inherit (builtins) compareVersions;
inherit (lib) optionals;
withBlackfire = !debug && ((compareVersions php.version "8.1.0") == 1);
in
php.buildEnv {
extensions = import ./php-ext.nix {inherit lib php debug;};
extraConfig = ''
xdebug.mode=debug,trace,profile
xdebug.start_with_request=trigger
xdebug.discover_client_host=false
xdebug.client_host=hazehost
xdebug.log_level=0
xdebug.output_dir=/tmp/xdebug
}:
php.buildEnv {
extensions = import ./php-ext.nix {inherit lib php debug;};
extraConfig = ''
xdebug.mode=debug,trace,profile
xdebug.start_with_request=trigger
xdebug.discover_client_host=false
xdebug.client_host=hazehost
xdebug.log_level=0
xdebug.output_dir=/tmp/xdebug
memory_limit=512M
memory_limit=512M
post_max_size 10G
upload_max_filesize 10G
post_max_size 10G
upload_max_filesize 10G
apc.enable_cli=1
opcache.enable_cli=1
'';
}
apc.enable_cli=1
opcache.enable_cli=1
'';
}

View file

@ -1,6 +1,6 @@
#!/bin/nu
def main [feature: path, ...rest] {
def --wrapped main [feature: path, ...rest] {
mut feature = $feature;
mut workdir = $"($env.WEBROOT)/build/integration"
if ($feature | str starts-with "apps/") {
@ -13,6 +13,10 @@ def main [feature: path, ...rest] {
$feature = $feature | str replace "build/integration/" ""
}
# don't use the proxy urls for generated urls
occ config:system:delete overwritehost
occ config:system:delete overwriteprotocol
cd $workdir
bash run.sh $feature ...$rest
}

View file

@ -1,6 +1,6 @@
#!/bin/nu
def main [...rest] {
def --wrapped main [...rest] {
cd $env.WEBROOT
XDEBUG_SESSION=haze phpunit --configuration $"($env.WEBROOT)/tests/phpunit-autotest.xml" ...$rest

View file

@ -1,6 +1,5 @@
{
rustPlatform,
pkg-config,
lib,
git,
}: let
@ -10,7 +9,7 @@
src = sourceByRegex ../. ["Cargo.*" "(src|certificates)(/.*)?"];
version = (fromTOML (readFile ../Cargo.toml)).package.version;
in
rustPlatform.buildRustPackage rec {
rustPlatform.buildRustPackage {
pname = "haze";
inherit src version;
@ -20,7 +19,7 @@ in
cargoLock = {
lockFile = ../Cargo.lock;
outputHashes = {
"hyper-reverse-proxy-0.5.2-dev" = "sha256-+ebi4FVVkiOpf75e8K5oGkHJBYQjLNJhUPNj+78zd7Q=";
"hyper-reverse-proxy-0.5.2-dev" = "sha256-awmj5aLFTea+kj81cwmfP1HWlWezwEKfyQSUJWjtamk=";
};
};
}

View file

@ -2,11 +2,15 @@ use crate::config::HazeConfig;
use crate::Result;
use git2::build::CheckoutBuilder;
use git2::{Branch, BranchType, Repository, RepositoryState};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use miette::{Context, IntoDiagnostic};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use rayon::ThreadPoolBuilder;
use std::fs::read_dir;
use std::iter::once;
use std::path::PathBuf;
use std::process::Command;
use std::time::Duration;
fn find_app_repos(config: &HazeConfig) -> Result<impl Iterator<Item = PathBuf>> {
let apps_dirs = once(config.sources_root.as_path().join("apps"))
@ -83,38 +87,66 @@ const GIT_BINARY: &str = match option_env!("GIT_BINARY") {
pub fn pull_all(config: &HazeConfig) -> Result<()> {
let (max_app, max_branch) = longest_app_branch(config)?;
for app_dir in find_app_repos(config)? {
let app_name = app_dir.file_name().unwrap().to_string_lossy();
let repo = Repository::init(&app_dir)
.into_diagnostic()
.wrap_err_with(|| format!("Failed to open repository {}", app_dir.display()))?;
let branch_name = current_branch_name(&repo).unwrap_or("unknown".into());
let progress = MultiProgress::new();
let pull_style = ProgressStyle::with_template("{spinner:.green} {msg}").unwrap();
print!(
"{app_name:<app_width$} - {branch_name:<branch_width$}",
app_width = max_app,
branch_width = max_branch
);
let pool = ThreadPoolBuilder::new()
.num_threads(8)
.build()
.into_diagnostic()?;
let repos = find_app_repos(config)?.collect::<Vec<_>>();
if repo.state() != RepositoryState::Clean {
println!(": repository not clean ❌");
continue;
}
pool.install(|| {
repos.par_iter().for_each(|app_dir| {
let app_name = app_dir.file_name().unwrap().to_string_lossy();
let Ok(repo) = Repository::init(app_dir) else {
return;
};
let branch_name = current_branch_name(&repo).unwrap_or("unknown".into());
let output = Command::new(GIT_BINARY)
.arg("pull")
.current_dir(&app_dir)
.output()
.into_diagnostic()
.wrap_err_with(|| format!("Failed to run git pull for {}", app_dir.display()))?;
let bar = ProgressBar::new_spinner().with_style(pull_style.clone());
bar.enable_steady_tick(Duration::from_millis(100));
let bar = progress.add(bar);
let msg = |state: &str| {
format!(
"{app_name:<app_width$} - {branch_name:<branch_width$}{state}",
app_width = max_app,
branch_width = max_branch
)
};
if repo.state() != RepositoryState::Clean {
bar.set_message(msg(" repository not clean"));
bar.finish();
return;
}
bar.set_message(msg(""));
let output = match Command::new(GIT_BINARY)
.arg("pull")
.current_dir(app_dir)
.output()
{
Ok(output) => output,
Err(error) => {
bar.set_message(msg(&format!(" {error}")));
return;
}
};
if output.status.success() {
bar.set_message(msg(""));
} else {
let err = String::from_utf8_lossy(&output.stderr);
let err_line = err.lines().next().unwrap();
bar.set_message(msg(&format!(" {err_line}")));
}
bar.finish();
});
});
if output.status.success() {
println!("");
} else {
println!("");
eprintln!("{}", String::from_utf8_lossy(&output.stderr))
}
}
Ok(())
}

View file

@ -633,12 +633,7 @@ async fn setup(docker: &Docker, options: CloudOptions, config: &HazeConfig) -> R
for service in cloud.services() {
for cmd in service.post_setup(docker, &cloud.id, config).await? {
cloud
.exec(
docker,
shell_words::split(&cmd).into_diagnostic()?,
false,
Vec::<String>::default(),
)
.exec(docker, cmd, false, Vec::<String>::default())
.await?;
}
}

View file

@ -5,26 +5,34 @@ use axum::http::header::HOST;
use axum::http::HeaderValue;
use axum::{
body::Body,
extract::{Request, State},
extract::Request,
response::{IntoResponse, Response},
Router,
};
use bollard::Docker;
use futures_util::StreamExt;
use hyper::body::Incoming;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::StatusCode;
use hyper_util::rt::TokioIo;
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
use miette::{miette, IntoDiagnostic};
use std::collections::HashMap;
use std::convert::Infallible;
use std::fs::{create_dir_all, set_permissions};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::pin::pin;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::UnixListener;
use tokio::signal::ctrl_c;
use tokio::spawn;
use tokio::time::sleep;
use tokio_stream::wrappers::{TcpListenerStream, UnixListenerStream};
use tracing::{debug, error, info};
struct ActiveInstances {
@ -163,20 +171,26 @@ async fn serve(instances: ActiveInstances, listen: String, base_address: String)
ctrl_c().await.ok();
};
let app = Router::new().fallback(handler).with_state(AppState {
let state = AppState {
instances: instances.clone(),
base_address: base_address.clone(),
proxy_client: Arc::new(proxy_client),
});
};
if !listen.starts_with('/') {
let addr: SocketAddr = listen.parse().into_diagnostic()?;
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
println!("listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app)
.with_graceful_shutdown(cancel)
.await
.unwrap();
let mut connections = pin!(TcpListenerStream::new(listener).take_until(cancel));
while let Some(stream) = connections.next().await {
match stream {
Ok(stream) => handle_connection(state.clone(), stream),
Err(error) => {
error!(%error, "connection failed");
}
}
}
} else {
let listen: PathBuf = listen.into();
if let Some(parent) = listen.parent() {
@ -187,18 +201,42 @@ async fn serve(instances: ActiveInstances, listen: String, base_address: String)
}
let _ = tokio::fs::remove_file(&listen).await;
let uds = UnixListener::bind(&listen).unwrap();
let listener = UnixListener::bind(&listen).unwrap();
println!("listening on {}", listen.display());
set_permissions(&listen, PermissionsExt::from_mode(0o666)).into_diagnostic()?;
axum::serve(uds, app)
.with_graceful_shutdown(cancel)
.await
.unwrap();
let mut connections = pin!(UnixListenerStream::new(listener).take_until(cancel));
while let Some(stream) = connections.next().await {
match stream {
Ok(stream) => handle_connection(state.clone(), stream),
Err(error) => {
error!(%error, "connection failed");
}
}
}
}
Ok(())
}
fn handle_connection<I: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
state: AppState,
stream: I,
) {
let io = TokioIo::new(stream);
// Spawn a tokio task to serve multiple connections concurrently
tokio::task::spawn(async move {
if let Err(err) = http1::Builder::new()
.serve_connection(io, service_fn(move |req| handler(state.clone(), req)))
.with_upgrades()
.await
{
eprintln!("Error serving connection: {:?}", err);
}
});
}
async fn get_remote(
host: Option<&HeaderValue>,
instances: &ActiveInstances,
@ -232,9 +270,9 @@ async fn get_remote(
}
}
type Client = hyper_util::client::legacy::Client<HttpConnector, Body>;
type Client = hyper_util::client::legacy::Client<HttpConnector, Incoming>;
async fn handler(State(state): State<AppState>, mut req: Request) -> Result<Response, StatusCode> {
async fn handler(state: AppState, mut req: Request<Incoming>) -> Result<Response, Infallible> {
let host = req.headers().get(HOST).cloned();
let remote = match get_remote(host.as_ref(), &state.instances, &state.base_address).await {
Ok(remote) => remote,
@ -259,13 +297,13 @@ async fn handler(State(state): State<AppState>, mut req: Request) -> Result<Resp
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
&uri,
req,
&state.proxy_client,
state.proxy_client.as_ref(),
)
.await
{
Ok(response) => Ok(response.map(Body::new)),
Err(error) => {
error!(%error, "error while proxying request");
error!(?error, "error while proxying request");
Ok(StatusCode::BAD_REQUEST.into_response())
}
}

View file

@ -14,6 +14,7 @@ mod sftp;
mod redis;
mod sharded;
mod smb;
mod webhook;
use crate::cloud::CloudOptions;
use crate::config::{HazeConfig, Preset, ProxyConfig};
@ -29,9 +30,10 @@ pub use crate::service::office::Office;
pub use crate::service::onlyoffice::OnlyOffice;
pub use crate::service::push::NotifyPush;
use crate::service::redis::Redis;
use crate::service::sftp::Sftp;
use crate::service::sftp::{Sftp, SftpKey};
use crate::service::sharded::{Sharding, ShardingMigrate, ShardingMigrateUnset, SingleShard};
use crate::service::smb::Smb;
use crate::service::webhook::Webhook;
use bollard::models::ContainerState;
use bollard::Docker;
use enum_dispatch::enum_dispatch;
@ -114,7 +116,7 @@ pub trait ServiceTrait {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
Ok(Vec::new())
}
@ -265,6 +267,8 @@ pub enum ServiceType {
Dav,
/// Sftp external storage
Sftp,
/// Sftp external storage with public key authentication
SftpKey,
/// ownCloud instance for migration
Oc,
/// Imaginary for preview generation
@ -296,6 +300,8 @@ pub enum ServiceType {
RedisTls,
/// Use FrankenPHP instead of PHP-FPM
FrankenPhp,
/// Webhook test listener
Webhook,
}
#[enum_dispatch]
@ -314,6 +320,7 @@ pub enum Service {
ShardingMigrate(ShardingMigrate),
ShardingMigrateUnset(ShardingMigrateUnset),
Sftp(Sftp),
SftpKey(SftpKey),
Kaspersky(Kaspersky),
KasperskyIcap(KasperskyIcap),
Clam(Clam),
@ -326,6 +333,7 @@ pub enum Service {
Redis(Redis),
RedisTls(RedisTls),
FrankenPhp(FrankenPhp),
Webhook(Webhook),
Preset(PresetService),
}
@ -356,6 +364,7 @@ impl Service {
}
ServiceType::Dav => Some(vec![Service::Dav(Dav)]),
ServiceType::Sftp => Some(vec![Service::Sftp(Sftp)]),
ServiceType::SftpKey => Some(vec![Service::SftpKey(SftpKey)]),
ServiceType::Oc => Some(vec![Service::Oc(Oc)]),
ServiceType::Imaginary => Some(vec![Service::Imaginary(Imaginary)]),
ServiceType::Kaspersky => Some(vec![Service::Kaspersky(Kaspersky)]),
@ -369,6 +378,7 @@ impl Service {
ServiceType::Redis => Some(vec![Service::Redis(Redis)]),
ServiceType::RedisTls => Some(vec![Service::RedisTls(RedisTls)]),
ServiceType::FrankenPhp => Some(vec![Service::FrankenPhp(FrankenPhp)]),
ServiceType::Webhook => Some(vec![Service::Webhook(Webhook)]),
}
} else {
presets
@ -431,15 +441,29 @@ impl ServiceTrait for PresetService {
_docker: &Docker,
_cloud_id: &str,
config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
let preset =
get_preset(&config.preset, &self.0).ok_or_else(|| Report::msg("invalid preset"))?;
let mut commands: Vec<_> = preset
.apps
.iter()
.map(|app| format!("occ app:enable {app} --force"))
.map(|app| {
vec![
"occ".into(),
"app:enable".into(),
app.clone(),
"--force".into(),
]
})
.collect();
commands.extend_from_slice(&preset.commands);
for cmnd in &preset.commands {
commands.push(shell_words::split(cmnd).into_diagnostic()?);
}
Ok(commands)
}
}
fn split_cmnd(s: &str) -> Vec<String> {
s.split(' ').map(String::from).collect()
}

View file

@ -2,7 +2,7 @@ use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::exec::exec;
use crate::image::pull_image;
use crate::service::ServiceTrait;
use crate::service::{split_cmnd, ServiceTrait};
use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions;
@ -85,14 +85,13 @@ impl ServiceTrait for ClamIcap {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
Ok(vec![
"occ config:app:set files_antivirus av_mode --value=icap".into(),
"occ config:app:set files_antivirus av_host --value=clamav-icap".into(),
"occ config:app:set files_antivirus av_port --value=1344".into(),
"occ config:app:set files_antivirus av_icap_request_service --value=avscan".into(),
"occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found"
.into(),
split_cmnd("occ config:app:set files_antivirus av_mode --value=icap"),
split_cmnd("occ config:app:set files_antivirus av_host --value=clamav-icap"),
split_cmnd("occ config:app:set files_antivirus av_port --value=1344"),
split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=avscan"),
split_cmnd("occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found"),
])
}
}
@ -171,7 +170,7 @@ impl ServiceTrait for ClamIcapTls {
docker: &Docker,
cloud_id: &str,
config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
let mut cert = Vec::new();
exec(
docker,
@ -191,14 +190,13 @@ impl ServiceTrait for ClamIcapTls {
.wrap_err("Failed to write icap certificate")?;
Ok(vec![
"occ config:app:set files_antivirus av_mode --value=icap".into(),
"occ config:app:set files_antivirus av_icap_tls --value=1".into(),
"occ config:app:set files_antivirus av_host --value=clamav-icap-tls".into(),
"occ config:app:set files_antivirus av_port --value=1345".into(),
"occ config:app:set files_antivirus av_icap_request_service --value=avscan".into(),
"occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found"
.into(),
"occ security:certificates:import data/icap-cert.pem".into(),
split_cmnd("occ config:app:set files_antivirus av_mode --value=icap"),
split_cmnd("occ config:app:set files_antivirus av_icap_tls --value=1"),
split_cmnd("occ config:app:set files_antivirus av_host --value=clamav-icap-tls"),
split_cmnd("occ config:app:set files_antivirus av_port --value=1345"),
split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=avscan"),
split_cmnd("occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found"),
split_cmnd("occ security:certificates:import data/icap-cert.pem"),
])
}
}
@ -221,10 +219,10 @@ impl ServiceTrait for Clam {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
Ok(vec![
"occ config:app:set files_antivirus av_mode --value=executable".into(),
"occ config:app:set files_antivirus av_path --value=/bin/clamscan".into(),
split_cmnd("occ config:app:set files_antivirus av_mode --value=executable"),
split_cmnd("occ config:app:set files_antivirus av_path --value=/bin/clamscan"),
])
}
}
@ -294,10 +292,12 @@ impl ServiceTrait for ClamSocket {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
Ok(vec![
"occ config:app:set files_antivirus av_mode --value=socket".into(),
"occ config:app:set files_antivirus av_socket --value=tcp://clamav-socket:3310".into(),
split_cmnd("occ config:app:set files_antivirus av_mode --value=socket"),
split_cmnd(
"occ config:app:set files_antivirus av_socket --value=tcp://clamav-socket:3310",
),
])
}
}

View file

@ -1,7 +1,7 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
use crate::service::{split_cmnd, ServiceTrait};
use crate::Result;
use bollard::config::ContainerCreateBody;
use bollard::models::{EndpointSettings, HostConfig, NetworkingConfig};
@ -76,12 +76,12 @@ impl ServiceTrait for Dav {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
Ok(vec![
"occ files_external:create dav dav password::password".into(),
"occ files_external:config 1 host dav".into(),
"occ files_external:config 1 user test".into(),
"occ files_external:config 1 password test".into(),
split_cmnd("occ files_external:create dav dav password::password"),
split_cmnd("occ files_external:config 1 host dav"),
split_cmnd("occ files_external:config 1 user test"),
split_cmnd("occ files_external:config 1 password test"),
])
}
}

View file

@ -1,7 +1,7 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
use crate::service::{split_cmnd, ServiceTrait};
use crate::Result;
use bollard::config::NetworkingConfig;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig};
@ -71,11 +71,14 @@ impl ServiceTrait for Imaginary {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
Ok(vec![
"occ config:system:set enabledPreviewProviders 0 --value='OC\\Preview\\Imaginary'"
.into(),
"occ config:system:set preview_imaginary_url --value='http://imaginary:9000'".into(),
split_cmnd(
"occ config:system:set enabledPreviewProviders 0 --value='OC\\Preview\\Imaginary'",
),
split_cmnd(
"occ config:system:set preview_imaginary_url --value='http://imaginary:9000'",
),
])
}
}

View file

@ -2,7 +2,7 @@ use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::exec::exec;
use crate::image::{image_exists, pull_image};
use crate::service::ServiceTrait;
use crate::service::{split_cmnd, ServiceTrait};
use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions;
@ -101,11 +101,11 @@ impl ServiceTrait for Kaspersky {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
Ok(vec![
"occ config:app:set files_antivirus av_mode --value=kaspersky".into(),
"occ config:app:set files_antivirus av_host --value=kaspersky".into(),
"occ config:app:set files_antivirus av_port --value=80".into(),
split_cmnd("occ config:app:set files_antivirus av_mode --value=kaspersky"),
split_cmnd("occ config:app:set files_antivirus av_host --value=kaspersky"),
split_cmnd("occ config:app:set files_antivirus av_port --value=80"),
])
}
}
@ -187,13 +187,15 @@ impl ServiceTrait for KasperskyIcap {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
Ok(vec![
"occ config:app:set files_antivirus av_mode --value=icap".into(),
"occ config:app:set files_antivirus av_host --value=kaspersky-icap".into(),
"occ config:app:set files_antivirus av_port --value=1344".into(),
"occ config:app:set files_antivirus av_icap_request_service --value=req".into(),
"occ config:app:set files_antivirus av_icap_response_header --value=X-Virus-ID".into(),
split_cmnd("occ config:app:set files_antivirus av_mode --value=icap"),
split_cmnd("occ config:app:set files_antivirus av_host --value=kaspersky-icap"),
split_cmnd("occ config:app:set files_antivirus av_port --value=1344"),
split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=req"),
split_cmnd(
"occ config:app:set files_antivirus av_icap_response_header --value=X-Virus-ID",
),
])
}
}

View file

@ -1,7 +1,7 @@
use crate::cloud::CloudOptions;
use crate::config::{HazeConfig, ProxyConfig};
use crate::image::pull_image;
use crate::service::ServiceTrait;
use crate::service::{split_cmnd, ServiceTrait};
use crate::Result;
use bollard::config::NetworkingConfig;
use bollard::models::{ContainerCreateBody, ContainerState, EndpointSettings, HostConfig};
@ -86,6 +86,46 @@ impl ServiceTrait for Ldap {
) -> Result<bool> {
self.is_running(docker, cloud_id).await
}
async fn post_setup(
&self,
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<Vec<String>>> {
Ok(vec![
split_cmnd("occ ldap:create-empty-config"),
split_cmnd("occ ldap:set-config s01 ldapHost ldap://ldap"),
split_cmnd("occ ldap:set-config s01 ldapPort 389"),
split_cmnd("occ ldap:set-config s01 ldapAgentName cn=admin,dc=example,dc=org"),
split_cmnd("occ ldap:set-config s01 ldapAgentPassword haze"),
split_cmnd("occ ldap:set-config s01 ldapBase dc=example,dc=org"),
split_cmnd("occ ldap:set-config s01 ldapBaseUsers dc=example,dc=org"),
split_cmnd("occ ldap:set-config s01 ldapBaseGroups dc=example,dc=org"),
split_cmnd("occ ldap:set-config s01 ldapLoginFilter (&(&(objectclass=inetOrgPerson))(uid=%uid))"),
split_cmnd("occ ldap:set-config s01 ldapUserFilter ((objectclass=inetOrgPerson))"),
split_cmnd("occ ldap:set-config s01 ldapUserFilterMode 0"),
split_cmnd("occ ldap:set-config s01 ldapUserDisplayName sn"),
split_cmnd("occ ldap:set-config s01 ldapUserFilterObjectclass inetOrgPerson"),
split_cmnd("occ ldap:set-config s01 ldapGroupFilter (&(|(objectclass=posixGroup)))"),
split_cmnd("occ ldap:set-config s01 ldapGroupFilterObjectclass posixGroup"),
split_cmnd("occ ldap:set-config s01 ldapEmailAttribute email"),
split_cmnd("occ ldap:set-config s01 ldapUuidUserAttribute email"),
split_cmnd("occ ldap:set-config s01 ldapUuidUserAttribute auto"),
split_cmnd("occ ldap:set-config s01 ldapUuidGroupAttribute auto"),
split_cmnd("occ ldap:set-config s01 ldapLoginFilterUsername 1"),
split_cmnd("occ ldap:set-config s01 ldapConfigurationActive 1"),
])
}
async fn start_message(
&self,
_docker: &Docker,
_cloud_id: &str,
_proxy: &ProxyConfig,
) -> Result<Option<String>> {
Ok(Some("\nLdap users provisioned:\n\t'cn=admin,dc=example,dc=org' and password 'haze'\n\t'cn=ldaptest,dc=example,dc=org' and password 'test'\n\nldaptest is available for login\n".into()))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
@ -185,8 +225,6 @@ impl ServiceTrait for LdapAdmin {
return Err(Report::msg("ldap admin not started"));
};
let addr = proxy.addr(&id, IpAddr::from_str(&ip).unwrap());
Ok(Some(format!(
"Ldap admin running at: {addr} with 'cn=admin,dc=example,dc=org' and password 'haze'"
)))
Ok(Some(format!("Ldap admin running at: {addr}")))
}
}

View file

@ -1,7 +1,7 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
use crate::service::{split_cmnd, ServiceTrait};
use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions;
@ -70,14 +70,14 @@ impl ServiceTrait for Mail {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
Ok(vec![
"occ config:system:set mail_smtpmode --value smtp".into(),
"occ config:system:set mail_sendmailmode --value smtp".into(),
"occ config:system:set mail_domain --value haze".into(),
"occ config:system:set mail_smtphost --value mail".into(),
"occ config:system:set mail_smtpport --value 25".into(),
"occ user:setting admin settings email admin@haze".into(),
split_cmnd("occ config:system:set mail_smtpmode --value smtp"),
split_cmnd("occ config:system:set mail_sendmailmode --value smtp"),
split_cmnd("occ config:system:set mail_domain --value haze"),
split_cmnd("occ config:system:set mail_smtphost --value mail"),
split_cmnd("occ config:system:set mail_smtpport --value 25"),
split_cmnd("occ user:setting admin settings email admin@haze"),
])
}
}

View file

@ -2,7 +2,7 @@ use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::exec::exec;
use crate::image::pull_image;
use crate::service::ServiceTrait;
use crate::service::{split_cmnd, ServiceTrait};
use crate::Result;
use bollard::models::{
ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig,
@ -247,18 +247,18 @@ impl ServiceTrait for ObjectStore {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
match self {
ObjectStore::S3 => Ok(vec![
"occ files_external:create s3 amazons3 amazons3::accesskey".into(),
"occ files_external:config 1 bucket ext".into(),
"occ files_external:config 1 hostname s3".into(),
"occ files_external:config 1 port 9000".into(),
"occ files_external:config 1 use_ssl false".into(),
"occ files_external:config 1 use_path_style true".into(),
"occ files_external:config 1 key minio".into(),
"occ files_external:config 1 secret minio123".into(),
"mc alias set s3 http://s3:9000 minio minio123".into(),
split_cmnd("occ files_external:create s3 amazons3 amazons3::accesskey"),
split_cmnd("occ files_external:config 1 bucket ext"),
split_cmnd("occ files_external:config 1 hostname s3"),
split_cmnd("occ files_external:config 1 port 9000"),
split_cmnd("occ files_external:config 1 use_ssl false"),
split_cmnd("occ files_external:config 1 use_path_style true"),
split_cmnd("occ files_external:config 1 key minio"),
split_cmnd("occ files_external:config 1 secret minio123"),
split_cmnd("mc alias set s3 http://s3:9000 minio minio123"),
]),
// ObjectStore::S3s => Ok(vec![
// "occ files_external:create s3 amazons3 amazons3::accesskey".into(),

View file

@ -83,7 +83,7 @@ impl ServiceTrait for Oc {
docker: &Docker,
cloud_id: &str,
config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
if let Some(ip) = self.get_ips(docker, cloud_id).await?.next() {
let container = self.container_name(cloud_id).unwrap();
let addr = config.proxy.addr(&container, ip);

View file

@ -119,7 +119,7 @@ impl ServiceTrait for Office {
docker: &Docker,
cloud_id: &str,
config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
let container = &self.container_name(cloud_id).unwrap();
let info = docker
.inspect_container(container, None)
@ -152,8 +152,22 @@ impl ServiceTrait for Office {
.addr_with_port(container, ip, self.proxy_port());
Ok(vec![
format!(r#"occ config:app:set richdocuments public_wopi_url --value="{public}""#),
r#"occ richdocuments:setup --wopi-url "http://office:9980" --callback-url "http://cloud""#.into(),
vec![
"occ".into(),
"config:app:set".into(),
"richdocuments".into(),
"public_wopi_url".into(),
"--value".into(),
public,
],
vec![
"occ".into(),
"richdocuments:setup".into(),
"--wopi-url".into(),
"http://office:9980".into(),
"--callback-url".into(),
"http://cloud".into(),
],
])
}

View file

@ -2,7 +2,7 @@ use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::exec::exec;
use crate::image::pull_image;
use crate::service::ServiceTrait;
use crate::service::{split_cmnd, ServiceTrait};
use crate::Result;
use bollard::models::{
ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig,
@ -82,7 +82,7 @@ impl ServiceTrait for OnlyOffice {
docker: &Docker,
cloud_id: &str,
config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
let info = docker
.inspect_container(&self.container_name(cloud_id).unwrap(), None)
.await
@ -137,16 +137,44 @@ impl ServiceTrait for OnlyOffice {
);
Ok(vec![
format!("occ config:app:set onlyoffice DocumentServerUrl --value {addr}/"),
format!("occ config:app:set onlyoffice jwt_secret --value {secret}"),
"occ onlyoffice:documentserver --check".into(),
vec![
"occ".into(),
"config:app:set".into(),
"onlyoffice".into(),
"DocumentServerUrl".into(),
"--value".into(),
addr,
],
vec![
"occ".into(),
"config:app:set".into(),
"onlyoffice".into(),
"jwt_secret".into(),
"--value".into(),
secret.into(),
],
split_cmnd("occ onlyoffice:documentserver --check"),
])
} else {
Ok(vec![
format!("occ config:app:set onlyoffice DocumentServerUrl --value https://{ip}/"),
"occ config:app:set onlyoffice verify_peer_off --value true".into(),
format!("occ config:app:set onlyoffice jwt_secret --value {secret}"),
"occ onlyoffice:documentserver --check".into(),
vec![
"occ".into(),
"config:app:set".into(),
"onlyoffice".into(),
"DocumentServerUrl".into(),
"--value".into(),
format!("https://{ip}/"),
],
split_cmnd("occ config:app:set onlyoffice verify_peer_off --value true"),
vec![
"occ".into(),
"config:app:set".into(),
"onlyoffice".into(),
"jwt_secret".into(),
"--value".into(),
secret.into(),
],
split_cmnd("occ onlyoffice:documentserver --check"),
])
}
}

View file

@ -87,7 +87,7 @@ impl ServiceTrait for NotifyPush {
docker: &Docker,
cloud_id: &str,
config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
let mut ips: Vec<_> = self.get_ips(docker, cloud_id).await?.collect();
if let Ok(local_interfaces) = list_afinet_netifas() {
ips.extend(local_interfaces.into_iter().map(|(_, ip)| ip));
@ -97,10 +97,14 @@ impl ServiceTrait for NotifyPush {
.iter()
.enumerate()
.map(|(i, ip)| {
format!(
"occ config:system:set trusted_proxies {} --value {ip}",
i + 1
)
vec![
"occ".into(),
"config:system:set".into(),
"trusted_proxies".into(),
(i + 1).to_string(),
"--value".into(),
ip.to_string(),
]
})
.collect();
@ -108,7 +112,7 @@ impl ServiceTrait for NotifyPush {
config
.proxy
.addr_with_port(&self.container_name(cloud_id).unwrap(), ips[0], 7867);
commands.push(format!("occ notify_push:setup {}", addr));
commands.push(vec!["occ".into(), "notify_push:setup".into(), addr]);
Ok(commands)
}

View file

@ -1,7 +1,7 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
use crate::service::{split_cmnd, ServiceTrait};
use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions;
@ -70,7 +70,9 @@ impl ServiceTrait for Redis {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
Ok(vec!["occ config:system:set redis host --value redis".into()])
) -> Result<Vec<Vec<String>>> {
Ok(vec![split_cmnd(
"occ config:system:set redis host --value redis",
)])
}
}

View file

@ -1,13 +1,14 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
use crate::service::{split_cmnd, ServiceTrait};
use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap;
use miette::IntoDiagnostic;
use miette::{Context, IntoDiagnostic};
use std::fs::{create_dir_all, write};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Sftp;
@ -50,7 +51,10 @@ impl ServiceTrait for Sftp {
}
}),
}),
cmd: Some(vec!["test:test:::data".into()]),
cmd: Some(vec![
"test:test:::data".into(),
"ldaptest:test:::data".into(),
]),
..Default::default()
};
let id = docker
@ -75,13 +79,119 @@ impl ServiceTrait for Sftp {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
Ok(vec![
"occ files_external:create sftp sftp password::password".into(),
"occ files_external:config 1 host sftp".into(),
"occ files_external:config 1 user test".into(),
"occ files_external:config 1 root data".into(),
"occ files_external:config 1 password test".into(),
split_cmnd("occ files_external:create sftp sftp password::password"),
split_cmnd("occ files_external:config 1 host sftp"),
split_cmnd("occ files_external:config 1 user test"),
split_cmnd("occ files_external:config 1 root data"),
split_cmnd("occ files_external:config 1 password test"),
])
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SftpKey;
#[async_trait::async_trait]
impl ServiceTrait for SftpKey {
fn name(&self) -> &str {
"sftp-key"
}
async fn spawn(
&self,
docker: &Docker,
cloud_id: &str,
network: &str,
config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "atmoz/sftp:alpine";
pull_image(docker, image).await?;
let options = Some(CreateContainerOptions {
name: self.container_name(cloud_id),
..CreateContainerOptions::default()
});
let key_dir = config.work_dir.join("certificates/sftp");
create_dir_all(&key_dir)
.into_diagnostic()
.wrap_err("Failed to create sftp certificate directory")?;
let private_path = key_dir.join("id_rsa");
let public_path = key_dir.join("id_rsa.pub");
let private_key = include_str!("../../certificates/sftp/id_rsa");
let public_key = include_str!("../../certificates/sftp/id_rsa.pub");
if !private_path.exists() {
write(&private_path, private_key)
.into_diagnostic()
.wrap_err("Failed to write sftp client certificate")?;
}
if !public_path.exists() {
write(&public_path, public_key)
.into_diagnostic()
.wrap_err("Failed to write sftp client key")?;
}
let volumes = vec![format!("{public_path}:/home/test/.ssh/keys/id_rsa:ro")];
let config = ContainerCreateBody {
image: Some(image.into()),
host_config: Some(HostConfig {
network_mode: Some(network.to_string()),
binds: Some(volumes),
..Default::default()
}),
labels: Some(hashmap! {
"haze-type".into() => self.name().into(),
"haze-cloud-id".into() => cloud_id.into(),
}),
networking_config: Some(NetworkingConfig {
endpoints_config: Some(hashmap! {
network.into() => EndpointSettings {
aliases: Some(vec![self.name().to_string()]),
..Default::default()
}
}),
}),
cmd: Some(vec!["test::::data".into()]),
..Default::default()
};
let id = docker
.create_container(options, config)
.await
.into_diagnostic()?
.id;
docker.start_container(&id, None).await.into_diagnostic()?;
Ok(vec![id])
}
fn container_name(&self, cloud_id: &str) -> Option<String> {
Some(format!("{}-sftp-key", cloud_id))
}
fn apps(&self) -> &'static [&'static str] {
&["files_external"]
}
async fn post_setup(
&self,
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<Vec<String>>> {
Ok(vec![
split_cmnd("occ files_external:create sftp sftp publickey::rsa_private"),
split_cmnd("occ files_external:config 1 host sftp-key"),
split_cmnd("occ files_external:config 1 user test"),
split_cmnd("occ files_external:config 1 root data"),
vec![
"occ".into(),
"files_external:config".into(),
"--value-from-file".into(),
"1".into(),
"private_key".into(),
"/certificates/sftp/id_rsa".into(),
],
])
}
}

View file

@ -1,7 +1,7 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
use crate::service::{split_cmnd, ServiceTrait};
use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions;
@ -40,8 +40,10 @@ impl ServiceTrait for Smb {
}),
env: Some(vec![
"ACCOUNT_test=test".into(),
"ACCOUNT_ldaptest=test".into(),
"UID_test=1000".into(),
"SAMBA_VOLUME_CONFIG_test=[test]; path=/tmp; valid users = test; guest ok = no; read only = no; browseable = yes".into(),
"SAMBA_VOLUME_CONFIG_ldaptest=[ldaptest]; path=/tmp; valid users = ldaptest; guest ok = no; read only = no; browseable = yes".into(),
]),
labels: Some(hashmap! {
"haze-type".into() => self.name().into(),
@ -79,13 +81,13 @@ impl ServiceTrait for Smb {
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<Vec<String>> {
) -> Result<Vec<Vec<String>>> {
Ok(vec![
"occ files_external:create smb smb password::password".into(),
"occ files_external:config 1 host smb".into(),
"occ files_external:config 1 user test".into(),
"occ files_external:config 1 password test".into(),
"occ files_external:config 1 share test".into(),
split_cmnd("occ files_external:create smb smb password::password"),
split_cmnd("occ files_external:config 1 host smb"),
split_cmnd("occ files_external:config 1 user test"),
split_cmnd("occ files_external:config 1 password test"),
split_cmnd("occ files_external:config 1 share test"),
])
}
}

71
src/service/webhook.rs Normal file
View file

@ -0,0 +1,71 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap;
use miette::IntoDiagnostic;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Webhook;
#[async_trait::async_trait]
impl ServiceTrait for Webhook {
fn name(&self) -> &str {
"webhook"
}
async fn spawn(
&self,
docker: &Docker,
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "ghcr.io/tarampampam/webhook-tester";
pull_image(docker, image).await?;
let options = Some(CreateContainerOptions {
name: self.container_name(cloud_id),
..CreateContainerOptions::default()
});
let config = ContainerCreateBody {
image: Some(image.into()),
host_config: Some(HostConfig {
network_mode: Some(network.to_string()),
..Default::default()
}),
labels: Some(hashmap! {
"haze-type".into() => self.name().into(),
"haze-cloud-id".into() => cloud_id.into(),
}),
networking_config: Some(NetworkingConfig {
endpoints_config: Some(hashmap! {
network.into() => EndpointSettings {
aliases: Some(vec![self.name().to_string()]),
..Default::default()
}
}),
}),
..Default::default()
};
let id = docker
.create_container(options, config)
.await
.into_diagnostic()?
.id;
docker.start_container(&id, None).await.into_diagnostic()?;
Ok(vec![id])
}
fn container_name(&self, cloud_id: &str) -> Option<String> {
Some(format!("{}-webhook", cloud_id))
}
fn proxy_port(&self) -> u16 {
8080
}
}

View file

@ -122,7 +122,7 @@ pub async fn download_nc(config: &HazeConfig, version: &str) -> Result<Utf8PathB
}
hash_bar.finish();
let extract_bar = ProgressBar::new_spinner().with_message("Extracing");
let extract_bar = ProgressBar::new_spinner().with_message("Extracting");
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()?;