- 数据库升到第 13 版:主机的登录、连接方式、代理和备注合进 hosts 一行,只有一对多的跳板、书签和端口转发单独成表;全部表改为 STRICT,并用 CHECK 写明约束;用凭据的主机不再冗余存用户名,凭据外键不再级联,在用的凭据删不掉 - 表名 sessions → hosts、session_jumps → host_jumps,session_id → host_id,地址列 host → address;schema 比对去掉排版,以后加字段可以直接 ADD COLUMN - 删除开发期的历史迁移代码:版本 1–12 的库直接拒绝打开、不动文件 - 代码里 Session / SessionId / SessionStore 等改为 Host*,模块 session/ → host/,Host.host → Host.address,菜单命令与界面元素 id 同步改名 - 外部 CLI 的参数、JSON 字段和错误码改为 host(host_not_found),协议版本升到 2 - CLAUDE.md、AGENTS.md、README 与 Agent skill 同步更新
108 lines
3.5 KiB
Rust
108 lines
3.5 KiB
Rust
//! Where ShellRS keeps its data on disk.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
/// Overrides the data directory. Set by tests and handy during development.
|
|
const DATA_DIR_ENV: &str = "SHELLRS_DATA_DIR";
|
|
|
|
/// The directory holding ShellRS's own files, e.g.
|
|
/// `~/Library/Application Support/shellrs` on macOS. Falls back to the
|
|
/// current directory on the platforms where `dirs` has nothing to offer.
|
|
pub fn data_dir() -> PathBuf {
|
|
if let Some(dir) = std::env::var_os(DATA_DIR_ENV) {
|
|
return PathBuf::from(dir);
|
|
}
|
|
dirs::data_dir()
|
|
.map(|dir| dir.join("shellrs"))
|
|
.unwrap_or_else(|| PathBuf::from("."))
|
|
}
|
|
|
|
/// The SQLite file holding hosts and groups. The parent directory is
|
|
/// created if it does not exist yet.
|
|
pub fn database_path() -> std::io::Result<PathBuf> {
|
|
let dir = data_dir();
|
|
std::fs::create_dir_all(&dir)?;
|
|
Ok(dir.join("shellrs.db"))
|
|
}
|
|
|
|
/// Where the private keys pasted into or generated by the credential form
|
|
/// are kept, beside the database. Created when the first one is saved.
|
|
pub fn keys_dir() -> PathBuf {
|
|
data_dir().join("keys")
|
|
}
|
|
|
|
/// Where updates are downloaded to: the user's cache folder. With
|
|
/// `SHELLRS_DATA_DIR` set it is beside the data instead, so a test or
|
|
/// development instance never shares downloads with the installed app.
|
|
pub fn updates_dir() -> PathBuf {
|
|
if std::env::var_os(DATA_DIR_ENV).is_some() {
|
|
return data_dir().join("updates");
|
|
}
|
|
dirs::cache_dir()
|
|
.map(|dir| dir.join("shellrs").join("updates"))
|
|
.unwrap_or_else(|| data_dir().join("updates"))
|
|
}
|
|
|
|
/// The interface settings, a small JSON file beside the database.
|
|
pub fn settings_path() -> std::io::Result<PathBuf> {
|
|
let dir = data_dir();
|
|
std::fs::create_dir_all(&dir)?;
|
|
Ok(dir.join("settings.json"))
|
|
}
|
|
|
|
/// Where the running app listens for the `shellrs` command. The command
|
|
/// and the app both ask here, so both follow `SHELLRS_DATA_DIR`.
|
|
pub fn cli_socket_path() -> PathBuf {
|
|
cli_endpoint(&data_dir())
|
|
}
|
|
|
|
/// Where an app keeping its data in `dir` listens for the `shellrs`
|
|
/// command: a socket in that directory, or on Windows a named pipe.
|
|
///
|
|
/// Pipe names are one namespace for the whole machine, so the name is made
|
|
/// from the data directory. That lives in the user's profile, so each user,
|
|
/// and each `SHELLRS_DATA_DIR`, gets a pipe of its own.
|
|
pub fn cli_endpoint(dir: &Path) -> PathBuf {
|
|
if cfg!(windows) {
|
|
PathBuf::from(pipe_name(dir))
|
|
} else {
|
|
dir.join("cli.sock")
|
|
}
|
|
}
|
|
|
|
/// `\\.\pipe\shellrs-cli-` and a hash of the directory. Windows paths
|
|
/// ignore case, so the hash does too.
|
|
fn pipe_name(dir: &Path) -> String {
|
|
use sha2::{Digest as _, Sha256};
|
|
|
|
let dir = std::path::absolute(dir).unwrap_or_else(|_| dir.to_path_buf());
|
|
let digest = Sha256::digest(dir.to_string_lossy().to_lowercase().as_bytes());
|
|
let hex: String = digest[..8]
|
|
.iter()
|
|
.map(|byte| format!("{byte:02x}"))
|
|
.collect();
|
|
format!(r"\\.\pipe\shellrs-cli-{hex}")
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::path::Path;
|
|
|
|
use super::pipe_name;
|
|
|
|
#[test]
|
|
fn each_data_directory_gets_a_pipe_of_its_own() {
|
|
let pipe = pipe_name(Path::new("/Users/me/AppData/Roaming/shellrs"));
|
|
assert!(pipe.starts_with(r"\\.\pipe\shellrs-cli-"));
|
|
assert_eq!(pipe.len(), r"\\.\pipe\shellrs-cli-".len() + 16);
|
|
assert_eq!(
|
|
pipe,
|
|
pipe_name(Path::new("/USERS/me/appdata/Roaming/SHELLRS"))
|
|
);
|
|
assert_ne!(
|
|
pipe,
|
|
pipe_name(Path::new("/Users/other/AppData/Roaming/shellrs"))
|
|
);
|
|
}
|
|
}
|