mirror of
https://codeberg.org/icewind/haze.git
synced 2026-10-01 16:54:08 +02:00
add database webui
This commit is contained in:
parent
0a74daf4fd
commit
515fc60f49
8 changed files with 213 additions and 22 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
- Support having a `#!` inside a haze script to specify the interpreter to use
|
- Support having a `#!` inside a haze script to specify the interpreter to use
|
||||||
|
- Add webui for accessing the database.
|
||||||
|
|
||||||
## 2.4.1
|
## 2.4.1
|
||||||
|
|
||||||
|
|
|
||||||
11
src/cloud.rs
11
src/cloud.rs
|
|
@ -4,9 +4,9 @@ use crate::exec::{ExitCode, exec, exec_io, exec_tty};
|
||||||
use crate::git::{create_worktree, find_app_repo, init_submodules, remove_worktree};
|
use crate::git::{create_worktree, find_app_repo, init_submodules, remove_worktree};
|
||||||
use crate::mapping::{Mapping, for_config};
|
use crate::mapping::{Mapping, for_config};
|
||||||
use crate::php::PhpVersion;
|
use crate::php::PhpVersion;
|
||||||
use crate::service::Service;
|
|
||||||
use crate::service::ServiceTrait;
|
use crate::service::ServiceTrait;
|
||||||
use crate::service::deduplicate_services;
|
use crate::service::deduplicate_services;
|
||||||
|
use crate::service::{DbGate, Service};
|
||||||
use crate::sources::download_nc;
|
use crate::sources::download_nc;
|
||||||
use bollard::Docker;
|
use bollard::Docker;
|
||||||
use bollard::config::NetworkCreateRequest;
|
use bollard::config::NetworkCreateRequest;
|
||||||
|
|
@ -347,6 +347,8 @@ fn test_option_parse() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static DEFAULT_SERVICES: &[Service] = &[Service::DbGate(DbGate)];
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Cloud {
|
pub struct Cloud {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|
@ -539,6 +541,8 @@ impl Cloud {
|
||||||
options
|
options
|
||||||
.services
|
.services
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|service| !service.hidden())
|
||||||
|
.chain(DEFAULT_SERVICES.into_iter())
|
||||||
.map(|service| service.spawn(docker, &id, &network, config, &options)),
|
.map(|service| service.spawn(docker, &id, &network, config, &options)),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -970,7 +974,10 @@ impl Cloud {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn services(&self) -> impl Iterator<Item = &Service> {
|
pub fn services(&self) -> impl Iterator<Item = &Service> {
|
||||||
self.options.services.iter()
|
self.options
|
||||||
|
.services
|
||||||
|
.iter()
|
||||||
|
.chain(DEFAULT_SERVICES.into_iter())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn db(&self) -> &Database {
|
pub fn db(&self) -> &Database {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use crate::config::HazeConfig;
|
use crate::config::HazeConfig;
|
||||||
use crate::exec::{exec, exec_tty, ExitCode};
|
use crate::exec::{ExitCode, exec, exec_tty};
|
||||||
use crate::image::pull_image;
|
use crate::image::pull_image;
|
||||||
|
use bollard::Docker;
|
||||||
use bollard::config::ContainerCreateBody;
|
use bollard::config::ContainerCreateBody;
|
||||||
use bollard::models::{EndpointSettings, HostConfig, NetworkingConfig};
|
use bollard::models::{EndpointSettings, HostConfig, NetworkingConfig};
|
||||||
use bollard::query_parameters::CreateContainerOptions;
|
use bollard::query_parameters::CreateContainerOptions;
|
||||||
use bollard::Docker;
|
|
||||||
use maplit::hashmap;
|
use maplit::hashmap;
|
||||||
use miette::{IntoDiagnostic, Report, Result, WrapErr};
|
use miette::{IntoDiagnostic, Report, Result, WrapErr};
|
||||||
use std::io::{stdout, Stdout};
|
use std::io::{Stdout, stdout};
|
||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
@ -32,6 +32,39 @@ impl DatabaseFamily {
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
self.into()
|
self.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn db_gate_driver(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
DatabaseFamily::Mysql => "mysql@dbgate-plugin-mysql",
|
||||||
|
DatabaseFamily::MariaDB => "mariadb@dbgate-plugin-mysql",
|
||||||
|
DatabaseFamily::Postgres => "postgres@dbgate-plugin-postgres",
|
||||||
|
DatabaseFamily::Oracle => "oracle@dbgate-plugin-oracle",
|
||||||
|
DatabaseFamily::Sqlite => "sqlite@dbgate-plugin-sqlite",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn port(&self) -> u16 {
|
||||||
|
match self {
|
||||||
|
DatabaseFamily::Mysql | DatabaseFamily::MariaDB => 3306,
|
||||||
|
DatabaseFamily::Postgres => 5432,
|
||||||
|
DatabaseFamily::Oracle => 1521,
|
||||||
|
DatabaseFamily::Sqlite => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn username(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
DatabaseFamily::Oracle => "system",
|
||||||
|
_ => "haze",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn db(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
DatabaseFamily::Oracle => "SYSTEM",
|
||||||
|
_ => "haze",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Default)]
|
#[derive(Clone, Debug, Eq, PartialEq, Default)]
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ use crate::args::{HazeCommand, SubCommand};
|
||||||
use crate::database::DatabaseFamily;
|
use crate::database::DatabaseFamily;
|
||||||
use crate::php::PhpVersion;
|
use crate::php::PhpVersion;
|
||||||
use crate::service::ServiceType;
|
use crate::service::ServiceType;
|
||||||
use owo_colors::colors::xterm::Gray;
|
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
|
use owo_colors::colors::xterm::Gray;
|
||||||
use strum::{EnumMessage, EnumProperty, IntoEnumIterator};
|
use strum::{EnumMessage, EnumProperty, IntoEnumIterator};
|
||||||
|
|
||||||
pub fn help(command: Option<&dyn SubCommand>) {
|
pub fn help(command: Option<&dyn SubCommand>) {
|
||||||
|
|
@ -151,13 +151,15 @@ fn subcommand_help(command: &dyn SubCommand) {
|
||||||
for service in ServiceType::iter() {
|
for service in ServiceType::iter() {
|
||||||
let service: ServiceType = service;
|
let service: ServiceType = service;
|
||||||
let service_str: &'static str = service.into();
|
let service_str: &'static str = service.into();
|
||||||
|
if let Some(doc) = service.get_documentation() {
|
||||||
println!(
|
println!(
|
||||||
" {}{} {}",
|
" {}{} {}",
|
||||||
service.blue(),
|
service.blue(),
|
||||||
" ".repeat(max_service_len - service_str.len()),
|
" ".repeat(max_service_len - service_str.len()),
|
||||||
service.get_documentation().unwrap_or_default(),
|
doc,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
println!("{}", "Options:".yellow().bold());
|
println!("{}", "Options:".yellow().bold());
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,11 @@ async fn main() -> Result<ExitCode> {
|
||||||
HazeArgs::List { filter } => {
|
HazeArgs::List { filter } => {
|
||||||
let list = Cloud::list(&docker, filter, &config).await?;
|
let list = Cloud::list(&docker, filter, &config).await?;
|
||||||
for cloud in list {
|
for cloud in list {
|
||||||
let mut services: Vec<_> = cloud.services().map(Service::name).collect();
|
let mut services: Vec<_> = cloud
|
||||||
|
.services()
|
||||||
|
.filter(|service| !service.hidden())
|
||||||
|
.map(Service::name)
|
||||||
|
.collect();
|
||||||
services.push(cloud.db().name());
|
services.push(cloud.db().name());
|
||||||
let services = services.join(", ");
|
let services = services.join(", ");
|
||||||
let pin = if cloud.is_pinned(&docker).await.unwrap_or(false) {
|
let pin = if cloud.is_pinned(&docker).await.unwrap_or(false) {
|
||||||
|
|
|
||||||
13
src/proxy.rs
13
src/proxy.rs
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::service::{ServiceTrait, ServiceType};
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
use crate::service::{ServiceTrait, ServiceType};
|
||||||
use crate::{Cloud, HazeConfig};
|
use crate::{Cloud, HazeConfig};
|
||||||
use axum::http::header::HOST;
|
|
||||||
use axum::http::HeaderValue;
|
use axum::http::HeaderValue;
|
||||||
|
use axum::http::header::HOST;
|
||||||
use axum::{
|
use axum::{
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::Request,
|
extract::Request,
|
||||||
|
|
@ -10,13 +10,13 @@ use axum::{
|
||||||
};
|
};
|
||||||
use bollard::Docker;
|
use bollard::Docker;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
|
use hyper::StatusCode;
|
||||||
use hyper::body::Incoming;
|
use hyper::body::Incoming;
|
||||||
use hyper::server::conn::http1;
|
use hyper::server::conn::http1;
|
||||||
use hyper::service::service_fn;
|
use hyper::service::service_fn;
|
||||||
use hyper::StatusCode;
|
|
||||||
use hyper_util::rt::TokioIo;
|
use hyper_util::rt::TokioIo;
|
||||||
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
|
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
|
||||||
use miette::{miette, IntoDiagnostic};
|
use miette::{IntoDiagnostic, miette};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::fs::{create_dir_all, set_permissions};
|
use std::fs::{create_dir_all, set_permissions};
|
||||||
|
|
@ -237,7 +237,8 @@ async fn serve(
|
||||||
} else {
|
} else {
|
||||||
let listen: PathBuf = listen.into();
|
let listen: PathBuf = listen.into();
|
||||||
if let Some(parent) = listen.parent()
|
if let Some(parent) = listen.parent()
|
||||||
&& !parent.exists() {
|
&& !parent.exists()
|
||||||
|
{
|
||||||
create_dir_all(parent).into_diagnostic()?;
|
create_dir_all(parent).into_diagnostic()?;
|
||||||
set_permissions(parent, PermissionsExt::from_mode(0o755)).into_diagnostic()?;
|
set_permissions(parent, PermissionsExt::from_mode(0o755)).into_diagnostic()?;
|
||||||
}
|
}
|
||||||
|
|
@ -337,7 +338,7 @@ async fn handler(state: AppState, mut req: Request<Incoming>) -> Result<Response
|
||||||
return Ok(hyper::Response::builder()
|
return Ok(hyper::Response::builder()
|
||||||
.status(StatusCode::BAD_REQUEST)
|
.status(StatusCode::BAD_REQUEST)
|
||||||
.body(e.into())
|
.body(e.into())
|
||||||
.unwrap())
|
.unwrap());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
mod authentik;
|
mod authentik;
|
||||||
mod clam;
|
mod clam;
|
||||||
mod dav;
|
mod dav;
|
||||||
|
mod dbgate;
|
||||||
mod imaginary;
|
mod imaginary;
|
||||||
mod kaspersky;
|
mod kaspersky;
|
||||||
mod ldap;
|
mod ldap;
|
||||||
|
|
@ -22,6 +23,7 @@ use crate::config::{HazeConfig, Preset, ProxyConfig};
|
||||||
pub use crate::service::authentik::{Authentik, AuthentikOidc, AuthentikSaml, AuthentikScim};
|
pub use crate::service::authentik::{Authentik, AuthentikOidc, AuthentikSaml, AuthentikScim};
|
||||||
pub use crate::service::clam::{Clam, ClamIcap, ClamIcapTls, ClamSocket};
|
pub use crate::service::clam::{Clam, ClamIcap, ClamIcapTls, ClamSocket};
|
||||||
use crate::service::dav::Dav;
|
use crate::service::dav::Dav;
|
||||||
|
pub use crate::service::dbgate::DbGate;
|
||||||
use crate::service::imaginary::Imaginary;
|
use crate::service::imaginary::Imaginary;
|
||||||
use crate::service::kaspersky::{Kaspersky, KasperskyIcap};
|
use crate::service::kaspersky::{Kaspersky, KasperskyIcap};
|
||||||
pub use crate::service::ldap::{Ldap, LdapAdmin};
|
pub use crate::service::ldap::{Ldap, LdapAdmin};
|
||||||
|
|
@ -59,6 +61,10 @@ pub trait ServiceTrait {
|
||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hidden(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
async fn spawn(
|
async fn spawn(
|
||||||
&self,
|
&self,
|
||||||
_docker: &Docker,
|
_docker: &Docker,
|
||||||
|
|
@ -340,6 +346,8 @@ pub enum ServiceType {
|
||||||
Oidc,
|
Oidc,
|
||||||
/// Configure Authentik as a SCIM server for Nextcloud
|
/// Configure Authentik as a SCIM server for Nextcloud
|
||||||
Scim,
|
Scim,
|
||||||
|
#[strum(serialize = "db", serialize = "db-gate")]
|
||||||
|
DbGate,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
|
|
@ -377,6 +385,7 @@ pub enum Service {
|
||||||
AuthentikSaml(AuthentikSaml),
|
AuthentikSaml(AuthentikSaml),
|
||||||
AuthentikOidc(AuthentikOidc),
|
AuthentikOidc(AuthentikOidc),
|
||||||
AuthentikScim(AuthentikScim),
|
AuthentikScim(AuthentikScim),
|
||||||
|
DbGate(DbGate),
|
||||||
Preset(PresetService),
|
Preset(PresetService),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -436,6 +445,7 @@ impl Service {
|
||||||
Service::Authentik(Authentik),
|
Service::Authentik(Authentik),
|
||||||
Service::AuthentikScim(AuthentikScim),
|
Service::AuthentikScim(AuthentikScim),
|
||||||
]),
|
]),
|
||||||
|
ServiceType::DbGate => Some(vec![Service::DbGate(DbGate)]),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
presets
|
presets
|
||||||
|
|
|
||||||
133
src/service/dbgate.rs
Normal file
133
src/service/dbgate.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
use std::io::Stdout;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
use crate::cloud::CloudOptions;
|
||||||
|
use crate::config::HazeConfig;
|
||||||
|
use crate::database::DatabaseFamily;
|
||||||
|
use crate::exec::exec;
|
||||||
|
use crate::image::pull_image;
|
||||||
|
use crate::mapping::Mapping;
|
||||||
|
use crate::service::ServiceTrait;
|
||||||
|
use bollard::Docker;
|
||||||
|
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
|
||||||
|
use bollard::query_parameters::CreateContainerOptions;
|
||||||
|
use maplit::hashmap;
|
||||||
|
use miette::IntoDiagnostic;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct DbGate;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ServiceTrait for DbGate {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"db"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hidden(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn spawn(
|
||||||
|
&self,
|
||||||
|
docker: &Docker,
|
||||||
|
cloud_id: &str,
|
||||||
|
network: &str,
|
||||||
|
config: &HazeConfig,
|
||||||
|
options: &CloudOptions,
|
||||||
|
) -> Result<Vec<String>> {
|
||||||
|
let image = "dbgate/dbgate:7.3.1";
|
||||||
|
pull_image(docker, image).await?;
|
||||||
|
let container_options = Some(CreateContainerOptions {
|
||||||
|
name: self.container_name(cloud_id),
|
||||||
|
..CreateContainerOptions::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let db_family = options.db.family();
|
||||||
|
|
||||||
|
let mut env = vec![
|
||||||
|
"CONNECTIONS=con1".into(),
|
||||||
|
"USAGE_ANALYTICS=false".into(),
|
||||||
|
"LOCAL_DBGATE_CLOUD=1".into(),
|
||||||
|
"LABEL_con1=haze".into(),
|
||||||
|
format!("ENGINE_con1={}", db_family.db_gate_driver()),
|
||||||
|
];
|
||||||
|
|
||||||
|
if db_family == DatabaseFamily::Sqlite {
|
||||||
|
env.extend(["FILE_con1=/haze-data/haze.db".into()]);
|
||||||
|
} else {
|
||||||
|
env.extend([
|
||||||
|
"SERVER_con1=db".into(),
|
||||||
|
format!("USER_con1={}", db_family.username()),
|
||||||
|
format!("PORT_con1={}", db_family.port()),
|
||||||
|
format!("DATABASE_con1={}", db_family.db()),
|
||||||
|
"PASSWORD_con1=haze".into(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if db_family == DatabaseFamily::Oracle {
|
||||||
|
env.push("SERVICE_NAME_con1=xe".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mapping = Mapping::new(
|
||||||
|
crate::mapping::MappingSourceType::WorkDir,
|
||||||
|
"data",
|
||||||
|
"/haze-data",
|
||||||
|
);
|
||||||
|
|
||||||
|
let config = ContainerCreateBody {
|
||||||
|
image: Some(image.into()),
|
||||||
|
host_config: Some(HostConfig {
|
||||||
|
network_mode: Some(network.to_string()),
|
||||||
|
binds: Some(vec![
|
||||||
|
mapping
|
||||||
|
.get_volume_arg(cloud_id, config, &config.sources_root)
|
||||||
|
.unwrap(),
|
||||||
|
]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
env: Some(env),
|
||||||
|
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(container_options, config)
|
||||||
|
.await
|
||||||
|
.into_diagnostic()?
|
||||||
|
.id;
|
||||||
|
docker.start_container(&id, None).await.into_diagnostic()?;
|
||||||
|
exec(
|
||||||
|
docker,
|
||||||
|
&id,
|
||||||
|
"root",
|
||||||
|
vec![
|
||||||
|
"sed",
|
||||||
|
"-i",
|
||||||
|
"s/attr(t,`title`,`Upgrade to Premium`)/attr(t,`style`,`display:none`)/",
|
||||||
|
"public/build/bundle.js",
|
||||||
|
],
|
||||||
|
Vec::<String>::new(),
|
||||||
|
None::<Stdout>,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(vec![id])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn container_name(&self, cloud_id: &str) -> Option<String> {
|
||||||
|
Some(format!("{}-db-gate", cloud_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proxy_port(&self) -> u16 {
|
||||||
|
3000
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue