feat: add Windows support for skills symlinks and secret key permissions
- Add Windows symlink support in skills/mod.rs with fallback chain: 1. symlink_dir (requires admin/developer mode) 2. mklink /J junction (works without admin) 3. copy_dir_recursive fallback - Add Windows file permissions in security/secrets.rs using icacls - Add copy_dir_recursive helper function for non-Unix platforms Fixes #28
This commit is contained in:
parent
5476195a7f
commit
27b7df53da
2 changed files with 71 additions and 4 deletions
|
|
@ -181,13 +181,22 @@ impl SecretStore {
|
||||||
fs::write(&self.key_path, hex_encode(&key))
|
fs::write(&self.key_path, hex_encode(&key))
|
||||||
.context("Failed to write secret key file")?;
|
.context("Failed to write secret key file")?;
|
||||||
|
|
||||||
// Set restrictive permissions (Unix only)
|
// Set restrictive permissions
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
fs::set_permissions(&self.key_path, fs::Permissions::from_mode(0o600))
|
fs::set_permissions(&self.key_path, fs::Permissions::from_mode(0o600))
|
||||||
.context("Failed to set key file permissions")?;
|
.context("Failed to set key file permissions")?;
|
||||||
}
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
// On Windows, use icacls to restrict permissions to current user only
|
||||||
|
let _ = std::process::Command::new("icacls")
|
||||||
|
.arg(&self.key_path)
|
||||||
|
.args(["/inheritance:r", "/grant:r"])
|
||||||
|
.arg(format!("{}:F", std::env::var("USERNAME").unwrap_or_default()))
|
||||||
|
.output();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(key)
|
Ok(key)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,23 @@ pub fn init_skills_dir(workspace_dir: &Path) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recursively copy a directory (used as fallback when symlinks aren't available)
|
||||||
|
#[cfg(any(windows, not(unix)))]
|
||||||
|
fn copy_dir_recursive(src: &Path, dest: &Path) -> Result<()> {
|
||||||
|
std::fs::create_dir_all(dest)?;
|
||||||
|
for entry in std::fs::read_dir(src)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let src_path = entry.path();
|
||||||
|
let dest_path = dest.join(entry.file_name());
|
||||||
|
if src_path.is_dir() {
|
||||||
|
copy_dir_recursive(&src_path, &dest_path)?;
|
||||||
|
} else {
|
||||||
|
std::fs::copy(&src_path, &dest_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle the `skills` CLI command
|
/// Handle the `skills` CLI command
|
||||||
pub fn handle_command(command: super::SkillCommands, workspace_dir: &Path) -> Result<()> {
|
pub fn handle_command(command: super::SkillCommands, workspace_dir: &Path) -> Result<()> {
|
||||||
match command {
|
match command {
|
||||||
|
|
@ -303,10 +320,51 @@ pub fn handle_command(command: super::SkillCommands, workspace_dir: &Path) -> Re
|
||||||
dest.display()
|
dest.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[cfg(not(unix))]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
// On non-unix, copy the directory
|
// On Windows, try symlink first (requires admin or developer mode),
|
||||||
anyhow::bail!("Symlink not supported on this platform. Copy the skill directory manually.");
|
// fall back to directory junction, then copy
|
||||||
|
use std::os::windows::fs::symlink_dir;
|
||||||
|
if symlink_dir(&src, &dest).is_ok() {
|
||||||
|
println!(
|
||||||
|
" {} Skill linked: {}",
|
||||||
|
console::style("✓").green().bold(),
|
||||||
|
dest.display()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Try junction as fallback (works without admin)
|
||||||
|
let junction_result = std::process::Command::new("cmd")
|
||||||
|
.args(["/C", "mklink", "/J"])
|
||||||
|
.arg(&dest)
|
||||||
|
.arg(&src)
|
||||||
|
.output();
|
||||||
|
|
||||||
|
if junction_result.is_ok() && junction_result.unwrap().status.success() {
|
||||||
|
println!(
|
||||||
|
" {} Skill linked (junction): {}",
|
||||||
|
console::style("✓").green().bold(),
|
||||||
|
dest.display()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Final fallback: copy the directory
|
||||||
|
copy_dir_recursive(&src, &dest)?;
|
||||||
|
println!(
|
||||||
|
" {} Skill copied: {}",
|
||||||
|
console::style("✓").green().bold(),
|
||||||
|
dest.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(any(unix, windows)))]
|
||||||
|
{
|
||||||
|
// On other platforms, copy the directory
|
||||||
|
copy_dir_recursive(&src, &dest)?;
|
||||||
|
println!(
|
||||||
|
" {} Skill copied: {}",
|
||||||
|
console::style("✓").green().bold(),
|
||||||
|
dest.display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue