新增本地服务器管理面板
新增 egui 服务器管理面板并支持 SSH alias 多服务器巡检 接入硬件状态、服务状态、HTTP 探测和生产巡检状态展示 增加受控 systemd 启动关闭重启操作和中文字体注入 补充本地服务器面板技术方案与团队共享记忆
This commit is contained in:
143
server-rs/crates/server-manager-panel/src/ssh_config.rs
Normal file
143
server-rs/crates/server-manager-panel/src/ssh_config.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SshAlias {
|
||||
pub name: String,
|
||||
pub source: PathBuf,
|
||||
}
|
||||
|
||||
pub fn discover_ssh_aliases() -> Vec<SshAlias> {
|
||||
let Some(home) = std::env::var_os("HOME") else {
|
||||
return Vec::new();
|
||||
};
|
||||
let config_path = PathBuf::from(home).join(".ssh/config");
|
||||
discover_from_file(&config_path)
|
||||
}
|
||||
|
||||
pub fn discover_from_file(path: &Path) -> Vec<SshAlias> {
|
||||
let mut visited = HashSet::new();
|
||||
let mut aliases = Vec::new();
|
||||
discover_inner(path, &mut visited, &mut aliases);
|
||||
dedupe_aliases(aliases)
|
||||
}
|
||||
|
||||
fn discover_inner(path: &Path, visited: &mut HashSet<PathBuf>, aliases: &mut Vec<SshAlias>) {
|
||||
let Ok(canonical) = path.canonicalize() else {
|
||||
return;
|
||||
};
|
||||
if !visited.insert(canonical.clone()) {
|
||||
return;
|
||||
}
|
||||
let Ok(content) = fs::read_to_string(&canonical) else {
|
||||
return;
|
||||
};
|
||||
|
||||
for line in content.lines() {
|
||||
let trimmed = trim_comment(line);
|
||||
let mut parts = trimmed.split_whitespace();
|
||||
let Some(keyword) = parts.next() else {
|
||||
continue;
|
||||
};
|
||||
if keyword.eq_ignore_ascii_case("host") {
|
||||
aliases.extend(parts.filter_map(|name| {
|
||||
is_concrete_alias(name).then(|| SshAlias {
|
||||
name: name.to_owned(),
|
||||
source: canonical.clone(),
|
||||
})
|
||||
}));
|
||||
} else if keyword.eq_ignore_ascii_case("include") {
|
||||
for include in parts {
|
||||
for include_path in expand_include_path(include, canonical.parent()) {
|
||||
discover_inner(&include_path, visited, aliases);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dedupe_aliases(aliases: Vec<SshAlias>) -> Vec<SshAlias> {
|
||||
let mut seen = HashSet::new();
|
||||
let mut deduped = Vec::new();
|
||||
for alias in aliases {
|
||||
if seen.insert(alias.name.clone()) {
|
||||
deduped.push(alias);
|
||||
}
|
||||
}
|
||||
deduped
|
||||
}
|
||||
|
||||
fn trim_comment(line: &str) -> &str {
|
||||
line.split('#').next().unwrap_or("").trim()
|
||||
}
|
||||
|
||||
fn is_concrete_alias(value: &str) -> bool {
|
||||
!value.is_empty()
|
||||
&& !value.starts_with('-')
|
||||
&& !value.starts_with('!')
|
||||
&& !value.contains('*')
|
||||
&& !value.contains('?')
|
||||
&& !value.contains('%')
|
||||
&& !value.contains('/')
|
||||
}
|
||||
|
||||
fn expand_include_path(raw: &str, parent: Option<&Path>) -> Vec<PathBuf> {
|
||||
if raw.contains('*') || raw.contains('?') {
|
||||
// 中文注释:SSH Include 支持复杂 glob;面板只解析普通文件,避免误扫过大目录。
|
||||
return Vec::new();
|
||||
}
|
||||
let expanded = if let Some(rest) = raw.strip_prefix("~/") {
|
||||
std::env::var_os("HOME")
|
||||
.map(PathBuf::from)
|
||||
.map(|home| home.join(rest))
|
||||
} else {
|
||||
let path = PathBuf::from(raw);
|
||||
if path.is_absolute() {
|
||||
Some(path)
|
||||
} else {
|
||||
parent.map(|base| base.join(path))
|
||||
}
|
||||
};
|
||||
expanded.into_iter().collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn host_parser_ignores_wildcards_and_negations() {
|
||||
let mut aliases = Vec::new();
|
||||
let source = PathBuf::from("/tmp/config");
|
||||
for line in [
|
||||
"Host dev release *.internal !blocked",
|
||||
"Host github.com",
|
||||
"Host ?pattern",
|
||||
"Host -bad",
|
||||
] {
|
||||
let trimmed = trim_comment(line);
|
||||
let mut parts = trimmed.split_whitespace();
|
||||
let keyword = parts.next().unwrap();
|
||||
if keyword.eq_ignore_ascii_case("host") {
|
||||
aliases.extend(parts.filter_map(|name| {
|
||||
is_concrete_alias(name).then(|| SshAlias {
|
||||
name: name.to_owned(),
|
||||
source: source.clone(),
|
||||
})
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let names: Vec<_> = dedupe_aliases(aliases)
|
||||
.into_iter()
|
||||
.map(|alias| alias.name)
|
||||
.collect();
|
||||
assert_eq!(names, ["dev", "release", "github.com"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_trimming_keeps_plain_aliases() {
|
||||
assert_eq!(trim_comment(" Host dev # release host "), "Host dev");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user