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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gb-infra"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ctrlc",
|
||||
"dotenv",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gb-llm"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -18,7 +18,7 @@ members = [
|
|||
"gb-document",
|
||||
"gb-file",
|
||||
"gb-llm",
|
||||
"gb-calendar",
|
||||
"gb-calendar", "gb-infra",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
|
@ -41,6 +41,7 @@ parking_lot = "0.12"
|
|||
bytes = "1.0"
|
||||
log = "0.4"
|
||||
env_logger = "0.10"
|
||||
ctrlc = "3.2"
|
||||
|
||||
# Web framework and servers
|
||||
axum = { version = "0.7.9", features = ["ws", "multipart"] }
|
||||
|
|
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