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: false,
|
||||||
deleted_at: None,
|
deleted_at: None,
|
||||||
deleted_reason: None,
|
deleted_reason: None,
|
||||||
|
environment: config.environment.clone(),
|
||||||
systemd_service: None,
|
systemd_service: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ pub struct MonitoredApp {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub deleted_reason: Option<String>,
|
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
|
// DEPRECATED: Mantener por compatibilidad con versiones antiguas
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub systemd_service: Option<String>,
|
pub systemd_service: Option<String>,
|
||||||
@@ -217,6 +223,7 @@ impl ConfigManager {
|
|||||||
deleted: false,
|
deleted: false,
|
||||||
deleted_at: None,
|
deleted_at: None,
|
||||||
deleted_reason: None,
|
deleted_reason: None,
|
||||||
|
environment: std::collections::HashMap::new(),
|
||||||
systemd_service: None,
|
systemd_service: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ pub fn sync_discovered_services(services: Vec<DiscoveredService>) {
|
|||||||
deleted: false,
|
deleted: false,
|
||||||
deleted_at: None,
|
deleted_at: None,
|
||||||
deleted_reason: None,
|
deleted_reason: None,
|
||||||
|
environment: std::collections::HashMap::new(),
|
||||||
systemd_service: None,
|
systemd_service: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ impl AppManager {
|
|||||||
deleted: false,
|
deleted: false,
|
||||||
deleted_at: None,
|
deleted_at: None,
|
||||||
deleted_reason: None,
|
deleted_reason: None,
|
||||||
|
environment: config.environment.clone(),
|
||||||
systemd_service: None,
|
systemd_service: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,21 +75,8 @@ impl ServiceGenerator {
|
|||||||
format!("{} {}", executable, config.script_path)
|
format!("{} {}", executable, config.script_path)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. Leer variables del archivo .env si existe
|
// Generar líneas de variables de entorno ADICIONALES (solo las del config, no del .env)
|
||||||
let env_file_vars = Self::read_env_file(&config.working_directory);
|
let mut env_lines: Vec<String> = config.environment
|
||||||
|
|
||||||
// 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
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(key, value)| format!("Environment=\"{}={}\"", key, value))
|
.map(|(key, value)| format!("Environment=\"{}={}\"", key, value))
|
||||||
.collect();
|
.collect();
|
||||||
@@ -113,11 +100,17 @@ impl ServiceGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let env_vars = env_lines.join("\n");
|
|
||||||
|
|
||||||
// Agregar SyslogIdentifier para logs más claros
|
// Agregar SyslogIdentifier para logs más claros
|
||||||
let syslog_id = format!("SyslogIdentifier=siax-app-{}", config.app_name);
|
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
|
// Construir el servicio con orden lógico
|
||||||
let mut service = format!(
|
let mut service = format!(
|
||||||
r#"[Unit]
|
r#"[Unit]
|
||||||
@@ -134,9 +127,26 @@ WorkingDirectory={}
|
|||||||
config.working_directory
|
config.working_directory
|
||||||
);
|
);
|
||||||
|
|
||||||
// Agregar variables de entorno (PATH primero, luego las demás)
|
// Agregar PATH si usa NVM (debe ir primero)
|
||||||
if !env_vars.is_empty() {
|
// Extraer PATH de env_lines si está en la primera posición
|
||||||
service.push_str(&env_vars);
|
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');
|
service.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -374,6 +374,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function addEnvVar() {
|
function addEnvVar() {
|
||||||
|
addEnvironmentVariable("", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEnvironmentVariable(key, value) {
|
||||||
const container = document.getElementById("env-container");
|
const container = document.getElementById("env-container");
|
||||||
const envItem = document.createElement("div");
|
const envItem = document.createElement("div");
|
||||||
envItem.className =
|
envItem.className =
|
||||||
@@ -382,11 +386,13 @@
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="KEY"
|
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"
|
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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="valor"
|
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"
|
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
|
<button
|
||||||
@@ -475,14 +481,32 @@
|
|||||||
document.getElementById("working_directory").value =
|
document.getElementById("working_directory").value =
|
||||||
app.path || "";
|
app.path || "";
|
||||||
|
|
||||||
// Cargar usuario desde JSON o usar valor por defecto
|
// Cargar usuario desde JSON (sin fallback)
|
||||||
document.getElementById("user").value = app.user || "root";
|
document.getElementById("user").value = app.user;
|
||||||
document.getElementById("restart_policy").value = "always";
|
document.getElementById("restart_policy").value = "always";
|
||||||
document.getElementById("app_type").value = "nodejs";
|
document.getElementById("app_type").value = "nodejs";
|
||||||
document.getElementById("description").value = "";
|
document.getElementById("description").value = "";
|
||||||
|
|
||||||
// Cargar variables de entorno (si las hay guardadas)
|
// ✅ Cargar variables de entorno ADICIONALES desde JSON
|
||||||
// Por ahora mostramos un campo vacío, el .env se cargará automáticamente
|
// (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) {
|
} catch (error) {
|
||||||
console.error("Error cargando app:", error);
|
console.error("Error cargando app:", error);
|
||||||
alert("Error al cargar los datos de la aplicación");
|
alert("Error al cargar los datos de la aplicación");
|
||||||
|
|||||||
Reference in New Issue
Block a user