Initial public release

This commit is contained in:
BB-fat
2026-06-22 17:36:26 +08:00
commit f1036a860c
289 changed files with 54589 additions and 0 deletions
+163
View File
@@ -0,0 +1,163 @@
//! Filesystem layout for the local daemon home (`~/.bsk` by default).
//!
//! Per design §3.2 the daemon owns a per-user home directory containing:
//!
//! ```text
//! ~/.bsk/
//! daemon.lock advisory file lock (M2.2)
//! daemon.sock UDS socket (Unix only, M2.3) — actually under run/
//! daemon.pid deprecated; pid lives in daemon.json
//! daemon.json DaemonInfo (M2.4)
//! daemon.log rotating daily file (M3.4)
//! run/ ephemeral runtime artifacts (sockets etc.)
//! ```
//!
//! Tests override the location via `BSK_HOME` so the user's real `~/.bsk`
//! is never touched. On Unix the home is created with mode 0700.
use std::env;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
/// Environment variable that overrides the home directory.
pub const BSK_HOME_ENV: &str = "BSK_HOME";
/// Resolve the bsk home directory:
/// 1. `BSK_HOME` env var (any non-empty value); or
/// 2. `~/.bsk` (using [`dirs::home_dir`]).
pub fn bsk_home() -> Result<PathBuf> {
if let Ok(p) = env::var(BSK_HOME_ENV) {
if !p.is_empty() {
return Ok(PathBuf::from(p));
}
}
let home = dirs::home_dir().context("could not determine user home directory")?;
Ok(home.join(".bsk"))
}
/// Ensure `~/.bsk` (or `$BSK_HOME`) exists, creating it with restrictive
/// permissions on Unix (`chmod 0700`). Returns the absolute path.
pub fn ensure_bsk_home() -> Result<PathBuf> {
let home = bsk_home()?;
if !home.exists() {
std::fs::create_dir_all(&home)
.with_context(|| format!("create bsk home {}", home.display()))?;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(0o700);
std::fs::set_permissions(&home, perms)
.with_context(|| format!("chmod 0700 {}", home.display()))?;
}
ensure_run_dir(&home)?;
Ok(home)
}
fn ensure_run_dir(home: &Path) -> Result<()> {
let run = home.join("run");
if !run.exists() {
std::fs::create_dir_all(&run).with_context(|| format!("create {}", run.display()))?;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(0o700);
std::fs::set_permissions(&run, perms)?;
}
Ok(())
}
pub fn lock_path() -> Result<PathBuf> {
Ok(bsk_home()?.join("daemon.lock"))
}
pub fn info_path() -> Result<PathBuf> {
Ok(bsk_home()?.join("daemon.json"))
}
pub fn log_path() -> Result<PathBuf> {
Ok(bsk_home()?.join("daemon.log"))
}
pub fn log_dir() -> Result<PathBuf> {
bsk_home()
}
/// Path to the IPC socket (Unix UDS). On Windows the IPC layer uses a
/// named-pipe whose name is derived from [`pipe_name`].
pub fn sock_path() -> Result<PathBuf> {
Ok(bsk_home()?.join("run").join("daemon.sock"))
}
/// Windows named-pipe name. Include the resolved `BSK_HOME` path in the
/// token so test homes and custom installs do not share a predictable
/// per-username pipe.
#[cfg(windows)]
pub fn pipe_name() -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let user = env::var("USERNAME").unwrap_or_else(|_| "default".to_string());
let home = bsk_home()
.map(|path| path.to_string_lossy().into_owned())
.unwrap_or_else(|_| "unknown-home".to_string());
let mut hasher = DefaultHasher::new();
user.hash(&mut hasher);
home.hash(&mut hasher);
format!(r"\\.\pipe\bsk-daemon-{user}-{:016x}", hasher.finish())
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
use tempfile::TempDir;
/// Serialise tests that mutate the global `BSK_HOME` env var.
fn env_guard() -> &'static Mutex<()> {
static GUARD: Mutex<()> = Mutex::new(());
&GUARD
}
fn with_temp_home<F: FnOnce(&Path)>(f: F) {
let _lock = env_guard().lock().unwrap_or_else(|e| e.into_inner());
let tmp = TempDir::new().unwrap();
// SAFETY: serialised by env_guard above.
unsafe {
std::env::set_var(BSK_HOME_ENV, tmp.path().join("bsk"));
}
f(tmp.path());
unsafe {
std::env::remove_var(BSK_HOME_ENV);
}
}
#[test]
fn ensure_creates_home() {
with_temp_home(|root| {
let home = ensure_bsk_home().unwrap();
assert!(home.starts_with(root));
assert!(home.exists());
assert!(home.join("run").exists());
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = std::fs::metadata(&home).unwrap().permissions().mode() & 0o777;
assert_eq!(mode, 0o700);
}
});
}
#[test]
fn computes_expected_paths() {
with_temp_home(|_| {
let home = ensure_bsk_home().unwrap();
assert_eq!(lock_path().unwrap(), home.join("daemon.lock"));
assert_eq!(info_path().unwrap(), home.join("daemon.json"));
assert_eq!(log_path().unwrap(), home.join("daemon.log"));
assert_eq!(sock_path().unwrap(), home.join("run").join("daemon.sock"));
});
}
}