From 86df49cdbbcac75fb64b55cdc3a22a376a14940e Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Mon, 26 Jan 2026 09:57:01 +0000
Subject: [PATCH] Resolve conflicts and finalize server_manager refactor
---
.gitignore | 2 +-
AGENTS.md | 2 +-
MANIFESTO.md | 8 +-
README.md | 67 +++----
{cylae => server_manager}/Cargo.lock | 36 ++--
{cylae => server_manager}/Cargo.toml | 2 +-
{cylae => server_manager}/src/core/docker.rs | 0
.../src/core/firewall.rs | 0
.../src/core/hardware.rs | 0
{cylae => server_manager}/src/core/mod.rs | 0
{cylae => server_manager}/src/core/secrets.rs | 0
{cylae => server_manager}/src/core/system.rs | 4 +-
.../src/main.rs => server_manager/src/lib.rs | 164 +++---------------
server_manager/src/main.rs | 142 +++++++++++++++
.../src/services/apps.rs | 2 +-
{cylae => server_manager}/src/services/arr.rs | 0
.../src/services/download.rs | 0
.../src/services/infra.rs | 4 +-
.../src/services/media.rs | 0
{cylae => server_manager}/src/services/mod.rs | 2 +-
server_manager/tests/integration_tests.rs | 104 +++++++++++
21 files changed, 339 insertions(+), 200 deletions(-)
rename {cylae => server_manager}/Cargo.lock (99%)
rename {cylae => server_manager}/Cargo.toml (94%)
rename {cylae => server_manager}/src/core/docker.rs (100%)
rename {cylae => server_manager}/src/core/firewall.rs (100%)
rename {cylae => server_manager}/src/core/hardware.rs (100%)
rename {cylae => server_manager}/src/core/mod.rs (100%)
rename {cylae => server_manager}/src/core/secrets.rs (100%)
rename {cylae => server_manager}/src/core/system.rs (93%)
rename cylae/src/main.rs => server_manager/src/lib.rs (52%)
create mode 100644 server_manager/src/main.rs
rename {cylae => server_manager}/src/services/apps.rs (98%)
rename {cylae => server_manager}/src/services/arr.rs (100%)
rename {cylae => server_manager}/src/services/download.rs (100%)
rename {cylae => server_manager}/src/services/infra.rs (97%)
rename {cylae => server_manager}/src/services/media.rs (100%)
rename {cylae => server_manager}/src/services/mod.rs (96%)
create mode 100644 server_manager/tests/integration_tests.rs
diff --git a/.gitignore b/.gitignore
index 696c3f6..7ebfaa8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
target/
-cylae/target/
+server_manager/target/
.venv/
__pycache__/
*.pyc
diff --git a/AGENTS.md b/AGENTS.md
index 8dc4259..3d321f4 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,7 +1,7 @@
# AGENTS.md
## Scope
-This directory contains the `cylae` project, a Rust-based media server orchestrator.
+This directory contains the `server_manager` project, a Rust-based media server orchestrator.
## Principles
1. **Rust First:** We do not use Python or Bash scripts for logic. Everything is in Rust.
diff --git a/MANIFESTO.md b/MANIFESTO.md
index 5d1e7bb..efcacb8 100644
--- a/MANIFESTO.md
+++ b/MANIFESTO.md
@@ -1,4 +1,4 @@
-# The Cylae Manifesto
+# The Server Manager Manifesto
## 1. Philosophy: State-of-the-Art over Legacy
We have discarded the old Python/Bash imperative scripts in favor of a modern, declarative, and type-safe architecture. The goal is not just to "install things," but to "orchestrate a state."
@@ -7,12 +7,12 @@ We have discarded the old Python/Bash imperative scripts in favor of a modern, d
We chose **Rust** for the core orchestration engine because:
* **Safety:** Memory safety guarantees prevent runtime crashes during critical system operations.
* **Performance:** Zero-cost abstractions allow for complex hardware profiling (GPU detection, RAM analysis) without startup lag.
-* **Portability:** A single static binary (`cylae`) is easier to distribute and manage than a Python venv with fragile dependencies.
+* **Portability:** A single static binary (`server_manager`) is easier to distribute and manage than a Python venv with fragile dependencies.
## 3. Architecture: Infrastructure as Code (IaC)
-Cylae does not simply run `docker run`. It acts as a **Compiler**:
+Server Manager does not simply run `docker run`. It acts as a **Compiler**:
1. **Input:** Hardware state (RAM, CPU, GPU) + User Configuration.
-2. **Process:** The `cylae` binary analyzes the hardware profile (e.g., "Low spec machine detected, disable transcoding") and selects the optimal service configurations.
+2. **Process:** The `server_manager` binary analyzes the hardware profile (e.g., "Low spec machine detected, disable transcoding") and selects the optimal service configurations.
3. **Output:** A deterministic `docker-compose.yml` file.
4. **Execution:** Docker takes over for the actual container management, ensuring idempotency.
diff --git a/README.md b/README.md
index f3ef537..c252599 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
-# Cylae - Next-Gen Media Server Orchestrator 🚀
+# Server Manager - Next-Gen Media Server Orchestrator 🚀
-  
+  
-**Cylae** is a powerful and intelligent tool written in Rust to deploy, manage, and optimize a complete personal media and cloud server stack. It detects your hardware and automatically configures 24 Docker services for optimal performance.
+**Server Manager** is a powerful and intelligent tool written in Rust to deploy, manage, and optimize a complete personal media and cloud server stack. It detects your hardware and automatically configures 24 Docker services for optimal performance.
-**Cylae** est un outil puissant et intelligent écrit en Rust pour déployer, gérer et optimiser une pile complète de serveur multimédia et cloud personnel. Il détecte votre matériel et configure automatiquement 24 services Docker pour des performances optimales.
+**Server Manager** est un outil puissant et intelligent écrit en Rust pour déployer, gérer et optimiser une pile complète de serveur multimédia et cloud personnel. Il détecte votre matériel et configure automatiquement 24 services Docker pour des performances optimales.
---
@@ -18,7 +18,7 @@
# 🇬🇧 English
-Welcome to the Cylae documentation. Whether you are a beginner or an expert, this tool is designed to make your life easier.
+Welcome to the Server Manager documentation. Whether you are a beginner or an expert, this tool is designed to make your life easier.
## ✨ Key Features
* **24 Integrated Services**: Plex, ArrStack, Nextcloud, Mailserver, etc.
@@ -40,11 +40,11 @@ No need to be a Linux wizard! Follow these simple steps.
2. **Start the installation** with a single command:
```bash
-sudo ./cylae install
+sudo ./server_manager install
```
That's it! 🎉
-Cylae will automatically:
+Server Manager will automatically:
1. Install system dependencies (curl, git, build-essential, etc.).
2. Check and install Docker.
3. Scan your hardware (RAM, CPU, Disk).
@@ -58,7 +58,7 @@ Once finished, go to `http://YOUR-SERVER-IP` (or the specific ports listed below
## 🤓 For Advanced Users (Experts)
-Cylae is built in Rust for performance and reliability. Here is how to use it to its full potential.
+Server Manager is built in Rust for performance and reliability. Here is how to use it to its full potential.
### Build from Source
@@ -67,25 +67,34 @@ Cylae is built in Rust for performance and reliability. Here is how to use it to
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Clone and build
-git clone https://github.com/your-repo/cylae.git
-cd cylae/cylae
+git clone https://github.com/Cylae/server_script
+cd server_script/server_manager
cargo build --release
-# The binary is located in target/release/cylae
-sudo cp target/release/cylae /usr/local/bin/
+# The binary is located in target/release/server_manager
+sudo cp target/release/server_manager /usr/local/bin/
+```
+
+### 🧪 Testing
+
+The project includes a comprehensive test suite covering hardware detection, secrets generation, and Docker Compose validation.
+
+```bash
+cd server_script/server_manager
+cargo test
```
### CLI Commands
The tool provides several subcommands:
-* `cylae install`: Full idempotent installation (dependencies, config, docker-compose up).
-* `cylae generate`: Generates `docker-compose.yml` and `secrets.yaml` only, without launching services. Useful for inspection.
-* `cylae status`: Displays detected hardware statistics and the profile (Low/Standard/High).
+* `server_manager install`: Full idempotent installation (dependencies, config, docker-compose up).
+* `server_manager generate`: Generates `docker-compose.yml` and `secrets.yaml` only, without launching services. Useful for inspection.
+* `server_manager status`: Displays detected hardware statistics and the profile (Low/Standard/High).
### ⚙️ Hardware Profiles
-Cylae adjusts configuration via `HardwareManager`:
+Server Manager adjusts configuration via `HardwareManager`:
| Profile | Criteria | Optimizations |
| :--- | :--- | :--- |
@@ -137,7 +146,7 @@ Here is the matrix of deployed services:
# 🇫🇷 Français
-Bienvenue sur la documentation de Cylae. Que vous soyez débutant ou expert, cet outil est conçu pour vous faciliter la vie.
+Bienvenue sur la documentation de Server Manager. Que vous soyez débutant ou expert, cet outil est conçu pour vous faciliter la vie.
## ✨ Fonctionnalités Clés
* **24 Services Intégrés** : Plex, ArrStack, Nextcloud, Mailserver, etc.
@@ -159,11 +168,11 @@ Pas besoin de connaître Linux sur le bout des doigts ! Suivez ces étapes simpl
2. **Lancez l'installation** avec une seule commande :
```bash
-sudo ./cylae install
+sudo ./server_manager install
```
C'est tout ! 🎉
-Cylae va automatiquement :
+Server Manager va automatiquement :
1. Installer les dépendances système (curl, git, build-essential, etc.).
2. Vérifier et installer Docker.
3. Scanner votre matériel (RAM, CPU, Disque).
@@ -177,7 +186,7 @@ Une fois terminé, rendez-vous sur `http://IP-DE-VOTRE-SERVEUR` (ou les ports sp
## 🤓 Pour les Utilisateurs Avancés (Experts)
-Cylae est construit en Rust pour la performance et la fiabilité. Voici comment l'utiliser au maximum de son potentiel.
+Server Manager est construit en Rust pour la performance et la fiabilité. Voici comment l'utiliser au maximum de son potentiel.
### Compilation depuis les sources
@@ -186,25 +195,25 @@ Cylae est construit en Rust pour la performance et la fiabilité. Voici comment
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Cloner et compiler
-git clone https://github.com/votre-repo/cylae.git
-cd cylae/cylae
+git clone https://github.com/Cylae/server_script
+cd server_script/server_manager
cargo build --release
-# Le binaire est dans target/release/cylae
-sudo cp target/release/cylae /usr/local/bin/
+# Le binaire est dans target/release/server_manager
+sudo cp target/release/server_manager /usr/local/bin/
```
### Commandes CLI
L'outil dispose de plusieurs sous-commandes :
-* `cylae install` : Installation complète idempotente (dépendances, config, docker-compose up).
-* `cylae generate` : Génère uniquement le fichier `docker-compose.yml` et `secrets.yaml` sans lancer les services. Utile pour inspection.
-* `cylae status` : Affiche les statistiques matérielles détectées et le profil (Low/Standard/High).
+* `server_manager install` : Installation complète idempotente (dépendances, config, docker-compose up).
+* `server_manager generate` : Génère uniquement le fichier `docker-compose.yml` et `secrets.yaml` sans lancer les services. Utile pour inspection.
+* `server_manager status` : Affiche les statistiques matérielles détectées et le profil (Low/Standard/High).
### ⚙️ Profils Matériels (Hardware Profiles)
-Cylae ajuste la configuration via `HardwareManager` :
+Server Manager ajuste la configuration via `HardwareManager` :
| Profil | Critères | Optimisations |
| :--- | :--- | :--- |
@@ -253,4 +262,4 @@ Voici la matrice des services déployés :
---
-Built with ❤️ by the Cylae Team.
+Built with ❤️ by the Server Manager Team.
diff --git a/cylae/Cargo.lock b/server_manager/Cargo.lock
similarity index 99%
rename from cylae/Cargo.lock
rename to server_manager/Cargo.lock
index d4f91d0..0b2321c 100644
--- a/cylae/Cargo.lock
+++ b/server_manager/Cargo.lock
@@ -173,24 +173,6 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
-[[package]]
-name = "cylae"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-trait",
- "clap",
- "env_logger",
- "libc",
- "log",
- "nix",
- "serde",
- "serde_yaml",
- "sysinfo",
- "tokio",
- "which",
-]
-
[[package]]
name = "either"
version = "1.15.0"
@@ -541,6 +523,24 @@ dependencies = [
"unsafe-libyaml",
]
+[[package]]
+name = "server_manager"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "clap",
+ "env_logger",
+ "libc",
+ "log",
+ "nix",
+ "serde",
+ "serde_yaml",
+ "sysinfo",
+ "tokio",
+ "which",
+]
+
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
diff --git a/cylae/Cargo.toml b/server_manager/Cargo.toml
similarity index 94%
rename from cylae/Cargo.toml
rename to server_manager/Cargo.toml
index 30b4499..8da2660 100644
--- a/cylae/Cargo.toml
+++ b/server_manager/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "cylae"
+name = "server_manager"
version = "0.1.0"
edition = "2021"
diff --git a/cylae/src/core/docker.rs b/server_manager/src/core/docker.rs
similarity index 100%
rename from cylae/src/core/docker.rs
rename to server_manager/src/core/docker.rs
diff --git a/cylae/src/core/firewall.rs b/server_manager/src/core/firewall.rs
similarity index 100%
rename from cylae/src/core/firewall.rs
rename to server_manager/src/core/firewall.rs
diff --git a/cylae/src/core/hardware.rs b/server_manager/src/core/hardware.rs
similarity index 100%
rename from cylae/src/core/hardware.rs
rename to server_manager/src/core/hardware.rs
diff --git a/cylae/src/core/mod.rs b/server_manager/src/core/mod.rs
similarity index 100%
rename from cylae/src/core/mod.rs
rename to server_manager/src/core/mod.rs
diff --git a/cylae/src/core/secrets.rs b/server_manager/src/core/secrets.rs
similarity index 100%
rename from cylae/src/core/secrets.rs
rename to server_manager/src/core/secrets.rs
diff --git a/cylae/src/core/system.rs b/server_manager/src/core/system.rs
similarity index 93%
rename from cylae/src/core/system.rs
rename to server_manager/src/core/system.rs
index f16dca8..c81ad38 100644
--- a/cylae/src/core/system.rs
+++ b/server_manager/src/core/system.rs
@@ -54,14 +54,14 @@ pub fn install_dependencies() -> Result<()> {
pub fn apply_optimizations() -> Result<()> {
info!("Applying system optimizations for media server performance...");
- let config = r#"# Cylae Media Server Optimizations
+ let config = r#"# Server Manager Media Server Optimizations
fs.inotify.max_user_watches=524288
vm.swappiness=10
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
"#;
- let path = Path::new("/etc/sysctl.d/99-cylae-optimization.conf");
+ let path = Path::new("/etc/sysctl.d/99-server-manager-optimization.conf");
fs::write(path, config).context("Failed to write sysctl config")?;
let status = Command::new("sysctl")
diff --git a/cylae/src/main.rs b/server_manager/src/lib.rs
similarity index 52%
rename from cylae/src/main.rs
rename to server_manager/src/lib.rs
index 116bf31..fbb8e04 100644
--- a/cylae/src/main.rs
+++ b/server_manager/src/lib.rs
@@ -1,139 +1,31 @@
-mod core;
-mod services;
+pub mod core;
+pub mod services;
-use clap::{Parser, Subcommand};
-use log::{info, error, LevelFilter};
-use std::collections::HashMap;
-use anyhow::{Result, Context};
-use std::process::Command;
-use std::fs;
-
-use crate::core::{system, hardware, firewall, docker, secrets};
-
-#[derive(Parser)]
-#[command(name = "cylae")]
-#[command(about = "Next-Gen Media Server Orchestrator", long_about = None)]
-struct Cli {
- #[command(subcommand)]
- command: Commands,
-}
-
-#[derive(Subcommand)]
-enum Commands {
- /// Full installation (Idempotent)
- Install,
- /// Show system status
- Status,
- /// Generate docker-compose.yml only
- Generate,
-}
-
-#[tokio::main]
-async fn main() -> Result<()> {
- env_logger::builder().filter_level(LevelFilter::Info).init();
-
- let cli = Cli::parse();
-
- match cli.command {
- Commands::Install => run_install().await?,
- Commands::Status => run_status(),
- Commands::Generate => run_generate().await?,
- }
-
- Ok(())
-}
-
-async fn run_install() -> Result<()> {
- info!("Starting Cylae Installation...");
-
- // 1. Root Check
- system::check_root()?;
-
- // 1.1 Create Install Directory
- let install_dir = std::path::Path::new("/opt/cylae");
- if !install_dir.exists() {
- info!("Creating installation directory at /opt/cylae...");
- fs::create_dir_all(install_dir).context("Failed to create /opt/cylae")?;
- }
- std::env::set_current_dir(install_dir).context("Failed to chdir to /opt/cylae")?;
-
- // 1.2 Load Secrets
- let secrets = secrets::Secrets::load_or_create()?;
-
- // 2. Hardware Detection
- let hw = hardware::HardwareInfo::detect();
-
- // 3. System Dependencies & Optimization
- system::install_dependencies()?;
- system::apply_optimizations()?;
+// Re-export build_compose_structure from main (we need to move it or expose it)
+// Since main.rs is a binary, we can't easily import from it in integration tests unless we use a lib.rs structure.
+// The common pattern is to have the logic in lib.rs and main.rs just calls it.
+// I will move build_compose_structure to lib.rs or make main.rs just a thin wrapper.
- // 4. Firewall
- firewall::configure()?;
+// Wait, I can't import functions from main.rs into integration tests directly if it's not a lib.
+// I need to refactor slightly.
+// Move `build_compose_structure` to a new module `src/orchestrator.rs` or keep it in `lib.rs`.
- // 5. Docker
- docker::install()?;
+// Let's check where `main.rs` is. It is in `src/main.rs`.
+// I should create `src/lib.rs` that exposes the modules and the orchestrator logic.
- // 6. Initialize Services
- initialize_services(&hw, &secrets)?;
+// Currently `main.rs` has `mod core; mod services;`.
+// I will move these to `lib.rs` and make `main.rs` use the lib.
- // 7. Generate Compose
- generate_compose(&hw, &secrets).await?;
+pub use crate::core::hardware;
+pub use crate::core::secrets;
- // 8. Launch
- info!("Launching Services via Docker Compose...");
- let status = Command::new("docker")
- .args(&["compose", "up", "-d", "--remove-orphans"])
- .status()
- .context("Failed to run docker compose up")?;
-
- if status.success() {
- info!("Cylae Stack Deployed Successfully! 🚀");
- } else {
- error!("Docker Compose failed.");
- }
-
- Ok(())
-}
-
-fn run_status() {
- let hw = hardware::HardwareInfo::detect();
- println!("=== System Status ===");
- println!("RAM: {} GB", hw.ram_gb);
- println!("Swap: {} GB", hw.swap_gb);
- println!("Disk: {} GB", hw.disk_gb);
- println!("Cores: {}", hw.cpu_cores);
- println!("Profile: {:?}", hw.profile);
- println!("Nvidia GPU: {}", hw.has_nvidia);
- println!("Intel QuickSync: {}", hw.has_intel_quicksync);
-
- println!("\n=== Docker Status ===");
- if let Ok(true) = Command::new("docker").arg("ps").status().map(|s| s.success()) {
- println!("Docker is running.");
- } else {
- println!("Docker is NOT running.");
- }
-}
-
-async fn run_generate() -> Result<()> {
- let hw = hardware::HardwareInfo::detect();
- // For generate, we might not be in /opt/cylae, but let's try to load secrets from CWD.
- // We propagate the error because generating a compose file with empty passwords is bad.
- let secrets = secrets::Secrets::load_or_create().context("Failed to load or create secrets.yaml")?;
- generate_compose(&hw, &secrets).await
-}
-
-fn initialize_services(hw: &hardware::HardwareInfo, secrets: &secrets::Secrets) -> Result<()> {
- info!("Initializing services...");
- let services = services::get_all_services();
- for service in services {
- service.initialize(hw, secrets).with_context(|| format!("Failed to initialize service: {}", service.name()))?;
- }
- Ok(())
-}
+use anyhow::Result;
+use std::collections::HashMap;
-async fn generate_compose(hw: &hardware::HardwareInfo, secrets: &secrets::Secrets) -> Result<()> {
- info!("Generating docker-compose.yml based on hardware profile...");
+// I will duplicate the logic or move it. Moving is better.
+// I'll put build_compose_structure here.
+pub fn build_compose_structure(hw: &hardware::HardwareInfo, secrets: &secrets::Secrets) -> Result {
let services = services::get_all_services();
let mut compose_services = HashMap::new();
@@ -197,9 +89,6 @@ async fn generate_compose(hw: &hardware::HardwareInfo, secrets: &secrets::Secret
// Healthcheck
if let Some(cmd) = service.healthcheck() {
let mut hc = serde_yaml::Mapping::new();
- // curl command needs to be split if using ["CMD-SHELL", ...] or just string.
- // Docker compose supports string or list.
- // We'll use list for safety: ["CMD-SHELL", cmd]
let mut test_seq = serde_yaml::Sequence::new();
test_seq.push("CMD-SHELL".into());
test_seq.push(cmd.into());
@@ -268,9 +157,9 @@ async fn generate_compose(hw: &hardware::HardwareInfo, secrets: &secrets::Secret
// Networks definition
let mut networks_map = serde_yaml::Mapping::new();
- let mut cylae_net = serde_yaml::Mapping::new();
- cylae_net.insert("driver".into(), "bridge".into());
- networks_map.insert("cylae_net".into(), serde_yaml::Value::Mapping(cylae_net));
+ let mut server_manager_net = serde_yaml::Mapping::new();
+ server_manager_net.insert("driver".into(), "bridge".into());
+ networks_map.insert("server_manager_net".into(), serde_yaml::Value::Mapping(server_manager_net));
// Top Level
let mut top_level = serde_yaml::Mapping::new();
@@ -279,10 +168,5 @@ async fn generate_compose(hw: &hardware::HardwareInfo, secrets: &secrets::Secret
)));
top_level.insert("networks".into(), serde_yaml::Value::Mapping(networks_map));
- let yaml_output = serde_yaml::to_string(&top_level)?;
-
- fs::write("docker-compose.yml", yaml_output).context("Failed to write docker-compose.yml")?;
- info!("docker-compose.yml generated.");
-
- Ok(())
+ Ok(top_level)
}
diff --git a/server_manager/src/main.rs b/server_manager/src/main.rs
new file mode 100644
index 0000000..078160f
--- /dev/null
+++ b/server_manager/src/main.rs
@@ -0,0 +1,142 @@
+use clap::{Parser, Subcommand};
+use log::{info, error, LevelFilter};
+use anyhow::{Result, Context};
+use std::process::Command;
+use std::fs;
+
+// Use the lib crate
+use server_manager::core::{system, hardware, firewall, docker, secrets};
+use server_manager::services;
+use server_manager::build_compose_structure;
+
+#[derive(Parser)]
+#[command(name = "server_manager")]
+#[command(about = "Next-Gen Media Server Orchestrator", long_about = None)]
+struct Cli {
+ #[command(subcommand)]
+ command: Commands,
+}
+
+#[derive(Subcommand)]
+enum Commands {
+ /// Full installation (Idempotent)
+ Install,
+ /// Show system status
+ Status,
+ /// Generate docker-compose.yml only
+ Generate,
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ env_logger::builder().filter_level(LevelFilter::Info).init();
+
+ let cli = Cli::parse();
+
+ match cli.command {
+ Commands::Install => run_install().await?,
+ Commands::Status => run_status(),
+ Commands::Generate => run_generate().await?,
+ }
+
+ Ok(())
+}
+
+async fn run_install() -> Result<()> {
+ info!("Starting Server Manager Installation...");
+
+ // 1. Root Check
+ system::check_root()?;
+
+ // 1.1 Create Install Directory
+ let install_dir = std::path::Path::new("/opt/server_manager");
+ if !install_dir.exists() {
+ info!("Creating installation directory at /opt/server_manager...");
+ fs::create_dir_all(install_dir).context("Failed to create /opt/server_manager")?;
+ }
+ std::env::set_current_dir(install_dir).context("Failed to chdir to /opt/server_manager")?;
+
+ // 1.2 Load Secrets
+ let secrets = secrets::Secrets::load_or_create()?;
+
+ // 2. Hardware Detection
+ let hw = hardware::HardwareInfo::detect();
+
+ // 3. System Dependencies & Optimization
+ system::install_dependencies()?;
+ system::apply_optimizations()?;
+
+ // 4. Firewall
+ firewall::configure()?;
+
+ // 5. Docker
+ docker::install()?;
+
+ // 6. Initialize Services
+ initialize_services(&hw, &secrets)?;
+
+ // 7. Generate Compose
+ generate_compose(&hw, &secrets).await?;
+
+ // 8. Launch
+ info!("Launching Services via Docker Compose...");
+ let status = Command::new("docker")
+ .args(&["compose", "up", "-d", "--remove-orphans"])
+ .status()
+ .context("Failed to run docker compose up")?;
+
+ if status.success() {
+ info!("Server Manager Stack Deployed Successfully! 🚀");
+ } else {
+ error!("Docker Compose failed.");
+ }
+
+ Ok(())
+}
+
+fn run_status() {
+ let hw = hardware::HardwareInfo::detect();
+ println!("=== System Status ===");
+ println!("RAM: {} GB", hw.ram_gb);
+ println!("Swap: {} GB", hw.swap_gb);
+ println!("Disk: {} GB", hw.disk_gb);
+ println!("Cores: {}", hw.cpu_cores);
+ println!("Profile: {:?}", hw.profile);
+ println!("Nvidia GPU: {}", hw.has_nvidia);
+ println!("Intel QuickSync: {}", hw.has_intel_quicksync);
+
+ println!("\n=== Docker Status ===");
+ if let Ok(true) = Command::new("docker").arg("ps").status().map(|s| s.success()) {
+ println!("Docker is running.");
+ } else {
+ println!("Docker is NOT running.");
+ }
+}
+
+async fn run_generate() -> Result<()> {
+ let hw = hardware::HardwareInfo::detect();
+ // For generate, we might not be in /opt/server_manager, but let's try to load secrets from CWD.
+ // We propagate the error because generating a compose file with empty passwords is bad.
+ let secrets = secrets::Secrets::load_or_create().context("Failed to load or create secrets.yaml")?;
+ generate_compose(&hw, &secrets).await
+}
+
+fn initialize_services(hw: &hardware::HardwareInfo, secrets: &secrets::Secrets) -> Result<()> {
+ info!("Initializing services...");
+ let services = services::get_all_services();
+ for service in services {
+ service.initialize(hw, secrets).with_context(|| format!("Failed to initialize service: {}", service.name()))?;
+ }
+ Ok(())
+}
+
+async fn generate_compose(hw: &hardware::HardwareInfo, secrets: &secrets::Secrets) -> Result<()> {
+ info!("Generating docker-compose.yml based on hardware profile...");
+ let top_level = build_compose_structure(hw, secrets)?;
+ let yaml_output = serde_yaml::to_string(&top_level)?;
+
+ fs::write("docker-compose.yml", yaml_output).context("Failed to write docker-compose.yml")?;
+ info!("docker-compose.yml generated.");
+
+ Ok(())
+}
diff --git a/cylae/src/services/apps.rs b/server_manager/src/services/apps.rs
similarity index 98%
rename from cylae/src/services/apps.rs
rename to server_manager/src/services/apps.rs
index 365755a..fc6d81b 100644
--- a/cylae/src/services/apps.rs
+++ b/server_manager/src/services/apps.rs
@@ -35,7 +35,7 @@ impl Service for YourlsService {
fn env_vars(&self, _hw: &HardwareInfo, secrets: &Secrets) -> HashMap {
let mut vars = HashMap::new();
vars.insert("YOURLS_DB_HOST".to_string(), "mariadb".to_string());
- vars.insert("YOURLS_DB_USER".to_string(), "cylae".to_string());
+ vars.insert("YOURLS_DB_USER".to_string(), "server_manager".to_string());
vars.insert("YOURLS_DB_PASS".to_string(), secrets.mysql_user_password.clone().unwrap_or_default());
vars.insert("YOURLS_DB_NAME".to_string(), "yourls".to_string());
vars.insert("YOURLS_USER".to_string(), "admin".to_string());
diff --git a/cylae/src/services/arr.rs b/server_manager/src/services/arr.rs
similarity index 100%
rename from cylae/src/services/arr.rs
rename to server_manager/src/services/arr.rs
diff --git a/cylae/src/services/download.rs b/server_manager/src/services/download.rs
similarity index 100%
rename from cylae/src/services/download.rs
rename to server_manager/src/services/download.rs
diff --git a/cylae/src/services/infra.rs b/server_manager/src/services/infra.rs
similarity index 97%
rename from cylae/src/services/infra.rs
rename to server_manager/src/services/infra.rs
index 75cd86c..ff3cdc2 100644
--- a/cylae/src/services/infra.rs
+++ b/server_manager/src/services/infra.rs
@@ -54,8 +54,8 @@ impl Service for MariaDBService {
vars.insert("PUID".to_string(), "1000".to_string());
vars.insert("PGID".to_string(), "1000".to_string());
vars.insert("MYSQL_ROOT_PASSWORD".to_string(), secrets.mysql_root_password.clone().unwrap_or_default());
- vars.insert("MYSQL_DATABASE".to_string(), "cylae".to_string());
- vars.insert("MYSQL_USER".to_string(), "cylae".to_string());
+ vars.insert("MYSQL_DATABASE".to_string(), "server_manager".to_string());
+ vars.insert("MYSQL_USER".to_string(), "server_manager".to_string());
vars.insert("MYSQL_PASSWORD".to_string(), secrets.mysql_user_password.clone().unwrap_or_default());
vars
}
diff --git a/cylae/src/services/media.rs b/server_manager/src/services/media.rs
similarity index 100%
rename from cylae/src/services/media.rs
rename to server_manager/src/services/media.rs
diff --git a/cylae/src/services/mod.rs b/server_manager/src/services/mod.rs
similarity index 96%
rename from cylae/src/services/mod.rs
rename to server_manager/src/services/mod.rs
index 2478079..6210ebc 100644
--- a/cylae/src/services/mod.rs
+++ b/server_manager/src/services/mod.rs
@@ -18,7 +18,7 @@ pub trait Service: Send + Sync {
fn ports(&self) -> Vec { vec![] }
fn env_vars(&self, _hw: &HardwareInfo, _secrets: &Secrets) -> HashMap { HashMap::new() }
fn volumes(&self, _hw: &HardwareInfo) -> Vec { vec![] }
- fn networks(&self) -> Vec { vec!["cylae_net".to_string()] }
+ fn networks(&self) -> Vec { vec!["server_manager_net".to_string()] }
fn devices(&self, _hw: &HardwareInfo) -> Vec { vec![] }
fn healthcheck(&self) -> Option { None }
fn depends_on(&self) -> Vec { vec![] }
diff --git a/server_manager/tests/integration_tests.rs b/server_manager/tests/integration_tests.rs
new file mode 100644
index 0000000..a9cfb6b
--- /dev/null
+++ b/server_manager/tests/integration_tests.rs
@@ -0,0 +1,104 @@
+use server_manager::core::hardware::{HardwareInfo, HardwareProfile};
+use server_manager::core::secrets::Secrets;
+use server_manager::build_compose_structure;
+
+#[test]
+fn test_generate_compose_structure() {
+ // 1. Mock Hardware and Secrets
+ let hw = HardwareInfo {
+ profile: HardwareProfile::Standard,
+ ram_gb: 8,
+ cpu_cores: 4,
+ has_nvidia: false,
+ has_intel_quicksync: false,
+ disk_gb: 512,
+ swap_gb: 2,
+ };
+ let secrets = Secrets {
+ mysql_root_password: Some("rootpass".to_string()),
+ mysql_user_password: Some("userpass".to_string()),
+ nextcloud_db_password: Some("nextcloudpass".to_string()),
+ glpi_db_password: Some("glpipass".to_string()),
+ gitea_db_password: Some("giteapass".to_string()),
+ yourls_admin_password: Some("yourlspass".to_string()),
+ mailserver_password: Some("mailpass".to_string()),
+ nextcloud_admin_password: Some("nextcloudadmin".to_string()),
+ roundcube_db_password: Some("roundcubepass".to_string()),
+ };
+
+ // 2. Build Structure
+ let result = build_compose_structure(&hw, &secrets);
+ assert!(result.is_ok(), "Failed to build compose structure");
+ let mapping = result.unwrap();
+
+ // 3. Verify Top Level Keys
+ assert!(mapping.contains_key(&serde_yaml::Value::from("services")));
+ assert!(mapping.contains_key(&serde_yaml::Value::from("networks")));
+
+ // 4. Verify Networks
+ let networks = mapping.get(&serde_yaml::Value::from("networks")).unwrap().as_mapping().unwrap();
+ assert!(networks.contains_key(&serde_yaml::Value::from("server_manager_net")));
+
+ // 5. Verify Services Count (Should be 24)
+ let services = mapping.get(&serde_yaml::Value::from("services")).unwrap().as_mapping().unwrap();
+ assert_eq!(services.len(), 24, "Expected 24 services");
+
+ // 6. Verify specific service (Plex)
+ assert!(services.contains_key(&serde_yaml::Value::from("plex")));
+ let plex = services.get(&serde_yaml::Value::from("plex")).unwrap().as_mapping().unwrap();
+ assert_eq!(plex.get(&serde_yaml::Value::from("image")).unwrap().as_str().unwrap(), "lscr.io/linuxserver/plex:latest");
+
+ // 7. Verify Network attachment
+ let plex_nets = plex.get(&serde_yaml::Value::from("networks")).unwrap().as_sequence().unwrap();
+ assert!(plex_nets.contains(&serde_yaml::Value::from("server_manager_net")));
+}
+
+#[test]
+fn test_profile_logic_low() {
+ // Test that Low profile disables SpamAssassin in MailService
+ let hw = HardwareInfo {
+ profile: HardwareProfile::Low,
+ ram_gb: 2,
+ cpu_cores: 2,
+ has_nvidia: false,
+ has_intel_quicksync: false,
+ disk_gb: 100,
+ swap_gb: 0,
+ };
+ let secrets = Secrets::default();
+
+ let result = build_compose_structure(&hw, &secrets).unwrap();
+ let services = result.get(&serde_yaml::Value::from("services")).unwrap().as_mapping().unwrap();
+
+ let mail = services.get(&serde_yaml::Value::from("mailserver")).unwrap().as_mapping().unwrap();
+ let envs = mail.get(&serde_yaml::Value::from("environment")).unwrap().as_sequence().unwrap();
+
+ // Check for ENABLE_SPAMASSASSIN=0
+ let has_disabled_spam = envs.iter().any(|v| v.as_str().unwrap() == "ENABLE_SPAMASSASSIN=0");
+ assert!(has_disabled_spam, "Low profile should disable SpamAssassin");
+}
+
+#[test]
+fn test_profile_logic_standard() {
+ // Test that Standard profile enables SpamAssassin
+ let hw = HardwareInfo {
+ profile: HardwareProfile::Standard,
+ ram_gb: 8,
+ cpu_cores: 4,
+ has_nvidia: false,
+ has_intel_quicksync: false,
+ disk_gb: 512,
+ swap_gb: 4,
+ };
+ let secrets = Secrets::default();
+
+ let result = build_compose_structure(&hw, &secrets).unwrap();
+ let services = result.get(&serde_yaml::Value::from("services")).unwrap().as_mapping().unwrap();
+
+ let mail = services.get(&serde_yaml::Value::from("mailserver")).unwrap().as_mapping().unwrap();
+ let envs = mail.get(&serde_yaml::Value::from("environment")).unwrap().as_sequence().unwrap();
+
+ // Check for ENABLE_SPAMASSASSIN=1
+ let has_enabled_spam = envs.iter().any(|v| v.as_str().unwrap() == "ENABLE_SPAMASSASSIN=1");
+ assert!(has_enabled_spam, "Standard profile should enable SpamAssassin");
+}