- Postgres updated to 18.
This commit is contained in:
parent
88a52f172e
commit
d970d48aa7
6 changed files with 770 additions and 377 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,3 +4,4 @@ target
|
||||||
work
|
work
|
||||||
*.out
|
*.out
|
||||||
bin
|
bin
|
||||||
|
botserver-stack
|
||||||
|
|
|
||||||
|
|
@ -38,3 +38,41 @@ valkey-cli -p 6379 monitor
|
||||||
|
|
||||||
- Prompt add-ons: Fill the file with info!, trace! and debug! macros.
|
- Prompt add-ons: Fill the file with info!, trace! and debug! macros.
|
||||||
-
|
-
|
||||||
|
|
||||||
|
|
||||||
|
# Zed Agents
|
||||||
|
```
|
||||||
|
"language_models": {
|
||||||
|
"openai_compatible": {
|
||||||
|
"Groq GPT 120b": {
|
||||||
|
"api_url": "https://api.groq.com/openai/v1",
|
||||||
|
"available_models": [
|
||||||
|
{
|
||||||
|
"name": "meta-llama/llama-4-scout-17b-16e-instruct",
|
||||||
|
"max_tokens": 30000,
|
||||||
|
"capabilities": {
|
||||||
|
"tools": true,
|
||||||
|
"images": false,
|
||||||
|
"parallel_tool_calls": false,
|
||||||
|
"prompt_cache_key": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "groq/compound",
|
||||||
|
"max_tokens": 70000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "openai/gpt-oss-120b",
|
||||||
|
"max_tokens": 8000,
|
||||||
|
"capabilities": {
|
||||||
|
"tools": true,
|
||||||
|
"images": false,
|
||||||
|
"parallel_tool_calls": false,
|
||||||
|
"prompt_cache_key": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$SCRIPT_DIR"
|
PROJECT_ROOT="$SCRIPT_DIR"
|
||||||
OUTPUT_FILE="$SCRIPT_DIR/prompt.out"
|
OUTPUT_FILE="/tmp/prompt.out"
|
||||||
rm $OUTPUT_FILE
|
rm $OUTPUT_FILE
|
||||||
echo "Please, fix this consolidated LLM Context" > "$OUTPUT_FILE"
|
echo "Please, fix this consolidated LLM Context" > "$OUTPUT_FILE"
|
||||||
|
|
||||||
prompts=(
|
prompts=(
|
||||||
"./prompts/dev/shared.md"
|
"./prompts/dev/platform/shared.md"
|
||||||
"./Cargo.toml"
|
"./Cargo.toml"
|
||||||
"./prompts/dev/fix.md"
|
"./prompts/dev/platform/fix-errors.md"
|
||||||
)
|
)
|
||||||
|
|
||||||
for file in "${prompts[@]}"; do
|
for file in "${prompts[@]}"; do
|
||||||
|
|
@ -21,7 +21,8 @@ dirs=(
|
||||||
#"auth"
|
#"auth"
|
||||||
#"automation"
|
#"automation"
|
||||||
#"basic"
|
#"basic"
|
||||||
"bot"
|
#"bot"
|
||||||
|
"bootstrap"
|
||||||
#"channels"
|
#"channels"
|
||||||
#"config"
|
#"config"
|
||||||
#"context"
|
#"context"
|
||||||
|
|
@ -49,11 +50,22 @@ done
|
||||||
echo "$PROJECT_ROOT/src/main.rs" >> "$OUTPUT_FILE"
|
echo "$PROJECT_ROOT/src/main.rs" >> "$OUTPUT_FILE"
|
||||||
cat "$PROJECT_ROOT/src/main.rs" >> "$OUTPUT_FILE"
|
cat "$PROJECT_ROOT/src/main.rs" >> "$OUTPUT_FILE"
|
||||||
|
|
||||||
cat "$PROJECT_ROOT/src/basic/keywords/hear_talk.rs" >> "$OUTPUT_FILE"
|
|
||||||
echo "$PROJECT_ROOT/src/basic/mod.rs">> "$OUTPUT_FILE"
|
|
||||||
cat "$PROJECT_ROOT/src/basic/mod.rs" >> "$OUTPUT_FILE"
|
|
||||||
|
|
||||||
|
|
||||||
echo "" >> "$OUTPUT_FILE"
|
echo "" >> "$OUTPUT_FILE"
|
||||||
|
|
||||||
cargo build --message-format=short 2>&1 | grep -E 'error' >> "$OUTPUT_FILE"
|
cargo build --message-format=short 2>&1 | grep -E 'error' >> "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
|
||||||
|
# Calculate and display token count (approximation: words * 1.3)
|
||||||
|
WORD_COUNT=$(wc -w < "$OUTPUT_FILE")
|
||||||
|
TOKEN_COUNT=$(echo "$WORD_COUNT * 1.3 / 1" | bc)
|
||||||
|
FILE_SIZE=$(wc -c < "$OUTPUT_FILE")
|
||||||
|
|
||||||
|
echo "" >> "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
echo "Approximate token count: $TOKEN_COUNT"
|
||||||
|
echo "Context size: $FILE_SIZE bytes"
|
||||||
|
|
||||||
|
cat "$OUTPUT_FILE" | xclip -selection clipboard
|
||||||
|
echo "Content copied to clipboard (xclip)"
|
||||||
|
rm -f "$OUTPUT_FILE"
|
||||||
|
|
|
||||||
4
gbot.sh
4
gbot.sh
|
|
@ -1,2 +1,2 @@
|
||||||
echo Starting General Bots...
|
|
||||||
npm run start
|
clear && cargo build && sudo RUST_BACKTRACE=1 ./target/debug/botserver
|
||||||
|
|
|
||||||
|
|
@ -424,10 +424,10 @@ impl BootstrapManager {
|
||||||
fn generate_password(&self) -> String {
|
fn generate_password(&self) -> String {
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
let mut rng = rand::rng();
|
let mut rng = rand::thread_rng();
|
||||||
(0..16)
|
(0..16)
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
let idx = rng.random_range(0..CHARSET.len());
|
let idx = rng.gen_range(0..CHARSET.len());
|
||||||
CHARSET[idx] as char
|
CHARSET[idx] as char
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
@ -479,10 +479,15 @@ impl BootstrapManager {
|
||||||
|
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
while start.elapsed().as_secs() < timeout_secs {
|
while start.elapsed().as_secs() < timeout_secs {
|
||||||
if TcpListener::bind((host, port)).is_err() {
|
match TcpListener::bind((host, port)) {
|
||||||
|
Ok(_) => {
|
||||||
|
thread::sleep(Duration::from_secs(1));
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
info!("Service {}:{} is ready", host, port);
|
info!("Service {}:{} is ready", host, port);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
thread::sleep(Duration::from_secs(1));
|
thread::sleep(Duration::from_secs(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,12 @@ impl PackageManager {
|
||||||
};
|
};
|
||||||
|
|
||||||
pm.register_components();
|
pm.register_components();
|
||||||
info!("PackageManager initialized with {} components in {:?} mode for tenant {}", pm.components.len(), pm.mode, pm.tenant);
|
info!(
|
||||||
|
"PackageManager initialized with {} components in {:?} mode for tenant {}",
|
||||||
|
pm.components.len(),
|
||||||
|
pm.mode,
|
||||||
|
pm.tenant
|
||||||
|
);
|
||||||
Ok(pm)
|
Ok(pm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,31 +173,37 @@ impl PackageManager {
|
||||||
required: true,
|
required: true,
|
||||||
ports: vec![5432],
|
ports: vec![5432],
|
||||||
dependencies: vec![],
|
dependencies: vec![],
|
||||||
linux_packages: vec!["wget".to_string(), "gnupg2".to_string(), "lsb-release".to_string(), "postgresql-common".to_string()],
|
linux_packages: vec!["wget".to_string()],
|
||||||
macos_packages: vec!["postgresql".to_string()],
|
macos_packages: vec!["wget".to_string()],
|
||||||
windows_packages: vec![],
|
windows_packages: vec![],
|
||||||
download_url: None,
|
download_url: Some("https://github.com/theseus-rs/postgresql-binaries/releases/download/18.0.0/postgresql-18.0.0-x86_64-unknown-linux-gnu.tar.gz".to_string()),
|
||||||
binary_name: Some("postgres".to_string()),
|
binary_name: Some("postgres".to_string()),
|
||||||
pre_install_cmds_linux: vec![
|
pre_install_cmds_linux: vec![],
|
||||||
"/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh".to_string(),
|
|
||||||
"apt-get update && apt-get install -y postgresql-16".to_string()
|
|
||||||
],
|
|
||||||
post_install_cmds_linux: vec![
|
post_install_cmds_linux: vec![
|
||||||
"sudo -u postgres psql -p 5432 -c \"CREATE USER default WITH PASSWORD 'defaultpass'\" || true".to_string(),
|
"tar -xzf postgresql-18.0.0-x86_64-unknown-linux-gnu.tar.gz".to_string(),
|
||||||
"sudo -u postgres psql -p 5432 -c \"CREATE DATABASE default_db OWNER default\" || true".to_string(),
|
"mv pgsql/* . && rm -rf pgsql".to_string(),
|
||||||
"sudo -u postgres psql -p 5432 -c \"GRANT ALL PRIVILEGES ON DATABASE default_db TO default\" || true".to_string()
|
"if [ ! -d \"{{DATA_PATH}}/pgdata\" ]; then ./initdb -D {{DATA_PATH}}/pgdata -U postgres; fi".to_string(),
|
||||||
|
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"data_directory = '{{DATA_PATH}}/pgdata'\" > {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
||||||
|
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"hba_file = '{{CONF_PATH}}/pg_hba.conf'\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
||||||
|
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"ident_file = '{{CONF_PATH}}/pg_ident.conf'\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
||||||
|
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"port = 5432\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
||||||
|
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"listen_addresses = '*'\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
||||||
|
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"log_directory = '{{LOGS_PATH}}'\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
||||||
|
"if [ ! -f \"{{CONF_PATH}}/postgresql.conf\" ]; then echo \"logging_collector = on\" >> {{CONF_PATH}}/postgresql.conf; fi".to_string(),
|
||||||
|
"if [ ! -f \"{{CONF_PATH}}/pg_hba.conf\" ]; then echo \"host all all all md5\" > {{CONF_PATH}}/pg_hba.conf; fi".to_string(),
|
||||||
|
"if [ ! -f \"{{CONF_PATH}}/pg_ident.conf\" ]; then touch {{CONF_PATH}}/pg_ident.conf; fi".to_string(),
|
||||||
|
"if [ ! -d \"{{DATA_PATH}}/pgdata\" ]; then ./pg_ctl -D {{DATA_PATH}}/pgdata -l {{LOGS_PATH}}/postgres.log start; sleep 5; ./psql -p 5432 -d postgres -c \"CREATE USER default WITH PASSWORD 'defaultpass'\"; ./psql -p 5432 -d postgres -c \"CREATE DATABASE default_db OWNER default\"; ./psql -p 5432 -d postgres -c \"GRANT ALL PRIVILEGES ON DATABASE default_db TO default\"; ./pg_ctl -D {{DATA_PATH}}/pgdata stop; fi".to_string()
|
||||||
],
|
],
|
||||||
pre_install_cmds_macos: vec![],
|
pre_install_cmds_macos: vec![],
|
||||||
post_install_cmds_macos: vec![
|
post_install_cmds_macos: vec![
|
||||||
"initdb -D {{DATA_PATH}}/pgdata".to_string(),
|
"tar -xzf postgresql-18.0-1-linux-x64-binaries.tar.gz".to_string(),
|
||||||
"sleep 5".to_string(),
|
"mv pgsql/* . && rm -rf pgsql".to_string(),
|
||||||
"psql -p 5432 -d postgres -c \"CREATE USER default WITH PASSWORD 'defaultpass'\" || true".to_string(),
|
"if [ ! -d \"{{DATA_PATH}}/pgdata\" ]; then ./initdb -D {{DATA_PATH}}/pgdata -U postgres; fi".to_string(),
|
||||||
"psql -p 5432 -d postgres -c \"CREATE DATABASE default_db OWNER default\" || true".to_string()
|
|
||||||
],
|
],
|
||||||
pre_install_cmds_windows: vec![],
|
pre_install_cmds_windows: vec![],
|
||||||
post_install_cmds_windows: vec![],
|
post_install_cmds_windows: vec![],
|
||||||
env_vars: HashMap::new(),
|
env_vars: HashMap::new(),
|
||||||
exec_cmd: "postgres -D {{DATA_PATH}}/pgdata -p 5432".to_string(),
|
exec_cmd: "./pg_ctl -D {{DATA_PATH}}/pgdata -l {{LOGS_PATH}}/postgres.log start".to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -421,7 +432,9 @@ impl PackageManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_table_editor(&mut self) {
|
fn register_table_editor(&mut self) {
|
||||||
self.components.insert("table-editor".to_string(), ComponentConfig {
|
self.components.insert(
|
||||||
|
"table-editor".to_string(),
|
||||||
|
ComponentConfig {
|
||||||
name: "table-editor".to_string(),
|
name: "table-editor".to_string(),
|
||||||
required: false,
|
required: false,
|
||||||
ports: vec![5757],
|
ports: vec![5757],
|
||||||
|
|
@ -439,11 +452,14 @@ impl PackageManager {
|
||||||
post_install_cmds_windows: vec![],
|
post_install_cmds_windows: vec![],
|
||||||
env_vars: HashMap::new(),
|
env_vars: HashMap::new(),
|
||||||
exec_cmd: "{{BIN_PATH}}/nocodb".to_string(),
|
exec_cmd: "{{BIN_PATH}}/nocodb".to_string(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_doc_editor(&mut self) {
|
fn register_doc_editor(&mut self) {
|
||||||
self.components.insert("doc-editor".to_string(), ComponentConfig {
|
self.components.insert(
|
||||||
|
"doc-editor".to_string(),
|
||||||
|
ComponentConfig {
|
||||||
name: "doc-editor".to_string(),
|
name: "doc-editor".to_string(),
|
||||||
required: false,
|
required: false,
|
||||||
ports: vec![9980],
|
ports: vec![9980],
|
||||||
|
|
@ -461,11 +477,14 @@ impl PackageManager {
|
||||||
post_install_cmds_windows: vec![],
|
post_install_cmds_windows: vec![],
|
||||||
env_vars: HashMap::new(),
|
env_vars: HashMap::new(),
|
||||||
exec_cmd: "coolwsd --config-file={{CONF_PATH}}/coolwsd.xml".to_string(),
|
exec_cmd: "coolwsd --config-file={{CONF_PATH}}/coolwsd.xml".to_string(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_desktop(&mut self) {
|
fn register_desktop(&mut self) {
|
||||||
self.components.insert("desktop".to_string(), ComponentConfig {
|
self.components.insert(
|
||||||
|
"desktop".to_string(),
|
||||||
|
ComponentConfig {
|
||||||
name: "desktop".to_string(),
|
name: "desktop".to_string(),
|
||||||
required: false,
|
required: false,
|
||||||
ports: vec![3389],
|
ports: vec![3389],
|
||||||
|
|
@ -483,11 +502,14 @@ impl PackageManager {
|
||||||
post_install_cmds_windows: vec![],
|
post_install_cmds_windows: vec![],
|
||||||
env_vars: HashMap::new(),
|
env_vars: HashMap::new(),
|
||||||
exec_cmd: "xrdp --nodaemon".to_string(),
|
exec_cmd: "xrdp --nodaemon".to_string(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_devtools(&mut self) {
|
fn register_devtools(&mut self) {
|
||||||
self.components.insert("devtools".to_string(), ComponentConfig {
|
self.components.insert(
|
||||||
|
"devtools".to_string(),
|
||||||
|
ComponentConfig {
|
||||||
name: "devtools".to_string(),
|
name: "devtools".to_string(),
|
||||||
required: false,
|
required: false,
|
||||||
ports: vec![],
|
ports: vec![],
|
||||||
|
|
@ -505,43 +527,57 @@ impl PackageManager {
|
||||||
post_install_cmds_windows: vec![],
|
post_install_cmds_windows: vec![],
|
||||||
env_vars: HashMap::new(),
|
env_vars: HashMap::new(),
|
||||||
exec_cmd: "".to_string(),
|
exec_cmd: "".to_string(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_bot(&mut self) {
|
fn register_bot(&mut self) {
|
||||||
self.components.insert("bot".to_string(), ComponentConfig {
|
self.components.insert(
|
||||||
|
"bot".to_string(),
|
||||||
|
ComponentConfig {
|
||||||
name: "bot".to_string(),
|
name: "bot".to_string(),
|
||||||
required: false,
|
required: false,
|
||||||
ports: vec![3000],
|
ports: vec![3000],
|
||||||
dependencies: vec![],
|
dependencies: vec![],
|
||||||
linux_packages: vec!["curl".to_string(), "gnupg".to_string(), "ca-certificates".to_string(), "git".to_string()],
|
linux_packages: vec![
|
||||||
|
"curl".to_string(),
|
||||||
|
"gnupg".to_string(),
|
||||||
|
"ca-certificates".to_string(),
|
||||||
|
"git".to_string(),
|
||||||
|
],
|
||||||
macos_packages: vec!["node".to_string()],
|
macos_packages: vec!["node".to_string()],
|
||||||
windows_packages: vec![],
|
windows_packages: vec![],
|
||||||
download_url: None,
|
download_url: None,
|
||||||
binary_name: None,
|
binary_name: None,
|
||||||
pre_install_cmds_linux: vec![
|
pre_install_cmds_linux: vec![
|
||||||
"curl -fsSL https://deb.nodesource.com/setup_22.x | bash -".to_string(),
|
"curl -fsSL https://deb.nodesource.com/setup_22.x | bash -".to_string(),
|
||||||
"apt-get update && apt-get install -y nodejs".to_string()
|
"apt-get update && apt-get install -y nodejs".to_string(),
|
||||||
],
|
],
|
||||||
post_install_cmds_linux: vec![],
|
post_install_cmds_linux: vec![],
|
||||||
pre_install_cmds_macos: vec![],
|
pre_install_cmds_macos: vec![],
|
||||||
post_install_cmds_macos: vec![],
|
post_install_cmds_macos: vec![],
|
||||||
pre_install_cmds_windows: vec![],
|
pre_install_cmds_windows: vec![],
|
||||||
post_install_cmds_windows: vec![],
|
post_install_cmds_windows: vec![],
|
||||||
env_vars: HashMap::from([
|
env_vars: HashMap::from([("DISPLAY".to_string(), ":99".to_string())]),
|
||||||
("DISPLAY".to_string(), ":99".to_string())
|
|
||||||
]),
|
|
||||||
exec_cmd: "".to_string(),
|
exec_cmd: "".to_string(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_system(&mut self) {
|
fn register_system(&mut self) {
|
||||||
self.components.insert("system".to_string(), ComponentConfig {
|
self.components.insert(
|
||||||
|
"system".to_string(),
|
||||||
|
ComponentConfig {
|
||||||
name: "system".to_string(),
|
name: "system".to_string(),
|
||||||
required: false,
|
required: false,
|
||||||
ports: vec![8000],
|
ports: vec![8000],
|
||||||
dependencies: vec![],
|
dependencies: vec![],
|
||||||
linux_packages: vec!["wget".to_string(), "curl".to_string(), "unzip".to_string(), "git".to_string()],
|
linux_packages: vec![
|
||||||
|
"wget".to_string(),
|
||||||
|
"curl".to_string(),
|
||||||
|
"unzip".to_string(),
|
||||||
|
"git".to_string(),
|
||||||
|
],
|
||||||
macos_packages: vec![],
|
macos_packages: vec![],
|
||||||
windows_packages: vec![],
|
windows_packages: vec![],
|
||||||
download_url: None,
|
download_url: None,
|
||||||
|
|
@ -554,7 +590,8 @@ impl PackageManager {
|
||||||
post_install_cmds_windows: vec![],
|
post_install_cmds_windows: vec![],
|
||||||
env_vars: HashMap::new(),
|
env_vars: HashMap::new(),
|
||||||
exec_cmd: "".to_string(),
|
exec_cmd: "".to_string(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_vector_db(&mut self) {
|
fn register_vector_db(&mut self) {
|
||||||
|
|
@ -580,7 +617,9 @@ impl PackageManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_host(&mut self) {
|
fn register_host(&mut self) {
|
||||||
self.components.insert("host".to_string(), ComponentConfig {
|
self.components.insert(
|
||||||
|
"host".to_string(),
|
||||||
|
ComponentConfig {
|
||||||
name: "host".to_string(),
|
name: "host".to_string(),
|
||||||
required: false,
|
required: false,
|
||||||
ports: vec![],
|
ports: vec![],
|
||||||
|
|
@ -605,13 +644,20 @@ impl PackageManager {
|
||||||
post_install_cmds_windows: vec![],
|
post_install_cmds_windows: vec![],
|
||||||
env_vars: HashMap::new(),
|
env_vars: HashMap::new(),
|
||||||
exec_cmd: "".to_string(),
|
exec_cmd: "".to_string(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn install(&self, component_name: &str) -> Result<()> {
|
pub fn install(&self, component_name: &str) -> Result<()> {
|
||||||
let component = self.components.get(component_name).context(format!("Component '{}' not found", component_name))?;
|
let component = self
|
||||||
|
.components
|
||||||
|
.get(component_name)
|
||||||
|
.context(format!("Component '{}' not found", component_name))?;
|
||||||
|
|
||||||
info!("Starting installation of component '{}' in {:?} mode", component_name, self.mode);
|
info!(
|
||||||
|
"Starting installation of component '{}' in {:?} mode",
|
||||||
|
component_name, self.mode
|
||||||
|
);
|
||||||
|
|
||||||
for dep in &component.dependencies {
|
for dep in &component.dependencies {
|
||||||
if !self.is_installed(dep) {
|
if !self.is_installed(dep) {
|
||||||
|
|
@ -625,19 +671,35 @@ impl PackageManager {
|
||||||
InstallMode::Container => self.install_container(component)?,
|
InstallMode::Container => self.install_container(component)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Component '{}' installation completed successfully", component_name);
|
info!(
|
||||||
|
"Component '{}' installation completed successfully",
|
||||||
|
component_name
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_local(&self, component: &ComponentConfig) -> Result<()> {
|
fn install_local(&self, component: &ComponentConfig) -> Result<()> {
|
||||||
info!("Installing component '{}' locally to {}", component.name, self.base_path.display());
|
info!(
|
||||||
|
"Installing component '{}' locally to {}",
|
||||||
|
component.name,
|
||||||
|
self.base_path.display()
|
||||||
|
);
|
||||||
|
|
||||||
self.create_directories(&component.name)?;
|
self.create_directories(&component.name)?;
|
||||||
|
|
||||||
let (pre_cmds, post_cmds) = match self.os_type {
|
let (pre_cmds, post_cmds) = match self.os_type {
|
||||||
OsType::Linux => (&component.pre_install_cmds_linux, &component.post_install_cmds_linux),
|
OsType::Linux => (
|
||||||
OsType::MacOS => (&component.pre_install_cmds_macos, &component.post_install_cmds_macos),
|
&component.pre_install_cmds_linux,
|
||||||
OsType::Windows => (&component.pre_install_cmds_windows, &component.post_install_cmds_windows),
|
&component.post_install_cmds_linux,
|
||||||
|
),
|
||||||
|
OsType::MacOS => (
|
||||||
|
&component.pre_install_cmds_macos,
|
||||||
|
&component.post_install_cmds_macos,
|
||||||
|
),
|
||||||
|
OsType::Windows => (
|
||||||
|
&component.pre_install_cmds_windows,
|
||||||
|
&component.post_install_cmds_windows,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.run_commands(pre_cmds, "local", &component.name)?;
|
self.run_commands(pre_cmds, "local", &component.name)?;
|
||||||
|
|
@ -661,10 +723,21 @@ impl PackageManager {
|
||||||
let container_name = format!("{}-{}", self.tenant, component.name);
|
let container_name = format!("{}-{}", self.tenant, component.name);
|
||||||
info!("Creating LXC container: {}", container_name);
|
info!("Creating LXC container: {}", container_name);
|
||||||
|
|
||||||
let output = Command::new("lxc").args(&["launch", "images:debian/12", &container_name, "-c", "security.privileged=true"]).output()?;
|
let output = Command::new("lxc")
|
||||||
|
.args(&[
|
||||||
|
"launch",
|
||||||
|
"images:debian/12",
|
||||||
|
&container_name,
|
||||||
|
"-c",
|
||||||
|
"security.privileged=true",
|
||||||
|
])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(anyhow::anyhow!("LXC container creation failed: {}", String::from_utf8_lossy(&output.stderr)));
|
return Err(anyhow::anyhow!(
|
||||||
|
"LXC container creation failed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(15));
|
std::thread::sleep(std::time::Duration::from_secs(15));
|
||||||
|
|
@ -672,9 +745,18 @@ impl PackageManager {
|
||||||
self.exec_in_container(&container_name, "mkdir -p /opt/gbo/{bin,data,conf,logs}")?;
|
self.exec_in_container(&container_name, "mkdir -p /opt/gbo/{bin,data,conf,logs}")?;
|
||||||
|
|
||||||
let (pre_cmds, post_cmds) = match self.os_type {
|
let (pre_cmds, post_cmds) = match self.os_type {
|
||||||
OsType::Linux => (&component.pre_install_cmds_linux, &component.post_install_cmds_linux),
|
OsType::Linux => (
|
||||||
OsType::MacOS => (&component.pre_install_cmds_macos, &component.post_install_cmds_macos),
|
&component.pre_install_cmds_linux,
|
||||||
OsType::Windows => (&component.pre_install_cmds_windows, &component.post_install_cmds_windows),
|
&component.post_install_cmds_linux,
|
||||||
|
),
|
||||||
|
OsType::MacOS => (
|
||||||
|
&component.pre_install_cmds_macos,
|
||||||
|
&component.post_install_cmds_macos,
|
||||||
|
),
|
||||||
|
OsType::Windows => (
|
||||||
|
&component.pre_install_cmds_windows,
|
||||||
|
&component.post_install_cmds_windows,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.run_commands(pre_cmds, &container_name, &component.name)?;
|
self.run_commands(pre_cmds, &container_name, &component.name)?;
|
||||||
|
|
@ -687,30 +769,52 @@ impl PackageManager {
|
||||||
|
|
||||||
if !packages.is_empty() {
|
if !packages.is_empty() {
|
||||||
let pkg_list = packages.join(" ");
|
let pkg_list = packages.join(" ");
|
||||||
self.exec_in_container(&container_name, &format!("apt-get update && apt-get install -y {}", pkg_list))?;
|
self.exec_in_container(
|
||||||
|
&container_name,
|
||||||
|
&format!("apt-get update && apt-get install -y {}", pkg_list),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(url) = &component.download_url {
|
if let Some(url) = &component.download_url {
|
||||||
self.download_in_container(&container_name, url, &component.name, component.binary_name.as_deref())?;
|
self.download_in_container(
|
||||||
|
&container_name,
|
||||||
|
url,
|
||||||
|
&component.name,
|
||||||
|
component.binary_name.as_deref(),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.run_commands(post_cmds, &container_name, &component.name)?;
|
self.run_commands(post_cmds, &container_name, &component.name)?;
|
||||||
|
|
||||||
self.exec_in_container(&container_name, "useradd --system --no-create-home --shell /bin/false gbuser")?;
|
self.exec_in_container(
|
||||||
|
&container_name,
|
||||||
|
"useradd --system --no-create-home --shell /bin/false gbuser",
|
||||||
|
)?;
|
||||||
self.mount_container_directories(&container_name, &component.name)?;
|
self.mount_container_directories(&container_name, &component.name)?;
|
||||||
|
|
||||||
if !component.exec_cmd.is_empty() {
|
if !component.exec_cmd.is_empty() {
|
||||||
self.create_container_service(&container_name, &component.name, &component.exec_cmd, &component.env_vars)?;
|
self.create_container_service(
|
||||||
|
&container_name,
|
||||||
|
&component.name,
|
||||||
|
&component.exec_cmd,
|
||||||
|
&component.env_vars,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setup_port_forwarding(&container_name, &component.ports)?;
|
self.setup_port_forwarding(&container_name, &component.ports)?;
|
||||||
|
|
||||||
info!("Container installation of '{}' completed in {}", component.name, container_name);
|
info!(
|
||||||
|
"Container installation of '{}' completed in {}",
|
||||||
|
component.name, container_name
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&self, component_name: &str) -> Result<()> {
|
pub fn remove(&self, component_name: &str) -> Result<()> {
|
||||||
let component = self.components.get(component_name).context(format!("Component '{}' not found", component_name))?;
|
let component = self
|
||||||
|
.components
|
||||||
|
.get(component_name)
|
||||||
|
.context(format!("Component '{}' not found", component_name))?;
|
||||||
|
|
||||||
info!("Removing component: {}", component_name);
|
info!("Removing component: {}", component_name);
|
||||||
|
|
||||||
|
|
@ -724,23 +828,30 @@ impl PackageManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_local(&self, component: &ComponentConfig) -> Result<()> {
|
fn remove_local(&self, component: &ComponentConfig) -> Result<()> {
|
||||||
|
if component.name == "tables" {
|
||||||
|
// Stop PostgreSQL if running
|
||||||
|
let bin_path = self.base_path.join("bin").join(&component.name);
|
||||||
|
let data_path = self.base_path.join("data").join(&component.name);
|
||||||
|
|
||||||
|
let _ = Command::new(bin_path.join("pg_ctl"))
|
||||||
|
.args(&["-D", data_path.join("pgdata").to_str().unwrap(), "stop"])
|
||||||
|
.output();
|
||||||
|
}
|
||||||
|
|
||||||
if self.os_type == OsType::Linux {
|
if self.os_type == OsType::Linux {
|
||||||
let _ = Command::new("systemctl").args(&["stop", &format!("{}.service", component.name)]).output();
|
let _ = Command::new("systemctl")
|
||||||
let _ = Command::new("systemctl").args(&["disable", &format!("{}.service", component.name)]).output();
|
.args(&["stop", &format!("{}.service", component.name)])
|
||||||
|
.output();
|
||||||
|
let _ = Command::new("systemctl")
|
||||||
|
.args(&["disable", &format!("{}.service", component.name)])
|
||||||
|
.output();
|
||||||
let service_path = format!("/etc/systemd/system/{}.service", component.name);
|
let service_path = format!("/etc/systemd/system/{}.service", component.name);
|
||||||
let _ = std::fs::remove_file(service_path);
|
let _ = std::fs::remove_file(service_path);
|
||||||
let _ = Command::new("systemctl").args(&["daemon-reload"]).output();
|
let _ = Command::new("systemctl").args(&["daemon-reload"]).output();
|
||||||
}
|
}
|
||||||
|
|
||||||
let bin_path = self.base_path.join("bin").join(&component.name);
|
let bin_path = self.base_path.join("bin").join(&component.name);
|
||||||
let data_path = self.base_path.join("data").join(&component.name);
|
|
||||||
let conf_path = self.base_path.join("conf").join(&component.name);
|
|
||||||
let logs_path = self.base_path.join("logs").join(&component.name);
|
|
||||||
|
|
||||||
let _ = std::fs::remove_dir_all(bin_path);
|
let _ = std::fs::remove_dir_all(bin_path);
|
||||||
let _ = std::fs::remove_dir_all(data_path);
|
|
||||||
let _ = std::fs::remove_dir_all(conf_path);
|
|
||||||
let _ = std::fs::remove_dir_all(logs_path);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -748,16 +859,20 @@ impl PackageManager {
|
||||||
fn remove_container(&self, component: &ComponentConfig) -> Result<()> {
|
fn remove_container(&self, component: &ComponentConfig) -> Result<()> {
|
||||||
let container_name = format!("{}-{}", self.tenant, component.name);
|
let container_name = format!("{}-{}", self.tenant, component.name);
|
||||||
|
|
||||||
let _ = Command::new("lxc").args(&["stop", &container_name]).output();
|
let _ = Command::new("lxc")
|
||||||
let output = Command::new("lxc").args(&["delete", &container_name]).output()?;
|
.args(&["stop", &container_name])
|
||||||
|
.output();
|
||||||
|
let output = Command::new("lxc")
|
||||||
|
.args(&["delete", &container_name])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
warn!("Container deletion had issues: {}", String::from_utf8_lossy(&output.stderr));
|
warn!(
|
||||||
|
"Container deletion had issues: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let host_base = format!("/opt/gbo/tenants/{}/{}", self.tenant, component.name);
|
|
||||||
let _ = std::fs::remove_dir_all(host_base);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -773,7 +888,18 @@ impl PackageManager {
|
||||||
}
|
}
|
||||||
InstallMode::Container => {
|
InstallMode::Container => {
|
||||||
let container_name = format!("{}-{}", self.tenant, component_name);
|
let container_name = format!("{}-{}", self.tenant, component_name);
|
||||||
Command::new("lxc").args(&["list", &container_name, "--format=json"]).output().map(|o| o.status.success()).unwrap_or(false)
|
let output = Command::new("lxc")
|
||||||
|
.args(&["list", &container_name, "--format=json"])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse JSON output to check if container exists and is running
|
||||||
|
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||||
|
!output_str.contains("\"name\":\"") || output_str.contains("\"status\":\"Stopped\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -782,7 +908,8 @@ impl PackageManager {
|
||||||
let dirs = ["bin", "data", "conf", "logs"];
|
let dirs = ["bin", "data", "conf", "logs"];
|
||||||
for dir in &dirs {
|
for dir in &dirs {
|
||||||
let path = self.base_path.join(dir).join(component);
|
let path = self.base_path.join(dir).join(component);
|
||||||
std::fs::create_dir_all(&path).context(format!("Failed to create directory: {:?}", path))?;
|
std::fs::create_dir_all(&path)
|
||||||
|
.context(format!("Failed to create directory: {:?}", path))?;
|
||||||
trace!("Created directory: {:?}", path);
|
trace!("Created directory: {:?}", path);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -799,17 +926,33 @@ impl PackageManager {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Installing {} system packages for component '{}'", packages.len(), component.name);
|
info!(
|
||||||
|
"Installing {} system packages for component '{}'",
|
||||||
|
packages.len(),
|
||||||
|
component.name
|
||||||
|
);
|
||||||
|
|
||||||
match self.os_type {
|
match self.os_type {
|
||||||
OsType::Linux => {
|
OsType::Linux => {
|
||||||
let output = Command::new("apt-get").args(&["install", "-y"]).args(packages).output()?;
|
let output = Command::new("apt-get").args(&["update"]).output()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
warn!("apt-get update had issues");
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new("apt-get")
|
||||||
|
.args(&["install", "-y"])
|
||||||
|
.args(packages)
|
||||||
|
.output()?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
warn!("Some packages may have failed to install");
|
warn!("Some packages may have failed to install");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OsType::MacOS => {
|
OsType::MacOS => {
|
||||||
let output = Command::new("brew").args(&["install"]).args(packages).output()?;
|
let output = Command::new("brew")
|
||||||
|
.args(&["install"])
|
||||||
|
.args(packages)
|
||||||
|
.output()?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
warn!("Homebrew installation had warnings");
|
warn!("Homebrew installation had warnings");
|
||||||
}
|
}
|
||||||
|
|
@ -822,12 +965,27 @@ impl PackageManager {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn download_and_install(&self, url: &str, component: &str, binary_name: Option<&str>) -> Result<()> {
|
fn download_and_install(
|
||||||
|
&self,
|
||||||
|
url: &str,
|
||||||
|
component: &str,
|
||||||
|
binary_name: Option<&str>,
|
||||||
|
) -> Result<()> {
|
||||||
let bin_path = self.base_path.join("bin").join(component);
|
let bin_path = self.base_path.join("bin").join(component);
|
||||||
let temp_file = bin_path.join("download.tmp");
|
|
||||||
|
|
||||||
info!("Downloading from: {}", url);
|
// Ensure the bin directory exists
|
||||||
let output = Command::new("wget").args(&["-O", temp_file.to_str().unwrap(), url]).output()?;
|
std::fs::create_dir_all(&bin_path)?;
|
||||||
|
|
||||||
|
let filename = url.split('/').last().unwrap_or("download.tmp");
|
||||||
|
let temp_file = bin_path.join(filename);
|
||||||
|
|
||||||
|
info!("Downloading from: {} to {:?}", url, temp_file);
|
||||||
|
|
||||||
|
// Download to the component's bin directory
|
||||||
|
let output = Command::new("wget")
|
||||||
|
.current_dir(&bin_path)
|
||||||
|
.args(&["-O", temp_file.to_str().unwrap(), url])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(anyhow::anyhow!("Download failed from URL: {}", url));
|
return Err(anyhow::anyhow!("Download failed from URL: {}", url));
|
||||||
|
|
@ -835,11 +993,33 @@ impl PackageManager {
|
||||||
|
|
||||||
if url.ends_with(".tar.gz") || url.ends_with(".tgz") {
|
if url.ends_with(".tar.gz") || url.ends_with(".tgz") {
|
||||||
trace!("Extracting tar.gz archive to {:?}", bin_path);
|
trace!("Extracting tar.gz archive to {:?}", bin_path);
|
||||||
Command::new("tar").args(&["-xzf", temp_file.to_str().unwrap(), "-C", bin_path.to_str().unwrap()]).output()?;
|
let output = Command::new("tar")
|
||||||
|
.current_dir(&bin_path)
|
||||||
|
.args(&["-xzf", temp_file.to_str().unwrap()])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Extraction failed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the downloaded archive
|
||||||
std::fs::remove_file(&temp_file)?;
|
std::fs::remove_file(&temp_file)?;
|
||||||
} else if url.ends_with(".zip") {
|
} else if url.ends_with(".zip") {
|
||||||
trace!("Extracting zip archive to {:?}", bin_path);
|
trace!("Extracting zip archive to {:?}", bin_path);
|
||||||
Command::new("unzip").args(&[temp_file.to_str().unwrap(), "-d", bin_path.to_str().unwrap()]).output()?;
|
let output = Command::new("unzip")
|
||||||
|
.current_dir(&bin_path)
|
||||||
|
.args(&["-o", temp_file.to_str().unwrap()])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Extraction failed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
));
|
||||||
|
}
|
||||||
std::fs::remove_file(&temp_file)?;
|
std::fs::remove_file(&temp_file)?;
|
||||||
} else if let Some(name) = binary_name {
|
} else if let Some(name) = binary_name {
|
||||||
let final_path = bin_path.join(name);
|
let final_path = bin_path.join(name);
|
||||||
|
|
@ -856,76 +1036,107 @@ impl PackageManager {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_service_file(&self, component: &str, exec_cmd: &str, env_vars: &HashMap<String, String>) -> Result<()> {
|
fn create_service_file(
|
||||||
|
&self,
|
||||||
|
component: &str,
|
||||||
|
exec_cmd: &str,
|
||||||
|
env_vars: &HashMap<String, String>,
|
||||||
|
) -> Result<()> {
|
||||||
let service_path = format!("/etc/systemd/system/{}.service", component);
|
let service_path = format!("/etc/systemd/system/{}.service", component);
|
||||||
|
|
||||||
let bin_path = self.base_path.join("bin").join(component).to_string_lossy().to_string();
|
let bin_path = self.base_path.join("bin").join(component);
|
||||||
let data_path = self.base_path.join("data").join(component).to_string_lossy().to_string();
|
let data_path = self.base_path.join("data").join(component);
|
||||||
let conf_path = self.base_path.join("conf").join(component).to_string_lossy().to_string();
|
let conf_path = self.base_path.join("conf").join(component);
|
||||||
let logs_path = self.base_path.join("logs").join(component).to_string_lossy().to_string();
|
let logs_path = self.base_path.join("logs").join(component);
|
||||||
|
|
||||||
|
// Ensure all directories exist
|
||||||
|
std::fs::create_dir_all(&bin_path)?;
|
||||||
|
std::fs::create_dir_all(&data_path)?;
|
||||||
|
std::fs::create_dir_all(&conf_path)?;
|
||||||
|
std::fs::create_dir_all(&logs_path)?;
|
||||||
|
|
||||||
let rendered_cmd = exec_cmd
|
let rendered_cmd = exec_cmd
|
||||||
.replace("{{BIN_PATH}}", &bin_path)
|
.replace("{{BIN_PATH}}", &bin_path.to_string_lossy())
|
||||||
.replace("{{DATA_PATH}}", &data_path)
|
.replace("{{DATA_PATH}}", &data_path.to_string_lossy())
|
||||||
.replace("{{CONF_PATH}}", &conf_path)
|
.replace("{{CONF_PATH}}", &conf_path.to_string_lossy())
|
||||||
.replace("{{LOGS_PATH}}", &logs_path);
|
.replace("{{LOGS_PATH}}", &logs_path.to_string_lossy());
|
||||||
|
|
||||||
let mut env_section = String::new();
|
let mut env_section = String::new();
|
||||||
for (key, value) in env_vars {
|
for (key, value) in env_vars {
|
||||||
let rendered_value = value.replace("{{DATA_PATH}}", &data_path);
|
let rendered_value = value
|
||||||
env_section.push_str(&format!("Environment=\"{}={}\"\n", key, rendered_value));
|
.replace("{{DATA_PATH}}", &data_path.to_string_lossy())
|
||||||
|
.replace("{{BIN_PATH}}", &bin_path.to_string_lossy())
|
||||||
|
.replace("{{CONF_PATH}}", &conf_path.to_string_lossy())
|
||||||
|
.replace("{{LOGS_PATH}}", &logs_path.to_string_lossy());
|
||||||
|
env_section.push_str(&format!("Environment={}={}\n", key, rendered_value));
|
||||||
}
|
}
|
||||||
|
|
||||||
let service_content = format!("[Unit]\nDescription={} Service\nAfter=network.target\n\n[Service]\nType=simple\n{}ExecStart={}\nWorkingDirectory={}\nRestart=always\nRestartSec=10\nUser=root\n\n[Install]\nWantedBy=multi-user.target\n", component, env_section, rendered_cmd, data_path);
|
let service_content = format!(
|
||||||
|
"[Unit]\nDescription={} Service\nAfter=network.target\n\n[Service]\nType=simple\n{}ExecStart={}\nWorkingDirectory={}\nRestart=always\nRestartSec=10\nUser=root\n\n[Install]\nWantedBy=multi-user.target\n",
|
||||||
|
component, env_section, rendered_cmd, data_path.to_string_lossy()
|
||||||
|
);
|
||||||
|
|
||||||
std::fs::write(&service_path, service_content)?;
|
std::fs::write(&service_path, service_content)?;
|
||||||
|
|
||||||
Command::new("systemctl").args(&["daemon-reload"]).output()?;
|
Command::new("systemctl")
|
||||||
Command::new("systemctl").args(&["enable", &format!("{}.service", component)]).output()?;
|
.args(&["daemon-reload"])
|
||||||
Command::new("systemctl").args(&["start", &format!("{}.service", component)]).output()?;
|
.output()?;
|
||||||
|
Command::new("systemctl")
|
||||||
|
.args(&["enable", &format!("{}.service", component)])
|
||||||
|
.output()?;
|
||||||
|
Command::new("systemctl")
|
||||||
|
.args(&["start", &format!("{}.service", component)])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
info!("Created and started systemd service: {}.service", component);
|
info!("Created and started systemd service: {}.service", component);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_commands(&self, commands: &[String], target: &str, component: &str) -> Result<()> {
|
fn run_commands(&self, commands: &[String], target: &str, component: &str) -> Result<()> {
|
||||||
for cmd in commands {
|
|
||||||
let bin_path = if target == "local" {
|
let bin_path = if target == "local" {
|
||||||
self.base_path.join("bin").join(component).to_string_lossy().to_string()
|
self.base_path.join("bin").join(component)
|
||||||
} else {
|
} else {
|
||||||
"/opt/gbo/bin".to_string()
|
PathBuf::from("/opt/gbo/bin")
|
||||||
};
|
};
|
||||||
|
|
||||||
let data_path = if target == "local" {
|
let data_path = if target == "local" {
|
||||||
self.base_path.join("data").join(component).to_string_lossy().to_string()
|
self.base_path.join("data").join(component)
|
||||||
} else {
|
} else {
|
||||||
"/opt/gbo/data".to_string()
|
PathBuf::from("/opt/gbo/data")
|
||||||
};
|
};
|
||||||
|
|
||||||
let conf_path = if target == "local" {
|
let conf_path = if target == "local" {
|
||||||
self.base_path.join("conf").join(component).to_string_lossy().to_string()
|
self.base_path.join("conf").join(component)
|
||||||
} else {
|
} else {
|
||||||
"/opt/gbo/conf".to_string()
|
PathBuf::from("/opt/gbo/conf")
|
||||||
};
|
};
|
||||||
|
|
||||||
let logs_path = if target == "local" {
|
let logs_path = if target == "local" {
|
||||||
self.base_path.join("logs").join(component).to_string_lossy().to_string()
|
self.base_path.join("logs").join(component)
|
||||||
} else {
|
} else {
|
||||||
"/opt/gbo/logs".to_string()
|
PathBuf::from("/opt/gbo/logs")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for cmd in commands {
|
||||||
let rendered_cmd = cmd
|
let rendered_cmd = cmd
|
||||||
.replace("{{BIN_PATH}}", &bin_path)
|
.replace("{{BIN_PATH}}", &bin_path.to_string_lossy())
|
||||||
.replace("{{DATA_PATH}}", &data_path)
|
.replace("{{DATA_PATH}}", &data_path.to_string_lossy())
|
||||||
.replace("{{CONF_PATH}}", &conf_path)
|
.replace("{{CONF_PATH}}", &conf_path.to_string_lossy())
|
||||||
.replace("{{LOGS_PATH}}", &logs_path);
|
.replace("{{LOGS_PATH}}", &logs_path.to_string_lossy());
|
||||||
|
|
||||||
trace!("Executing command: {}", rendered_cmd);
|
trace!("Executing command: {}", rendered_cmd);
|
||||||
|
|
||||||
if target == "local" {
|
if target == "local" {
|
||||||
let output = Command::new("bash").args(&["-c", &rendered_cmd]).output()?;
|
// Run commands in the component's bin directory
|
||||||
|
let output = Command::new("bash")
|
||||||
|
.current_dir(&bin_path)
|
||||||
|
.args(&["-c", &rendered_cmd])
|
||||||
|
.output()?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
warn!("Command had non-zero exit: {}", String::from_utf8_lossy(&output.stderr));
|
warn!(
|
||||||
|
"Command had non-zero exit: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.exec_in_container(target, &rendered_cmd)?;
|
self.exec_in_container(target, &rendered_cmd)?;
|
||||||
|
|
@ -936,24 +1147,38 @@ impl PackageManager {
|
||||||
|
|
||||||
fn exec_in_container(&self, container: &str, command: &str) -> Result<()> {
|
fn exec_in_container(&self, container: &str, command: &str) -> Result<()> {
|
||||||
debug!("Executing in container {}: {}", container, command);
|
debug!("Executing in container {}: {}", container, command);
|
||||||
let output = Command::new("lxc").args(&["exec", container, "--", "bash", "-c", command]).output()?;
|
let output = Command::new("lxc")
|
||||||
|
.args(&["exec", container, "--", "bash", "-c", command])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
warn!("Container command failed: {}", String::from_utf8_lossy(&output.stderr));
|
warn!(
|
||||||
|
"Container command failed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn download_in_container(&self, container: &str, url: &str, _component: &str, binary_name: Option<&str>) -> Result<()> {
|
fn download_in_container(
|
||||||
|
&self,
|
||||||
|
container: &str,
|
||||||
|
url: &str,
|
||||||
|
component: &str,
|
||||||
|
binary_name: Option<&str>,
|
||||||
|
) -> Result<()> {
|
||||||
let download_cmd = format!("wget -O /tmp/download.tmp {}", url);
|
let download_cmd = format!("wget -O /tmp/download.tmp {}", url);
|
||||||
self.exec_in_container(container, &download_cmd)?;
|
self.exec_in_container(container, &download_cmd)?;
|
||||||
|
|
||||||
if url.ends_with(".tar.gz") || url.ends_with(".tgz") {
|
if url.ends_with(".tar.gz") || url.ends_with(".tgz") {
|
||||||
self.exec_in_container(container, "tar -xzf /tmp/download.tmp -C /opt/gbo/bin")?;
|
self.exec_in_container(container, "tar -xzf /tmp/download.tmp -C /opt/gbo/bin")?;
|
||||||
} else if url.ends_with(".zip") {
|
} else if url.ends_with(".zip") {
|
||||||
self.exec_in_container(container, "unzip /tmp/download.tmp -d /opt/gbo/bin")?;
|
self.exec_in_container(container, "unzip -o /tmp/download.tmp -d /opt/gbo/bin")?;
|
||||||
} else if let Some(name) = binary_name {
|
} else if let Some(name) = binary_name {
|
||||||
let mv_cmd = format!("mv /tmp/download.tmp /opt/gbo/bin/{} && chmod +x /opt/gbo/bin/{}", name, name);
|
let mv_cmd = format!(
|
||||||
|
"mv /tmp/download.tmp /opt/gbo/bin/{} && chmod +x /opt/gbo/bin/{}",
|
||||||
|
name, name
|
||||||
|
);
|
||||||
self.exec_in_container(container, &mv_cmd)?;
|
self.exec_in_container(container, &mv_cmd)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -968,19 +1193,47 @@ impl PackageManager {
|
||||||
let host_path = format!("{}/{}", host_base, dir);
|
let host_path = format!("{}/{}", host_base, dir);
|
||||||
std::fs::create_dir_all(&host_path)?;
|
std::fs::create_dir_all(&host_path)?;
|
||||||
|
|
||||||
let device_name = format!("{}{}", component, dir);
|
let device_name = format!("{}-{}", component, dir);
|
||||||
let container_path = format!("/opt/gbo/{}", dir);
|
let container_path = format!("/opt/gbo/{}", dir);
|
||||||
|
|
||||||
let _ = Command::new("lxc").args(&["config", "device", "remove", container, &device_name]).output();
|
let _ = Command::new("lxc")
|
||||||
|
.args(&["config", "device", "remove", container, &device_name])
|
||||||
|
.output();
|
||||||
|
|
||||||
Command::new("lxc").args(&["config", "device", "add", container, &device_name, "disk", &format!("source={}", host_path), &format!("path={}", container_path)]).output()?;
|
let output = Command::new("lxc")
|
||||||
|
.args(&[
|
||||||
|
"config",
|
||||||
|
"device",
|
||||||
|
"add",
|
||||||
|
container,
|
||||||
|
&device_name,
|
||||||
|
"disk",
|
||||||
|
&format!("source={}", host_path),
|
||||||
|
&format!("path={}", container_path),
|
||||||
|
])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
trace!("Mounted {} to {} in container {}", host_path, container_path, container);
|
if !output.status.success() {
|
||||||
|
warn!("Failed to mount {} in container {}", dir, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Mounted {} to {} in container {}",
|
||||||
|
host_path,
|
||||||
|
container_path,
|
||||||
|
container
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_container_service(&self, container: &str, component: &str, exec_cmd: &str, env_vars: &HashMap<String, String>) -> Result<()> {
|
fn create_container_service(
|
||||||
|
&self,
|
||||||
|
container: &str,
|
||||||
|
component: &str,
|
||||||
|
exec_cmd: &str,
|
||||||
|
env_vars: &HashMap<String, String>,
|
||||||
|
) -> Result<()> {
|
||||||
let rendered_cmd = exec_cmd
|
let rendered_cmd = exec_cmd
|
||||||
.replace("{{BIN_PATH}}", "/opt/gbo/bin")
|
.replace("{{BIN_PATH}}", "/opt/gbo/bin")
|
||||||
.replace("{{DATA_PATH}}", "/opt/gbo/data")
|
.replace("{{DATA_PATH}}", "/opt/gbo/data")
|
||||||
|
|
@ -989,16 +1242,34 @@ impl PackageManager {
|
||||||
|
|
||||||
let mut env_section = String::new();
|
let mut env_section = String::new();
|
||||||
for (key, value) in env_vars {
|
for (key, value) in env_vars {
|
||||||
let rendered_value = value.replace("{{DATA_PATH}}", "/opt/gbo/data");
|
let rendered_value = value
|
||||||
env_section.push_str(&format!("Environment=\"{}={}\"\n", key, rendered_value));
|
.replace("{{DATA_PATH}}", "/opt/gbo/data")
|
||||||
|
.replace("{{BIN_PATH}}", "/opt/gbo/bin")
|
||||||
|
.replace("{{CONF_PATH}}", "/opt/gbo/conf")
|
||||||
|
.replace("{{LOGS_PATH}}", "/opt/gbo/logs");
|
||||||
|
env_section.push_str(&format!("Environment={}={}\n", key, rendered_value));
|
||||||
}
|
}
|
||||||
|
|
||||||
let service_content = format!("[Unit]\nDescription={} Service\nAfter=network.target\n\n[Service]\nType=simple\n{}ExecStart={}\nWorkingDirectory=/opt/gbo/data\nRestart=always\nRestartSec=10\nUser=root\n\n[Install]\nWantedBy=multi-user.target\n", component, env_section, rendered_cmd);
|
let service_content = format!(
|
||||||
|
"[Unit]\nDescription={} Service\nAfter=network.target\n\n[Service]\nType=simple\n{}ExecStart={}\nWorkingDirectory=/opt/gbo/data\nRestart=always\nRestartSec=10\nUser=root\n\n[Install]\nWantedBy=multi-user.target\n",
|
||||||
|
component, env_section, rendered_cmd
|
||||||
|
);
|
||||||
|
|
||||||
let service_file = format!("/tmp/{}.service", component);
|
let service_file = format!("/tmp/{}.service", component);
|
||||||
std::fs::write(&service_file, &service_content)?;
|
std::fs::write(&service_file, &service_content)?;
|
||||||
|
|
||||||
Command::new("lxc").args(&["file", "push", &service_file, &format!("{}/etc/systemd/system/{}.service", container, component)]).output()?;
|
let output = Command::new("lxc")
|
||||||
|
.args(&[
|
||||||
|
"file",
|
||||||
|
"push",
|
||||||
|
&service_file,
|
||||||
|
&format!("{}/etc/systemd/system/{}.service", container, component),
|
||||||
|
])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
warn!("Failed to push service file to container");
|
||||||
|
}
|
||||||
|
|
||||||
self.exec_in_container(container, "systemctl daemon-reload")?;
|
self.exec_in_container(container, "systemctl daemon-reload")?;
|
||||||
self.exec_in_container(container, &format!("systemctl enable {}", component))?;
|
self.exec_in_container(container, &format!("systemctl enable {}", component))?;
|
||||||
|
|
@ -1006,7 +1277,10 @@ impl PackageManager {
|
||||||
|
|
||||||
std::fs::remove_file(&service_file)?;
|
std::fs::remove_file(&service_file)?;
|
||||||
|
|
||||||
info!("Created and started service in container {}: {}", container, component);
|
info!(
|
||||||
|
"Created and started service in container {}: {}",
|
||||||
|
container, component
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1014,11 +1288,32 @@ impl PackageManager {
|
||||||
for port in ports {
|
for port in ports {
|
||||||
let device_name = format!("port-{}", port);
|
let device_name = format!("port-{}", port);
|
||||||
|
|
||||||
let _ = Command::new("lxc").args(&["config", "device", "remove", container, &device_name]).output();
|
let _ = Command::new("lxc")
|
||||||
|
.args(&["config", "device", "remove", container, &device_name])
|
||||||
|
.output();
|
||||||
|
|
||||||
Command::new("lxc").args(&["config", "device", "add", container, &device_name, "proxy", &format!("listen=tcp:0.0.0.0:{}", port), &format!("connect=tcp:127.0.0.1:{}", port)]).output()?;
|
let output = Command::new("lxc")
|
||||||
|
.args(&[
|
||||||
|
"config",
|
||||||
|
"device",
|
||||||
|
"add",
|
||||||
|
container,
|
||||||
|
&device_name,
|
||||||
|
"proxy",
|
||||||
|
&format!("listen=tcp:0.0.0.0:{}", port),
|
||||||
|
&format!("connect=tcp:127.0.0.1:{}", port),
|
||||||
|
])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
trace!("Port forwarding configured: {} -> container {}", port, container);
|
if !output.status.success() {
|
||||||
|
warn!("Failed to setup port forwarding for port {}", port);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Port forwarding configured: {} -> container {}",
|
||||||
|
port,
|
||||||
|
container
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -1042,13 +1337,23 @@ pub mod cli {
|
||||||
match command.as_str() {
|
match command.as_str() {
|
||||||
"install" => {
|
"install" => {
|
||||||
if args.len() < 3 {
|
if args.len() < 3 {
|
||||||
eprintln!("Usage: botserver install <component> [--container] [--tenant <name>]");
|
eprintln!(
|
||||||
|
"Usage: botserver install <component> [--container] [--tenant <name>]"
|
||||||
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let component = &args[2];
|
let component = &args[2];
|
||||||
let mode = if args.contains(&"--container".to_string()) { InstallMode::Container } else { InstallMode::Local };
|
let mode = if args.contains(&"--container".to_string()) {
|
||||||
let tenant = if let Some(idx) = args.iter().position(|a| a == "--tenant") { args.get(idx + 1).cloned() } else { None };
|
InstallMode::Container
|
||||||
|
} else {
|
||||||
|
InstallMode::Local
|
||||||
|
};
|
||||||
|
let tenant = if let Some(idx) = args.iter().position(|a| a == "--tenant") {
|
||||||
|
args.get(idx + 1).cloned()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let pm = PackageManager::new(mode, tenant)?;
|
let pm = PackageManager::new(mode, tenant)?;
|
||||||
pm.install(component)?;
|
pm.install(component)?;
|
||||||
|
|
@ -1056,38 +1361,70 @@ pub mod cli {
|
||||||
}
|
}
|
||||||
"remove" => {
|
"remove" => {
|
||||||
if args.len() < 3 {
|
if args.len() < 3 {
|
||||||
eprintln!("Usage: botserver remove <component> [--container] [--tenant <name>]");
|
eprintln!(
|
||||||
|
"Usage: botserver remove <component> [--container] [--tenant <name>]"
|
||||||
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let component = &args[2];
|
let component = &args[2];
|
||||||
let mode = if args.contains(&"--container".to_string()) { InstallMode::Container } else { InstallMode::Local };
|
let mode = if args.contains(&"--container".to_string()) {
|
||||||
let tenant = if let Some(idx) = args.iter().position(|a| a == "--tenant") { args.get(idx + 1).cloned() } else { None };
|
InstallMode::Container
|
||||||
|
} else {
|
||||||
|
InstallMode::Local
|
||||||
|
};
|
||||||
|
let tenant = if let Some(idx) = args.iter().position(|a| a == "--tenant") {
|
||||||
|
args.get(idx + 1).cloned()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let pm = PackageManager::new(mode, tenant)?;
|
let pm = PackageManager::new(mode, tenant)?;
|
||||||
pm.remove(component)?;
|
pm.remove(component)?;
|
||||||
println!("✓ Component '{}' removed successfully", component);
|
println!("✓ Component '{}' removed successfully", component);
|
||||||
}
|
}
|
||||||
"list" => {
|
"list" => {
|
||||||
let mode = if args.contains(&"--container".to_string()) { InstallMode::Container } else { InstallMode::Local };
|
let mode = if args.contains(&"--container".to_string()) {
|
||||||
let tenant = if let Some(idx) = args.iter().position(|a| a == "--tenant") { args.get(idx + 1).cloned() } else { None };
|
InstallMode::Container
|
||||||
|
} else {
|
||||||
|
InstallMode::Local
|
||||||
|
};
|
||||||
|
let tenant = if let Some(idx) = args.iter().position(|a| a == "--tenant") {
|
||||||
|
args.get(idx + 1).cloned()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let pm = PackageManager::new(mode, tenant)?;
|
let pm = PackageManager::new(mode, tenant)?;
|
||||||
println!("Available components:");
|
println!("Available components:");
|
||||||
for component in pm.list() {
|
for component in pm.list() {
|
||||||
let status = if pm.is_installed(&component) { "✓ installed" } else { " available" };
|
let status = if pm.is_installed(&component) {
|
||||||
|
"✓ installed"
|
||||||
|
} else {
|
||||||
|
" available"
|
||||||
|
};
|
||||||
println!(" {} {}", status, component);
|
println!(" {} {}", status, component);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"status" => {
|
"status" => {
|
||||||
if args.len() < 3 {
|
if args.len() < 3 {
|
||||||
eprintln!("Usage: botserver status <component> [--container] [--tenant <name>]");
|
eprintln!(
|
||||||
|
"Usage: botserver status <component> [--container] [--tenant <name>]"
|
||||||
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let component = &args[2];
|
let component = &args[2];
|
||||||
let mode = if args.contains(&"--container".to_string()) { InstallMode::Container } else { InstallMode::Local };
|
let mode = if args.contains(&"--container".to_string()) {
|
||||||
let tenant = if let Some(idx) = args.iter().position(|a| a == "--tenant") { args.get(idx + 1).cloned() } else { None };
|
InstallMode::Container
|
||||||
|
} else {
|
||||||
|
InstallMode::Local
|
||||||
|
};
|
||||||
|
let tenant = if let Some(idx) = args.iter().position(|a| a == "--tenant") {
|
||||||
|
args.get(idx + 1).cloned()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let pm = PackageManager::new(mode, tenant)?;
|
let pm = PackageManager::new(mode, tenant)?;
|
||||||
if pm.is_installed(component) {
|
if pm.is_installed(component) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue