refactor: 配置文件引擎迁移至 mlua 并优化环境变量注入
- 配置文件解析引擎由 shim 替换为 mlua (Lua 5.4) - 优化路由策略:统一以根目录 shims.lua 为入口,支持 tools/ 目录模块化降级探测 - 自动注入 __SHIM_DIR__ 绝对路径与 package.path 模块搜索路径 - 环境变量改用 Command::envs 安全注入,并在 UAC 提权时透传
This commit is contained in:
225
src/shims.rs
225
src/shims.rs
@@ -1,89 +1,166 @@
|
||||
use fs_err as fs;
|
||||
use mlua::{Error as LuaError, Lua, Table, Value};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
env, fs,
|
||||
io::{Error, ErrorKind},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
pub struct Shim {
|
||||
pub target_path: PathBuf,
|
||||
pub args: Option<Vec<String>>,
|
||||
pub envs: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl Shim {
|
||||
pub fn init() -> Result<Self, Error> {
|
||||
let shim_path = get_shim_file_path()?;
|
||||
let kvs = parse_shim_file(&shim_path)?;
|
||||
let target_path = match kvs.get("path") {
|
||||
Some(p) => PathBuf::from(p),
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::NotFound,
|
||||
format!("no path key in {}", shim_path.to_string_lossy()),
|
||||
let (_exe_path, _exe_dir, root_dir, tools_dir, exe_name) = Self::get_exe_context()?;
|
||||
|
||||
let lua = Lua::new();
|
||||
let globals = lua.globals();
|
||||
|
||||
// 1. 将根目录 (Root) 注入为 __SHIM_DIR__,作为全局相对路径的锚点
|
||||
let root_dir_str = root_dir.to_string_lossy().replace('\\', "/");
|
||||
let tools_dir_str = tools_dir.to_string_lossy().replace('\\', "/");
|
||||
globals
|
||||
.set("__SHIM_DIR__", root_dir_str.clone())
|
||||
.map_err(|e| Error::new(ErrorKind::Other, format!("注入 __SHIM_DIR__ 失败: {}", e)))?;
|
||||
|
||||
// 2. 注册 get_env 获取环境变量的函数
|
||||
let get_env = lua
|
||||
.create_function(|_, key: String| -> Result<String, LuaError> {
|
||||
Ok(env::var(key).unwrap_or_default())
|
||||
})
|
||||
.map_err(|e| Error::new(ErrorKind::Other, format!("注册 get_env 失败: {}", e)))?;
|
||||
|
||||
globals
|
||||
.set("get_env", get_env)
|
||||
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
|
||||
|
||||
// 3. 增强 package.path,让 require 自动在当前目录及 conf.d 目录下寻找模块
|
||||
if let Ok(package) = globals.get::<Table>("package") {
|
||||
if let Ok(path) = package.get::<String>("path") {
|
||||
let new_path = format!(
|
||||
"{};{}/?.lua;{}/?/init.lua;{}/?.lua;{}/?/init.lua",
|
||||
path, root_dir_str, root_dir_str, tools_dir_str, tools_dir_str
|
||||
);
|
||||
let _ = package.set("path", new_path);
|
||||
}
|
||||
}
|
||||
|
||||
Self::resolve_config(&lua, &root_dir, &tools_dir, &exe_name)
|
||||
}
|
||||
|
||||
fn get_exe_context() -> Result<(PathBuf, PathBuf, PathBuf, PathBuf, String), Error> {
|
||||
let current_exe = env::current_exe().map_err(|e| {
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("获取代理程序(shim)路径失败: {}", e),
|
||||
)
|
||||
})?;
|
||||
|
||||
let exe_dir = current_exe
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new(""))
|
||||
.to_path_buf();
|
||||
// root_dir 是 bin 的上一级(项目的根目录)
|
||||
let root_dir = exe_dir
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new(""))
|
||||
.to_path_buf();
|
||||
// tools_dir 是 root 下的 tools 目录
|
||||
let tools_dir = root_dir.join("tools");
|
||||
|
||||
let exe_name = current_exe
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
|
||||
Ok((current_exe, exe_dir, root_dir, tools_dir, exe_name))
|
||||
}
|
||||
/// 执行 Lua 脚本文件并返回 Table
|
||||
fn evaluate_lua_script(lua: &Lua, config_file: &Path) -> Result<Table, Error> {
|
||||
let code = fs::read_to_string(config_file)?; // 这里不安全需要优化
|
||||
|
||||
lua.load(&code)
|
||||
.set_name(config_file.to_string_lossy().into_owned())
|
||||
.eval::<Table>()
|
||||
.map_err(|e| {
|
||||
Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("Lua 脚本解析错误 ({}): {}", config_file.display(), e),
|
||||
)
|
||||
})
|
||||
}
|
||||
/// 利用 mlua 原生类型转换,将 Lua Table 映射为 Rust Shim 结构体
|
||||
fn from_table(table: Table) -> Result<Self, Error> {
|
||||
let path_str: String = table
|
||||
.get("path")
|
||||
.map_err(|_| Error::new(ErrorKind::InvalidData, "配置文件中缺少必需的 'path' 字段"))?;
|
||||
let target_path = PathBuf::from(path_str);
|
||||
|
||||
let args: Option<Vec<String>> = table.get("args").ok();
|
||||
let envs: Option<HashMap<String, String>> = table.get("env").ok();
|
||||
|
||||
Ok(Self {
|
||||
target_path,
|
||||
args,
|
||||
envs,
|
||||
})
|
||||
}
|
||||
fn resolve_config(
|
||||
lua: &Lua,
|
||||
root_dir: &Path,
|
||||
tools_dir: &Path,
|
||||
exe_name: &str,
|
||||
) -> Result<Self, Error> {
|
||||
// 1. 尝试全局统一入口 shims.lua
|
||||
let entry_config = root_dir.join("shims.lua");
|
||||
if entry_config.exists() {
|
||||
let root_table = Self::evaluate_lua_script(lua, &entry_config)?;
|
||||
|
||||
// 如果 shims.lua 中存在以当前程序名命名的配置项,直接接管并返回
|
||||
if let Ok(Value::Table(v)) = root_table.get::<Value>(exe_name) {
|
||||
return Self::from_table(v);
|
||||
}
|
||||
// 如果 shims.lua 存在但未接管 exe_name,代码会自然“穿透”流转到下方寻找独立配置
|
||||
}
|
||||
|
||||
// 2. 降级探测特定同名独立配置文件(优先 tools/{exe_name}.lua,次选 root/{exe_name}.lua)
|
||||
let specific_config_tools = tools_dir.join(format!("{}.lua", exe_name));
|
||||
let specific_config_root = root_dir.join(format!("{}.lua", exe_name));
|
||||
|
||||
let specific_config = if specific_config_tools.exists() {
|
||||
Some(specific_config_tools)
|
||||
} else if specific_config_root.exists() {
|
||||
Some(specific_config_root)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(config_file) = specific_config {
|
||||
let config_table = Self::evaluate_lua_script(lua, &config_file)?;
|
||||
return if config_table.contains_key("path").unwrap_or(false) {
|
||||
Self::from_table(config_table)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!(
|
||||
"独立配置文件 [{}] 必须包含 'path' 字段",
|
||||
config_file.display()
|
||||
),
|
||||
))
|
||||
}
|
||||
};
|
||||
let args = kvs.get("args").map(|a| {
|
||||
a.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
Ok(Self { target_path, args })
|
||||
};
|
||||
}
|
||||
|
||||
// 3. 两阶段探测均失败后的统一报错
|
||||
Err(Error::new(
|
||||
ErrorKind::NotFound,
|
||||
format!(
|
||||
"未找到关于 '{}' 的配置。请确保在 shims.lua 中进行了定义,或者存在独立的 {}.lua 配置文件。",
|
||||
exe_name, exe_name
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_shim_file_path() -> Result<PathBuf, Error> {
|
||||
let mut current_exe = env::current_exe().map_err(|e| {
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("acquiring shim executable path: {}", e),
|
||||
)
|
||||
})?;
|
||||
if !current_exe.set_extension("shim") {
|
||||
return Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("{} is not a file", current_exe.to_string_lossy()),
|
||||
));
|
||||
}
|
||||
Ok(current_exe)
|
||||
}
|
||||
use unicode_bom::Bom;
|
||||
fn parse_shim_file(shim_path: &Path) -> Result<HashMap<String, String>, Error> {
|
||||
let mut kvs = HashMap::new();
|
||||
|
||||
let raw_content = fs::read_to_string(shim_path).map_err(|e| {
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("reading {}: {}", shim_path.to_string_lossy(), e),
|
||||
)
|
||||
})?;
|
||||
//NOTE: expedient trick for utf-8 with bom
|
||||
let bom = Bom::from(raw_content.as_bytes());
|
||||
for line in raw_content[bom.len()..]
|
||||
.lines()
|
||||
.filter(|l| !l.trim().is_empty())
|
||||
{
|
||||
let mut components = line.split("=");
|
||||
let key = match components.next() {
|
||||
Some(k) => k.trim(),
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("invalid line in shim file: {}", line),
|
||||
));
|
||||
}
|
||||
};
|
||||
let value = match components.next() {
|
||||
Some(v) => v.trim(),
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("invalid line in shim file: {}", line),
|
||||
));
|
||||
}
|
||||
};
|
||||
kvs.insert(key.to_string(), value.to_string());
|
||||
}
|
||||
|
||||
Ok(kvs)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user