feat(gb-infra): Add service management for MinIO, Stalwart, Zitadel, and NGINX with environment variable handling
This commit is contained in:
parent
bbb1657e42
commit
0473753001
11 changed files with 443 additions and 2 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -2926,6 +2926,17 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gb-infra"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ctrlc",
|
||||||
|
"dotenv",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gb-llm"
|
name = "gb-llm"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -18,7 +18,7 @@ members = [
|
||||||
"gb-document",
|
"gb-document",
|
||||||
"gb-file",
|
"gb-file",
|
||||||
"gb-llm",
|
"gb-llm",
|
||||||
"gb-calendar",
|
"gb-calendar", "gb-infra",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
@ -41,6 +41,7 @@ parking_lot = "0.12"
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
|
ctrlc = "3.2"
|
||||||
|
|
||||||
# Web framework and servers
|
# Web framework and servers
|
||||||
axum = { version = "0.7.9", features = ["ws", "multipart"] }
|
axum = { version = "0.7.9", features = ["ws", "multipart"] }
|
||||||
|
@ -136,4 +137,4 @@ docx = "1.1"
|
||||||
zip = "0.6"
|
zip = "0.6"
|
||||||
|
|
||||||
[workspace.metadata]
|
[workspace.metadata]
|
||||||
msrv = "1.70.0"
|
msrv = "1.70.0"
|
||||||
|
|
13
gb-infra/Cargo.toml
Normal file
13
gb-infra/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "gb-infra"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dotenv = { workspace = true }
|
||||||
|
ctrlc = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
9
gb-infra/src/lib.rs
Normal file
9
gb-infra/src/lib.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
pub mod manager;
|
||||||
|
pub mod utils;
|
||||||
|
pub mod services {
|
||||||
|
pub mod minio;
|
||||||
|
pub mod nginx;
|
||||||
|
pub mod postgresql;
|
||||||
|
pub mod stalwart;
|
||||||
|
pub mod zitadel;
|
||||||
|
}
|
60
gb-infra/src/manager.rs
Normal file
60
gb-infra/src/manager.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use crate::services::{zitadel, stalwart, minio, postgresql, nginx};
|
||||||
|
use dotenv::dotenv;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub struct ServiceManager {
|
||||||
|
services: Vec<Box<dyn Service>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
dotenv().ok();
|
||||||
|
ServiceManager {
|
||||||
|
services: vec![
|
||||||
|
Box::new(zitadel::Zitadel::new()),
|
||||||
|
Box::new(stalwart::Stalwart::new()),
|
||||||
|
Box::new(minio::MinIO::new()),
|
||||||
|
Box::new(postgresql::PostgreSQL::new()),
|
||||||
|
Box::new(nginx::NGINX::new()),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
for service in &mut self.services {
|
||||||
|
service.start().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
for service in &mut self.services {
|
||||||
|
service.stop().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) {
|
||||||
|
self.start();
|
||||||
|
let running = Arc::new(Mutex::new(true));
|
||||||
|
let running_clone = Arc::clone(&running);
|
||||||
|
|
||||||
|
ctrlc::set_handler(move || {
|
||||||
|
println!("Exiting service manager...");
|
||||||
|
let mut running = running_clone.lock().unwrap();
|
||||||
|
*running = false;
|
||||||
|
})
|
||||||
|
.expect("Failed to set Ctrl+C handler.");
|
||||||
|
|
||||||
|
while *running.lock().unwrap() {
|
||||||
|
thread::sleep(Duration::from_secs(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Service {
|
||||||
|
fn start(&mut self) -> Result<(), String>;
|
||||||
|
fn stop(&mut self) -> Result<(), String>;
|
||||||
|
}
|
54
gb-infra/src/services/minio.rs
Normal file
54
gb-infra/src/services/minio.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use crate::manager::Service;
|
||||||
|
use std::env;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use dotenv::dotenv;
|
||||||
|
|
||||||
|
pub struct MinIO {
|
||||||
|
env_vars: HashMap<String, String>,
|
||||||
|
process: Option<std::process::Child>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MinIO {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
dotenv().ok();
|
||||||
|
let env_vars = vec![
|
||||||
|
"MINIO_ROOT_USER",
|
||||||
|
"MINIO_ROOT_PASSWORD",
|
||||||
|
"MINIO_VOLUMES",
|
||||||
|
"MINIO_ADDRESS",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|key| env::var(key).ok().map(|value| (key.to_string(), value)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
MinIO {
|
||||||
|
env_vars,
|
||||||
|
process: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service for MinIO {
|
||||||
|
fn start(&mut self) -> Result<(), String> {
|
||||||
|
if self.process.is_some() {
|
||||||
|
return Err("MinIO is already running.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut command = Command::new("/opt/gbo/bin/minio");
|
||||||
|
for (key, value) in &self.env_vars {
|
||||||
|
command.env(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.process = Some(command.spawn().map_err(|e| e.to_string())?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) -> Result<(), String> {
|
||||||
|
if let Some(mut child) = self.process.take() {
|
||||||
|
child.kill().map_err(|e| e.to_string())?;
|
||||||
|
child.wait().map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
83
gb-infra/src/services/nginx.rs
Normal file
83
gb-infra/src/services/nginx.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use crate::manager::Service;
|
||||||
|
use std::env;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use dotenv::dotenv;
|
||||||
|
|
||||||
|
pub struct NGINX {
|
||||||
|
env_vars: HashMap<String, String>,
|
||||||
|
process: Option<std::process::Child>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NGINX {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
dotenv().ok();
|
||||||
|
let env_vars = vec![
|
||||||
|
"NGINX_ERROR_LOG",
|
||||||
|
"NGINX_ACCESS_LOG",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|key| env::var(key).ok().map(|value| (key.to_string(), value)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
NGINX {
|
||||||
|
env_vars,
|
||||||
|
process: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service for NGINX {
|
||||||
|
fn start(&mut self) -> Result<(), String> {
|
||||||
|
if self.process.is_some() {
|
||||||
|
return Err("NGINX is already running.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure NGINX logs
|
||||||
|
let error_log = self.env_vars.get("NGINX_ERROR_LOG").unwrap();
|
||||||
|
let access_log = self.env_vars.get("NGINX_ACCESS_LOG").unwrap();
|
||||||
|
|
||||||
|
// Update NGINX configuration
|
||||||
|
let nginx_conf = format!(
|
||||||
|
r#"
|
||||||
|
error_log {} debug;
|
||||||
|
access_log {};
|
||||||
|
events {{}}
|
||||||
|
http {{
|
||||||
|
server {{
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
location / {{
|
||||||
|
root /var/www/html;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
error_log, access_log
|
||||||
|
);
|
||||||
|
|
||||||
|
// Write the configuration to /etc/nginx/nginx.conf
|
||||||
|
std::fs::write("/etc/nginx/nginx.conf", nginx_conf).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Start NGINX
|
||||||
|
let mut command = Command::new("nginx");
|
||||||
|
self.process = Some(command.spawn().map_err(|e| e.to_string())?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) -> Result<(), String> {
|
||||||
|
if let Some(mut child) = self.process.take() {
|
||||||
|
child.kill().map_err(|e| e.to_string())?;
|
||||||
|
child.wait().map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop NGINX
|
||||||
|
Command::new("nginx")
|
||||||
|
.arg("-s")
|
||||||
|
.arg("stop")
|
||||||
|
.status()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
89
gb-infra/src/services/postgresql.rs
Normal file
89
gb-infra/src/services/postgresql.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use crate::manager::Service;
|
||||||
|
use std::env;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use dotenv::dotenv;
|
||||||
|
|
||||||
|
pub struct PostgreSQL {
|
||||||
|
env_vars: HashMap<String, String>,
|
||||||
|
process: Option<std::process::Child>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PostgreSQL {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
dotenv().ok();
|
||||||
|
let env_vars = vec![
|
||||||
|
"POSTGRES_DATA_DIR",
|
||||||
|
"POSTGRES_PORT",
|
||||||
|
"POSTGRES_USER",
|
||||||
|
"POSTGRES_PASSWORD",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|key| env::var(key).ok().map(|value| (key.to_string(), value)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
PostgreSQL {
|
||||||
|
env_vars,
|
||||||
|
process: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service for PostgreSQL {
|
||||||
|
fn start(&mut self) -> Result<(), String> {
|
||||||
|
if self.process.is_some() {
|
||||||
|
return Err("PostgreSQL is already running.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize PostgreSQL data directory if it doesn't exist
|
||||||
|
let data_dir = self.env_vars.get("POSTGRES_DATA_DIR").unwrap();
|
||||||
|
if !std::path::Path::new(data_dir).exists() {
|
||||||
|
Command::new("sudo")
|
||||||
|
.arg("-u")
|
||||||
|
.arg("postgres")
|
||||||
|
.arg("/usr/lib/postgresql/14/bin/initdb")
|
||||||
|
.arg("-D")
|
||||||
|
.arg(data_dir)
|
||||||
|
.status()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start PostgreSQL
|
||||||
|
let mut command = Command::new("sudo");
|
||||||
|
command
|
||||||
|
.arg("-u")
|
||||||
|
.arg("postgres")
|
||||||
|
.arg("/usr/lib/postgresql/14/bin/pg_ctl")
|
||||||
|
.arg("start")
|
||||||
|
.arg("-D")
|
||||||
|
.arg(data_dir);
|
||||||
|
|
||||||
|
for (key, value) in &self.env_vars {
|
||||||
|
command.env(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.process = Some(command.spawn().map_err(|e| e.to_string())?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) -> Result<(), String> {
|
||||||
|
if let Some(mut child) = self.process.take() {
|
||||||
|
child.kill().map_err(|e| e.to_string())?;
|
||||||
|
child.wait().map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop PostgreSQL
|
||||||
|
let data_dir = self.env_vars.get("POSTGRES_DATA_DIR").unwrap();
|
||||||
|
Command::new("sudo")
|
||||||
|
.arg("-u")
|
||||||
|
.arg("postgres")
|
||||||
|
.arg("/usr/lib/postgresql/14/bin/pg_ctl")
|
||||||
|
.arg("stop")
|
||||||
|
.arg("-D")
|
||||||
|
.arg(data_dir)
|
||||||
|
.status()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
58
gb-infra/src/services/stalwart.rs
Normal file
58
gb-infra/src/services/stalwart.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use crate::manager::Service;
|
||||||
|
use std::env;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use dotenv::dotenv;
|
||||||
|
|
||||||
|
pub struct Stalwart {
|
||||||
|
env_vars: HashMap<String, String>,
|
||||||
|
process: Option<std::process::Child>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stalwart {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
dotenv().ok();
|
||||||
|
let env_vars = vec![
|
||||||
|
"STALWART_LOG_LEVEL",
|
||||||
|
"STALWART_OAUTH_PROVIDER",
|
||||||
|
"STALWART_OAUTH_CLIENT_ID",
|
||||||
|
"STALWART_OAUTH_CLIENT_SECRET",
|
||||||
|
"STALWART_OAUTH_AUTHORIZATION_ENDPOINT",
|
||||||
|
"STALWART_OAUTH_TOKEN_ENDPOINT",
|
||||||
|
"STALWART_OAUTH_USERINFO_ENDPOINT",
|
||||||
|
"STALWART_OAUTH_SCOPE",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|key| env::var(key).ok().map(|value| (key.to_string(), value)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Stalwart {
|
||||||
|
env_vars,
|
||||||
|
process: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service for Stalwart {
|
||||||
|
fn start(&mut self) -> Result<(), String> {
|
||||||
|
if self.process.is_some() {
|
||||||
|
return Err("Stalwart Mail is already running.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut command = Command::new("/opt/gbo/bin/stalwart");
|
||||||
|
for (key, value) in &self.env_vars {
|
||||||
|
command.env(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.process = Some(command.spawn().map_err(|e| e.to_string())?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) -> Result<(), String> {
|
||||||
|
if let Some(mut child) = self.process.take() {
|
||||||
|
child.kill().map_err(|e| e.to_string())?;
|
||||||
|
child.wait().map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
63
gb-infra/src/services/zitadel.rs
Normal file
63
gb-infra/src/services/zitadel.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use crate::manager::Service;
|
||||||
|
use std::env;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use dotenv::dotenv;
|
||||||
|
|
||||||
|
pub struct Zitadel {
|
||||||
|
env_vars: HashMap<String, String>,
|
||||||
|
process: Option<std::process::Child>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zitadel {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
dotenv().ok();
|
||||||
|
let env_vars = vec![
|
||||||
|
"ZITADEL_DEFAULTINSTANCE_INSTANCENAME",
|
||||||
|
"ZITADEL_DEFAULTINSTANCE_ORG_NAME",
|
||||||
|
"ZITADEL_DATABASE_POSTGRES_HOST",
|
||||||
|
"ZITADEL_DATABASE_POSTGRES_PORT",
|
||||||
|
"ZITADEL_DATABASE_POSTGRES_DATABASE",
|
||||||
|
"ZITADEL_DATABASE_POSTGRES_USER_USERNAME",
|
||||||
|
"ZITADEL_DATABASE_POSTGRES_USER_PASSWORD",
|
||||||
|
"ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE",
|
||||||
|
"ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE",
|
||||||
|
"ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME",
|
||||||
|
"ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD",
|
||||||
|
"ZITADEL_EXTERNALSECURE",
|
||||||
|
"ZITADEL_MASTERKEY",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|key| env::var(key).ok().map(|value| (key.to_string(), value)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Zitadel {
|
||||||
|
env_vars,
|
||||||
|
process: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service for Zitadel {
|
||||||
|
fn start(&mut self) -> Result<(), String> {
|
||||||
|
if self.process.is_some() {
|
||||||
|
return Err("Zitadel is already running.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut command = Command::new("/opt/gbo/bin/zitadel");
|
||||||
|
for (key, value) in &self.env_vars {
|
||||||
|
command.env(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.process = Some(command.spawn().map_err(|e| e.to_string())?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) -> Result<(), String> {
|
||||||
|
if let Some(mut child) = self.process.take() {
|
||||||
|
child.kill().map_err(|e| e.to_string())?;
|
||||||
|
child.wait().map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
0
gb-infra/src/utils.rs
Normal file
0
gb-infra/src/utils.rs
Normal file
Loading…
Add table
Reference in a new issue