Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions server_manager/src/core/hardware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub struct HardwareInfo {
pub has_intel_quicksync: bool,
pub disk_gb: u64,
pub swap_gb: u64,
pub user_id: u32,
pub group_id: u32,
}

impl HardwareInfo {
Expand Down Expand Up @@ -58,6 +60,8 @@ impl HardwareInfo {
has_intel_quicksync,
disk_gb,
swap_gb,
user_id: 1000,
group_id: 1000,
}
}

Expand Down
59 changes: 58 additions & 1 deletion server_manager/src/core/system.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use nix::unistd::Uid;
use nix::unistd::{Uid, User};
use std::process::Command;
use std::path::Path;
use std::fs;
use std::env;
use anyhow::{Result, Context, bail};
use log::{info, warn};

Expand Down Expand Up @@ -51,6 +52,62 @@ pub fn install_dependencies() -> Result<()> {
Ok(())
}

pub fn ensure_media_user() -> Result<(u32, u32)> {
// 1. Check if running under sudo (use the real user)
if let Ok(sudo_user) = env::var("SUDO_USER") {
if !sudo_user.is_empty() {
info!("Detected sudo user: {}", sudo_user);
if let Ok(Some(user)) = User::from_name(&sudo_user) {
info!("Using detected user: {} (UID: {}, GID: {})", sudo_user, user.uid, user.gid);
return Ok((user.uid.as_raw(), user.gid.as_raw()));
}
}
}

// 2. Check if 'server_manager' user exists
if let Ok(Some(user)) = User::from_name("server_manager") {
info!("Found existing system user: server_manager (UID: {})", user.uid);
return Ok((user.uid.as_raw(), user.gid.as_raw()));
}

// 3. Create 'server_manager' user
info!("Creating system user: server_manager");
let status = Command::new("useradd")
.arg("-r") // System account
.arg("-m") // Create home directory (useful for some configs)
.arg("-d").arg("/opt/server_manager") // Home is install dir
.arg("-s").arg("/bin/bash") // Shell allowed for debugging
.arg("server_manager")
.status()
.context("Failed to create user server_manager")?;

if !status.success() {
bail!("Failed to execute useradd command");
}

// Verify creation
if let Ok(Some(user)) = User::from_name("server_manager") {
Ok((user.uid.as_raw(), user.gid.as_raw()))
} else {
bail!("User server_manager was created but could not be found")
}
}

pub fn chown_recursive(path: &Path, uid: u32, gid: u32) -> Result<()> {
info!("Recursively setting ownership of {:?} to {}:{}", path, uid, gid);
let status = Command::new("chown")
.arg("-R")
.arg(format!("{}:{}", uid, gid))
.arg(path)
.status()
.context("Failed to execute chown")?;

if !status.success() {
warn!("chown command returned non-zero exit code");
}
Ok(())
}

pub fn apply_optimizations() -> Result<()> {
info!("Applying system optimizations for media server performance...");

Expand Down
10 changes: 9 additions & 1 deletion server_manager/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ async fn run_install() -> Result<()> {
// 1. Root Check
system::check_root()?;

// 1.0 Ensure Media User
let (uid, gid) = system::ensure_media_user()?;

// 1.1 Create Install Directory
let install_dir = std::path::Path::new("/opt/server_manager");
if !install_dir.exists() {
Expand All @@ -60,7 +63,9 @@ async fn run_install() -> Result<()> {
let secrets = secrets::Secrets::load_or_create()?;

// 2. Hardware Detection
let hw = hardware::HardwareInfo::detect();
let mut hw = hardware::HardwareInfo::detect();
hw.user_id = uid;
hw.group_id = gid;

// 3. System Dependencies & Optimization
system::install_dependencies()?;
Expand All @@ -79,6 +84,9 @@ async fn run_install() -> Result<()> {
// 7. Generate Compose
generate_compose(&hw, &secrets).await?;

// 7.1 Set Permissions
system::chown_recursive(install_dir, uid, gid)?;

// 8. Launch
info!("Launching Services via Docker Compose...");
let status = Command::new("docker")
Expand Down
6 changes: 3 additions & 3 deletions server_manager/src/services/apps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,10 @@ $AUTOCONFIG = array(
Ok(())
}

fn env_vars(&self, _hw: &HardwareInfo, secrets: &Secrets) -> HashMap<String, String> {
fn env_vars(&self, hw: &HardwareInfo, secrets: &Secrets) -> HashMap<String, String> {
let mut vars = HashMap::new();
vars.insert("PUID".to_string(), "1000".to_string());
vars.insert("PGID".to_string(), "1000".to_string());
vars.insert("PUID".to_string(), hw.user_id.to_string());
vars.insert("PGID".to_string(), hw.group_id.to_string());
vars.insert("MYSQL_HOST".to_string(), "mariadb".to_string());
vars.insert("MYSQL_DATABASE".to_string(), "nextcloud".to_string());
vars.insert("MYSQL_USER".to_string(), "nextcloud".to_string());
Expand Down
4 changes: 2 additions & 2 deletions server_manager/src/services/arr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ macro_rules! define_arr_service {
}
fn env_vars(&self, hw: &HardwareInfo, _secrets: &Secrets) -> HashMap<String, String> {
let mut vars = HashMap::new();
vars.insert("PUID".to_string(), "1000".to_string());
vars.insert("PGID".to_string(), "1000".to_string());
vars.insert("PUID".to_string(), hw.user_id.to_string());
vars.insert("PGID".to_string(), hw.group_id.to_string());
vars.insert("COMPlus_EnableDiagnostics".to_string(), "0".to_string());

if let HardwareProfile::Low = hw.profile {
Expand Down
6 changes: 3 additions & 3 deletions server_manager/src/services/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ impl Service for QBittorrentService {
vec!["8080:8080".to_string(), "6881:6881".to_string(), "6881:6881/udp".to_string()]
}

fn env_vars(&self, _hw: &HardwareInfo, _secrets: &Secrets) -> HashMap<String, String> {
fn env_vars(&self, hw: &HardwareInfo, _secrets: &Secrets) -> HashMap<String, String> {
let mut vars = HashMap::new();
vars.insert("PUID".to_string(), "1000".to_string());
vars.insert("PGID".to_string(), "1000".to_string());
vars.insert("PUID".to_string(), hw.user_id.to_string());
vars.insert("PGID".to_string(), hw.group_id.to_string());
vars.insert("WEBUI_PORT".to_string(), "8080".to_string());
vars
}
Expand Down
6 changes: 3 additions & 3 deletions server_manager/src/services/infra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ impl Service for MariaDBService {
Ok(())
}

fn env_vars(&self, _hw: &HardwareInfo, secrets: &Secrets) -> HashMap<String, String> {
fn env_vars(&self, hw: &HardwareInfo, secrets: &Secrets) -> HashMap<String, String> {
let mut vars = HashMap::new();
vars.insert("PUID".to_string(), "1000".to_string());
vars.insert("PGID".to_string(), "1000".to_string());
vars.insert("PUID".to_string(), hw.user_id.to_string());
vars.insert("PGID".to_string(), hw.group_id.to_string());
vars.insert("MYSQL_ROOT_PASSWORD".to_string(), secrets.mysql_root_password.clone().unwrap_or_default());
vars.insert("MYSQL_DATABASE".to_string(), "server_manager".to_string());
vars.insert("MYSQL_USER".to_string(), "server_manager".to_string());
Expand Down
4 changes: 2 additions & 2 deletions server_manager/src/services/media.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ impl Service for PlexService {

fn env_vars(&self, hw: &HardwareInfo, _secrets: &Secrets) -> HashMap<String, String> {
let mut vars = HashMap::new();
vars.insert("PUID".to_string(), "1000".to_string());
vars.insert("PGID".to_string(), "1000".to_string());
vars.insert("PUID".to_string(), hw.user_id.to_string());
vars.insert("PGID".to_string(), hw.group_id.to_string());
vars.insert("VERSION".to_string(), "docker".to_string());

// Legacy requirement
Expand Down
6 changes: 6 additions & 0 deletions server_manager/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ fn test_generate_compose_structure() {
has_intel_quicksync: false,
disk_gb: 512,
swap_gb: 2,
user_id: 1000,
group_id: 1000,
};
let secrets = Secrets {
mysql_root_password: Some("rootpass".to_string()),
Expand Down Expand Up @@ -65,6 +67,8 @@ fn test_profile_logic_low() {
has_intel_quicksync: false,
disk_gb: 100,
swap_gb: 0,
user_id: 1000,
group_id: 1000,
};
let secrets = Secrets::default();

Expand All @@ -90,6 +94,8 @@ fn test_profile_logic_standard() {
has_intel_quicksync: false,
disk_gb: 512,
swap_gb: 4,
user_id: 1000,
group_id: 1000,
};
let secrets = Secrets::default();

Expand Down
Loading