feat: Sistema de variables de entorno mejorado con EnvironmentFile
- Agregado campo 'environment' a MonitoredApp para almacenar variables ADICIONALES - Solo se almacenan en JSON las variables agregadas manualmente desde el panel - Las variables del .env del proyecto se cargan automáticamente con EnvironmentFile - Modificado service_generator.rs para usar directiva EnvironmentFile en systemd - Fix: Usuario ahora se lee correctamente del JSON sin fallback a 'root' - Edit.html pre-carga variables adicionales del JSON al editar - Separación clara: .env (proyecto) vs variables adicionales (JSON) - Transparencia total con .env nativo del proyecto Beneficios: ✅ No duplicación de variables (.env es la fuente de verdad) ✅ JSON solo guarda variables extras (pequeño y limpio) ✅ .env funciona igual que en desarrollo ✅ systemd lee .env con EnvironmentFile ✅ Variables adicionales se persisten en JSON ✅ Al editar, se pre-cargan variables adicionales guardadas
This commit is contained in:
@@ -176,6 +176,7 @@ pub async fn update_app_handler(
|
||||
deleted: false,
|
||||
deleted_at: None,
|
||||
deleted_reason: None,
|
||||
environment: config.environment.clone(),
|
||||
systemd_service: None,
|
||||
created_at: None,
|
||||
};
|
||||
|
||||
@@ -57,6 +57,12 @@ pub struct MonitoredApp {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_reason: Option<String>,
|
||||
|
||||
// --- VARIABLES DE ENTORNO ADICIONALES ---
|
||||
/// Variables de entorno ADICIONALES (las del .env se cargan con EnvironmentFile)
|
||||
/// Solo almacenamos aquí las variables que el usuario agrega manualmente desde el panel
|
||||
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
|
||||
pub environment: std::collections::HashMap<String, String>,
|
||||
|
||||
// DEPRECATED: Mantener por compatibilidad con versiones antiguas
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub systemd_service: Option<String>,
|
||||
@@ -217,6 +223,7 @@ impl ConfigManager {
|
||||
deleted: false,
|
||||
deleted_at: None,
|
||||
deleted_reason: None,
|
||||
environment: std::collections::HashMap::new(),
|
||||
systemd_service: None,
|
||||
created_at: None,
|
||||
};
|
||||
|
||||
@@ -261,6 +261,7 @@ pub fn sync_discovered_services(services: Vec<DiscoveredService>) {
|
||||
deleted: false,
|
||||
deleted_at: None,
|
||||
deleted_reason: None,
|
||||
environment: std::collections::HashMap::new(),
|
||||
systemd_service: None,
|
||||
created_at: None,
|
||||
};
|
||||
|
||||
@@ -92,6 +92,7 @@ impl AppManager {
|
||||
deleted: false,
|
||||
deleted_at: None,
|
||||
deleted_reason: None,
|
||||
environment: config.environment.clone(),
|
||||
systemd_service: None,
|
||||
created_at: None,
|
||||
};
|
||||
|
||||
@@ -75,21 +75,8 @@ impl ServiceGenerator {
|
||||
format!("{} {}", executable, config.script_path)
|
||||
};
|
||||
|
||||
// 1. Leer variables del archivo .env si existe
|
||||
let env_file_vars = Self::read_env_file(&config.working_directory);
|
||||
|
||||
// 2. Merge: .env primero, luego sobrescribir con variables del config
|
||||
let mut merged_env = env_file_vars.clone();
|
||||
for (key, value) in &config.environment {
|
||||
merged_env.insert(key.clone(), value.clone());
|
||||
}
|
||||
|
||||
if !env_file_vars.is_empty() {
|
||||
logger.info("ServiceGenerator", &format!("📄 Usando {} variables desde .env", env_file_vars.len()));
|
||||
}
|
||||
|
||||
// Generar variables de entorno (desde .env + config manual)
|
||||
let mut env_lines: Vec<String> = merged_env
|
||||
// Generar líneas de variables de entorno ADICIONALES (solo las del config, no del .env)
|
||||
let mut env_lines: Vec<String> = config.environment
|
||||
.iter()
|
||||
.map(|(key, value)| format!("Environment=\"{}={}\"", key, value))
|
||||
.collect();
|
||||
@@ -113,11 +100,17 @@ impl ServiceGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
let env_vars = env_lines.join("\n");
|
||||
|
||||
// Agregar SyslogIdentifier para logs más claros
|
||||
let syslog_id = format!("SyslogIdentifier=siax-app-{}", config.app_name);
|
||||
|
||||
// Verificar si existe .env en el proyecto
|
||||
let env_file_path = Path::new(&config.working_directory).join(".env");
|
||||
let has_env_file = env_file_path.exists();
|
||||
|
||||
if has_env_file {
|
||||
logger.info("ServiceGenerator", &format!("📄 .env encontrado, usando EnvironmentFile: {}", env_file_path.display()));
|
||||
}
|
||||
|
||||
// Construir el servicio con orden lógico
|
||||
let mut service = format!(
|
||||
r#"[Unit]
|
||||
@@ -134,9 +127,26 @@ WorkingDirectory={}
|
||||
config.working_directory
|
||||
);
|
||||
|
||||
// Agregar variables de entorno (PATH primero, luego las demás)
|
||||
if !env_vars.is_empty() {
|
||||
service.push_str(&env_vars);
|
||||
// Agregar PATH si usa NVM (debe ir primero)
|
||||
// Extraer PATH de env_lines si está en la primera posición
|
||||
let mut path_line: Option<String> = None;
|
||||
if !env_lines.is_empty() && env_lines[0].starts_with("Environment=PATH=") {
|
||||
path_line = Some(env_lines.remove(0));
|
||||
}
|
||||
|
||||
if let Some(path) = path_line {
|
||||
service.push_str(&path);
|
||||
service.push('\n');
|
||||
}
|
||||
|
||||
// ✅ AGREGAR EnvironmentFile si existe .env en el proyecto
|
||||
if has_env_file {
|
||||
service.push_str(&format!("EnvironmentFile={}\n", env_file_path.display()));
|
||||
}
|
||||
|
||||
// Agregar variables de entorno ADICIONALES (las del formulario/JSON)
|
||||
if !env_lines.is_empty() {
|
||||
service.push_str(&env_lines.join("\n"));
|
||||
service.push('\n');
|
||||
}
|
||||
|
||||
|
||||
@@ -374,6 +374,10 @@
|
||||
|
||||
<script>
|
||||
function addEnvVar() {
|
||||
addEnvironmentVariable("", "");
|
||||
}
|
||||
|
||||
function addEnvironmentVariable(key, value) {
|
||||
const container = document.getElementById("env-container");
|
||||
const envItem = document.createElement("div");
|
||||
envItem.className =
|
||||
@@ -382,11 +386,13 @@
|
||||
<input
|
||||
type="text"
|
||||
placeholder="KEY"
|
||||
value="${key}"
|
||||
class="env-key bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="valor"
|
||||
value="${value}"
|
||||
class="env-value bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
<button
|
||||
@@ -475,14 +481,32 @@
|
||||
document.getElementById("working_directory").value =
|
||||
app.path || "";
|
||||
|
||||
// Cargar usuario desde JSON o usar valor por defecto
|
||||
document.getElementById("user").value = app.user || "root";
|
||||
// Cargar usuario desde JSON (sin fallback)
|
||||
document.getElementById("user").value = app.user;
|
||||
document.getElementById("restart_policy").value = "always";
|
||||
document.getElementById("app_type").value = "nodejs";
|
||||
document.getElementById("description").value = "";
|
||||
|
||||
// Cargar variables de entorno (si las hay guardadas)
|
||||
// Por ahora mostramos un campo vacío, el .env se cargará automáticamente
|
||||
// ✅ Cargar variables de entorno ADICIONALES desde JSON
|
||||
// (Las del .env se cargan automáticamente con EnvironmentFile)
|
||||
if (
|
||||
app.environment &&
|
||||
Object.keys(app.environment).length > 0
|
||||
) {
|
||||
// Limpiar el campo vacío por defecto
|
||||
document.getElementById("env-container").innerHTML = "";
|
||||
|
||||
// Agregar cada variable del JSON
|
||||
Object.entries(app.environment).forEach(
|
||||
([key, value]) => {
|
||||
addEnvironmentVariable(key, value);
|
||||
},
|
||||
);
|
||||
|
||||
console.log(
|
||||
`✅ Cargadas ${Object.keys(app.environment).length} variables adicionales desde JSON`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error cargando app:", error);
|
||||
alert("Error al cargar los datos de la aplicación");
|
||||
|
||||
Reference in New Issue
Block a user