fix: read secrets from legacy data dir

This commit is contained in:
Keisuke Hirata 2026-07-28 04:12:26 +09:00
parent 5d1950647e
commit 6114cc9018
No known key found for this signature in database
4 changed files with 71 additions and 6 deletions

View File

@ -5,6 +5,9 @@
//! - **`config_dir`** — 人が手で書く / 編集する設定。`profiles.toml`, //! - **`config_dir`** — 人が手で書く / 編集する設定。`profiles.toml`,
//! `providers.toml`, `models.toml`, `prompts/`, `prompts.toml` 等 //! `providers.toml`, `models.toml`, `prompts/`, `prompts.toml` 等
//! - **`data_dir`** — プログラムが書く永続データ。`sessions/` 等 //! - **`data_dir`** — プログラムが書く永続データ。`sessions/` 等
//! - **`secret_data_dir`** — local secret store の読み書き base。既存
//! secret store は path-derived key を使うため、通常 data とは別に
//! legacy `$HOME/.yoi` を既定にする
//! - **`runtime_dir`** — 再起動で消えてよいランタイム状態。socket, //! - **`runtime_dir`** — 再起動で消えてよいランタイム状態。socket,
//! `workers.json`, `pid` ファイル等 //! `workers.json`, `pid` ファイル等
//! //!
@ -14,6 +17,7 @@
//! |---|---|---|---|---| //! |---|---|---|---|---|
//! | config | `YOI_CONFIG_DIR` | `$YOI_HOME/config` | `$XDG_CONFIG_HOME/yoi` | `$HOME/.config/yoi` | //! | config | `YOI_CONFIG_DIR` | `$YOI_HOME/config` | `$XDG_CONFIG_HOME/yoi` | `$HOME/.config/yoi` |
//! | data | `YOI_DATA_DIR` | `$YOI_HOME` | `$XDG_DATA_HOME/yoi` | `$HOME/.local/share/yoi` | //! | data | `YOI_DATA_DIR` | `$YOI_HOME` | `$XDG_DATA_HOME/yoi` | `$HOME/.local/share/yoi` |
//! | secrets | `YOI_DATA_DIR` | `$YOI_HOME` | — | `$HOME/.yoi` |
//! | runtime | `YOI_RUNTIME_DIR` | `$YOI_HOME/run` | `$XDG_RUNTIME_DIR/yoi` | `$HOME/.yoi/run` | //! | runtime | `YOI_RUNTIME_DIR` | `$YOI_HOME/run` | `$XDG_RUNTIME_DIR/yoi` | `$HOME/.yoi/run` |
//! //!
//! `YOI_HOME=$X` のとき config は `$X/config`、data は `$X` 直下、 //! `YOI_HOME=$X` のとき config は `$X/config`、data は `$X` 直下、
@ -47,6 +51,19 @@ pub fn data_dir() -> Option<PathBuf> {
) )
} }
/// Secret store 用の data directory。
///
/// SecretStore は base path から復号 key を導出するため、通常 data dir の
/// XDG fallback には追従させず、明示 override が無い場合は legacy
/// `$HOME/.yoi` を読み書きする。
pub fn secret_data_dir() -> Option<PathBuf> {
resolve_secret_data_dir_from_parts(
env_path("YOI_DATA_DIR"),
env_path("YOI_HOME"),
env_path("HOME"),
)
}
/// ランタイムディレクトリ。socket, `workers.json`, Worker ごとの `pid` / /// ランタイムディレクトリ。socket, `workers.json`, Worker ごとの `pid` /
/// `status.json` 等が置かれる。再起動で消えて構わない。 /// `status.json` 等が置かれる。再起動で消えて構わない。
pub fn runtime_dir() -> Option<PathBuf> { pub fn runtime_dir() -> Option<PathBuf> {
@ -147,6 +164,20 @@ fn resolve_data_dir_from_parts(
Some(home?.join(".local").join("share").join("yoi")) Some(home?.join(".local").join("share").join("yoi"))
} }
fn resolve_secret_data_dir_from_parts(
yoi_data_dir: Option<PathBuf>,
yoi_home: Option<PathBuf>,
home: Option<PathBuf>,
) -> Option<PathBuf> {
if let Some(p) = yoi_data_dir {
return Some(p);
}
if let Some(p) = yoi_home {
return Some(p);
}
Some(home?.join(".yoi"))
}
fn resolve_runtime_dir_from_parts( fn resolve_runtime_dir_from_parts(
yoi_runtime_dir: Option<PathBuf>, yoi_runtime_dir: Option<PathBuf>,
yoi_home: Option<PathBuf>, yoi_home: Option<PathBuf>,
@ -322,6 +353,40 @@ mod tests {
); );
} }
#[test]
fn secret_data_dir_falls_back_to_home_dot_yoi() {
assert_eq!(
resolve_secret_data_dir_from_parts(None, None, Some(PathBuf::from("/h"))).unwrap(),
PathBuf::from("/h/.yoi")
);
}
#[test]
fn secret_data_dir_yoi_home_wins() {
assert_eq!(
resolve_secret_data_dir_from_parts(
None,
Some(PathBuf::from("/sand")),
Some(PathBuf::from("/h"))
)
.unwrap(),
PathBuf::from("/sand")
);
}
#[test]
fn secret_data_dir_explicit_wins_over_yoi_home() {
assert_eq!(
resolve_secret_data_dir_from_parts(
Some(PathBuf::from("/explicit-data")),
Some(PathBuf::from("/sand")),
Some(PathBuf::from("/h")),
)
.unwrap(),
PathBuf::from("/explicit-data")
);
}
#[test] #[test]
fn runtime_dir_prefers_xdg_runtime_dir() { fn runtime_dir_prefers_xdg_runtime_dir() {
assert_eq!( assert_eq!(

View File

@ -47,7 +47,7 @@ impl WebTools {
.user_agent("yoi-web-tools/0.1") .user_agent("yoi-web-tools/0.1")
.build() .build()
.expect("static reqwest client configuration is valid"); .expect("static reqwest client configuration is valid");
let secret_store = manifest::paths::data_dir().map(SecretStore::new); let secret_store = manifest::paths::secret_data_dir().map(SecretStore::new);
Self { Self {
config, config,
client, client,

View File

@ -218,10 +218,10 @@ impl std::fmt::Debug for Action {
} }
pub async fn launch() -> ExitCode { pub async fn launch() -> ExitCode {
let data_dir = match manifest::paths::data_dir() { let data_dir = match manifest::paths::secret_data_dir() {
Some(path) => path, Some(path) => path,
None => { None => {
eprintln!("yoi keys: could not determine yoi data directory"); eprintln!("yoi keys: could not determine yoi secret data directory");
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
}; };

View File

@ -70,11 +70,11 @@ struct DefaultSecretResolver;
impl SecretResolver for DefaultSecretResolver { impl SecretResolver for DefaultSecretResolver {
fn get_secret(&self, id: &str) -> Result<SecretValue, secrets::Error> { fn get_secret(&self, id: &str) -> Result<SecretValue, secrets::Error> {
let data_dir = manifest::paths::data_dir().ok_or_else(|| secrets::Error::Read { let data_dir = manifest::paths::secret_data_dir().ok_or_else(|| secrets::Error::Read {
path: std::path::PathBuf::from("<data_dir>"), path: std::path::PathBuf::from("<secret_data_dir>"),
source: std::io::Error::new( source: std::io::Error::new(
std::io::ErrorKind::NotFound, std::io::ErrorKind::NotFound,
"could not determine yoi data directory", "could not determine yoi secret data directory",
), ),
})?; })?;
SecretStore::new(data_dir).get(id) SecretStore::new(data_dir).get(id)