diff --git a/Cargo.toml b/Cargo.toml index 5236315..868fff7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "BSD-2-Clause" readme = "README.md" homepage = "https://github.com/dotinx/naj" repository = "https://github.com/dotinx/naj" -categories = ["command-line-utilities", "development-tools::cargo-plugins"] +categories = ["command-line-utilities"] exclude = ["tests/", "scripts/",] diff --git a/README.md b/README.md index 6d95e6a..6fc30f6 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,8 @@ On the first run, Naj will automatically create these directories and a default ### Environment Variables -* `GOSH_CONFIG_PATH`: Override the config directory (Useful for NixOS or testing). -* `GOSH_MOCKING=1`: Dry-run mode. Prints the constructed `git` command to stderr instead of executing it. +* `NAJ_CONFIG_PATH`: Override the config directory (Useful for NixOS or testing). +* `NAJ_MOCKING=1`: Dry-run mode. Prints the constructed `git` command to stderr instead of executing it. ## 🔒 Security Design: Blind Injection diff --git a/scripts/tests/README.md b/scripts/tests/README.md index fa3fa00..47b6ccd 100644 --- a/scripts/tests/README.md +++ b/scripts/tests/README.md @@ -21,7 +21,7 @@ The tests in this directory focus on end-to-end (E2E) validation, ensuring that To run these tests locally, ensure you have the following installed: - **Bash**: Most scripts use standard Bash features. - **Git**: Version 2.34+ is required for SSH signing tests. -- **Naj**: The `naj` binary must be available in your PATH or accessible via the `GOSH_CMD` environment variable. +- **Naj**: The `naj` binary must be available in your PATH or accessible via the `NAJ_CMD` environment variable. ## Running Tests @@ -46,7 +46,7 @@ Each script initializes a sandbox in `/tmp/naj_test_*` or similar, ensuring that ## Design Principles -1. **Isolation**: All tests use a dedicated `GOSH_CONFIG_PATH` and temporary directories to ensure side-effect-free execution. +1. **Isolation**: All tests use a dedicated `NAJ_CONFIG_PATH` and temporary directories to ensure side-effect-free execution. 2. **Assertions**: Scripts use exit codes and explicit checks to verify expected outcomes (e.g., checking `git cat-file` for signatures). 3. **Readability**: Log levels (STEP, INFO, ERROR) are used to provide clear feedback during execution. @@ -55,5 +55,5 @@ Each script initializes a sandbox in `/tmp/naj_test_*` or similar, ensuring that When adding a new test script: - Use `set -e` to ensure the script fails on the first error. - Use a dedicated sandbox directory for all temporary files. -- Export `GOSH_CONFIG_PATH` to point into your sandbox. +- Export `NAJ_CONFIG_PATH` to point into your sandbox. - Document the scenario and expected results in the script header. \ No newline at end of file diff --git a/scripts/tests/alice.sh b/scripts/tests/alice.sh index abe72e4..01bdcbb 100755 --- a/scripts/tests/alice.sh +++ b/scripts/tests/alice.sh @@ -2,11 +2,11 @@ set -e # --- 0. 环境与工具准备 --- -GOSH_CMD="naj" # 确保已编译或 alias 到 cargo run +NAJ_CMD="naj" # 确保已编译或 alias 到 cargo run BASE_DIR="/tmp/alice_demo_signed" # 隔离 Naj 配置 -export GOSH_CONFIG_PATH="$BASE_DIR/config" +export NAJ_CONFIG_PATH="$BASE_DIR/config" # 隔离 SSH 密钥目录 SSH_DIR="$BASE_DIR/ssh_keys" # 模拟仓库目录 @@ -29,7 +29,7 @@ info "Git Version: $GIT_VERSION (SSH Signing requires 2.34+)" # --- 1. 清理与沙盒初始化 --- log "Initializing Sandbox at $BASE_DIR..." rm -rf "$BASE_DIR" -mkdir -p "$GOSH_CONFIG_PATH" +mkdir -p "$NAJ_CONFIG_PATH" mkdir -p "$SSH_DIR" mkdir -p "$REPO_DIR" @@ -48,11 +48,11 @@ info "Generated Personal Key: $SSH_DIR/id_personal" log "Creating Naj Profiles..." # 3.1 创建基础 Work Profile -$GOSH_CMD -c "Alice Work" "alice@contoso.com" "work" +$NAJ_CMD -c "Alice Work" "alice@contoso.com" "work" # 3.2 手动追加 SSH 签名配置到 Work Profile # 这里演示了 Naj 的灵活性:你可以手动编辑生成的 .gitconfig -WORK_PROFILE="$GOSH_CONFIG_PATH/profiles/work.gitconfig" +WORK_PROFILE="$NAJ_CONFIG_PATH/profiles/work.gitconfig" cat >> "$WORK_PROFILE" <> "$PERSONAL_PROFILE" < Infer -> Switch) # 注意:这里我们 Clone 本地路径,但 core.sshCommand 依然会被配置进去,这是符合预期的 -$GOSH_CMD work clone "$REPO_DIR/backend.git" work-backend +$NAJ_CMD work clone "$REPO_DIR/backend.git" work-backend cd work-backend # 提交代码 @@ -123,7 +123,7 @@ git init --quiet "oss-project" cd oss-project # 切换到 Personal -$GOSH_CMD personal +$NAJ_CMD personal # 提交 touch fun.txt @@ -149,7 +149,7 @@ log "Scenario C: Ephemeral Execution (Security Check)" # 当前在 oss-project (Personal),我们想用 Work 身份签个名 # 执行 naj work commit -$GOSH_CMD work commit --allow-empty -m "Hotfix via Exec" > /dev/null +$NAJ_CMD work commit --allow-empty -m "Hotfix via Exec" > /dev/null # 验证最后一次提交的签名 # 注意:Exec 模式下,Naj 会通过 -c user.signingkey="" 先清空,再注入 work profile diff --git a/scripts/tests/alice2.sh b/scripts/tests/alice2.sh index 41a2dcd..d6eedd2 100755 --- a/scripts/tests/alice2.sh +++ b/scripts/tests/alice2.sh @@ -2,11 +2,11 @@ set -e # --- 0. 环境与工具准备 --- -GOSH_CMD="naj" # 确保已编译或 alias 到 cargo run +NAJ_CMD="naj" # 确保已编译或 alias 到 cargo run BASE_DIR="/tmp/alice_demo_debug" # 隔离 Naj 配置 -export GOSH_CONFIG_PATH="$BASE_DIR/config" +export NAJ_CONFIG_PATH="$BASE_DIR/config" # 隔离 SSH 密钥目录 SSH_DIR="$BASE_DIR/ssh_keys" # 模拟仓库目录 @@ -43,7 +43,7 @@ info "Git Version: $GIT_VERSION (SSH Signing requires 2.34+)" # --- 1. 清理与沙盒初始化 --- log "Initializing Sandbox at $BASE_DIR..." rm -rf "$BASE_DIR" -mkdir -p "$GOSH_CONFIG_PATH" +mkdir -p "$NAJ_CONFIG_PATH" mkdir -p "$SSH_DIR" mkdir -p "$REPO_DIR" @@ -58,8 +58,8 @@ info "Generated Personal Key: .../id_personal" log "Creating Naj Profiles..." # 3.1 Work Profile -$GOSH_CMD -c "Alice Work" "alice@contoso.com" "work" -WORK_PROFILE="$GOSH_CONFIG_PATH/profiles/work.gitconfig" +$NAJ_CMD -c "Alice Work" "alice@contoso.com" "work" +WORK_PROFILE="$NAJ_CONFIG_PATH/profiles/work.gitconfig" cat >> "$WORK_PROFILE" <> "$PERSONAL_PROFILE" < Alice Profile -$GOSH_CMD -c "Alice Work" "alice@contoso.com" "alice_work" -cat >> "$GOSH_CONFIG_PATH/profiles/alice_work.gitconfig" <> "$NAJ_CONFIG_PATH/profiles/alice_work.gitconfig" < Bob Profile -$GOSH_CMD -c "Bob Partner" "bob@partner.org" "bob_partner" -cat >> "$GOSH_CONFIG_PATH/profiles/bob_partner.gitconfig" <> "$NAJ_CONFIG_PATH/profiles/bob_partner.gitconfig" <>> [Commit 1] Alice starts the project" -$GOSH_CMD alice_work +$NAJ_CMD alice_work touch README.md git add README.md git commit -m "Init Project Alpha" > /dev/null @@ -129,14 +129,14 @@ verify_last_commit "alice@contoso.com" "alice_work" # 2. Bob 进来修改 (模拟同一台机器切换身份) echo ">>> [Commit 2] Bob adds features" -$GOSH_CMD bob_partner +$NAJ_CMD bob_partner echo "Feature by Bob" >> README.md git commit -am "Bob adds feature" > /dev/null verify_last_commit "bob@partner.org" "bob_partner" # 3. Alice 审查并修改 echo ">>> [Commit 3] Alice reviews and updates" -$GOSH_CMD alice_work +$NAJ_CMD alice_work echo "Reviewed by Alice" >> README.md git commit -am "Alice review" > /dev/null verify_last_commit "alice@contoso.com" "alice_work" @@ -149,7 +149,7 @@ git init --quiet project-beta cd project-beta # 1. Bob 拥有这个项目 -$GOSH_CMD bob_partner +$NAJ_CMD bob_partner touch main.rs git add main.rs git commit -m "Bob starts Beta" > /dev/null @@ -163,7 +163,7 @@ echo "// Hotfix" >> main.rs git add main.rs # 这里是关键测试:Exec 模式下的签名注入 -$GOSH_CMD alice_work commit -m "Alice hotfix" > /dev/null +$NAJ_CMD alice_work commit -m "Alice hotfix" > /dev/null # 验证:虽然此时 .git/config 指向 Bob,但这个 Commit 必须是 Alice 签名的 verify_last_commit "alice@contoso.com" "alice_work" diff --git a/scripts/tests/edge_cases.sh b/scripts/tests/edge_cases.sh index 7903f49..8c6b53a 100755 --- a/scripts/tests/edge_cases.sh +++ b/scripts/tests/edge_cases.sh @@ -2,9 +2,9 @@ set -e # --- 配置 --- -GOSH_CMD="naj" +NAJ_CMD="naj" BASE_DIR="/tmp/naj_edge_test" -export GOSH_CONFIG_PATH="$BASE_DIR/config" +export NAJ_CONFIG_PATH="$BASE_DIR/config" REPO_DIR="$BASE_DIR/repos" # 颜色 @@ -15,11 +15,11 @@ log() { echo -e "\n\033[0;34m[TEST] $1\033[0m"; } # --- 初始化 --- rm -rf "$BASE_DIR" -mkdir -p "$GOSH_CONFIG_PATH" "$REPO_DIR" +mkdir -p "$NAJ_CONFIG_PATH" "$REPO_DIR" # 创建一个 Profile log "Creating Profile..." -$GOSH_CMD -c "Edge User" "edge@test.com" "edge" +$NAJ_CMD -c "Edge User" "edge@test.com" "edge" # --- 测试 1: 子目录执行 --- log "Scenario 1: Running from a deep subdirectory" @@ -33,7 +33,7 @@ echo "Current dir: $(pwd)" echo "Executing 'naj edge' from subdirectory..." # 执行 switch -$GOSH_CMD edge +$NAJ_CMD edge # 验证 # 我们需要回到根目录看 config,或者直接用 git config @@ -57,7 +57,7 @@ git init --quiet echo "Current dir: $(pwd)" echo "Executing 'naj edge'..." -$GOSH_CMD edge +$NAJ_CMD edge # 验证 CONFIG_EMAIL=$(git config user.email) diff --git a/scripts/tests/security_edge.sh b/scripts/tests/security_edge.sh index 00b4a97..d3299fc 100755 --- a/scripts/tests/security_edge.sh +++ b/scripts/tests/security_edge.sh @@ -1,7 +1,7 @@ #!/bin/bash # --- 准备 --- -GOSH_CMD="naj" # 确保已编译或 alias +NAJ_CMD="naj" # 确保已编译或 alias BASE_DIR="/tmp/naj_security_test" UNSAFE_REPO="$BASE_DIR/root_owned_repo" @@ -21,7 +21,7 @@ echo "[TEST] Running 'naj' in a dubious ownership repo..." cd "$UNSAFE_REPO" # 2. 尝试运行 naj (期望失败) -if $GOSH_CMD -l > /dev/null 2>&1; then +if $NAJ_CMD -l > /dev/null 2>&1; then # 注意:naj -l 不需要 git 仓库,所以应该成功。 # 我们需要测 switch 或 exec,这需要 git 上下文 echo " (naj list works, which is fine)" @@ -29,7 +29,7 @@ fi echo "Attempting to switch profile..." # 捕获输出 -OUTPUT=$($GOSH_CMD testprofile 2>&1 || true) +OUTPUT=$($NAJ_CMD testprofile 2>&1 || true) # 3. 验证结果 if echo "$OUTPUT" | grep -q "fatal: detected dubious ownership"; then diff --git a/src/config.rs b/src/config.rs index 34af83c..abc4330 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,7 +29,7 @@ impl Default for NajConfig { } pub fn get_config_root() -> Result { - if let Ok(path) = std::env::var("GOSH_CONFIG_PATH") { + if let Ok(path) = std::env::var("NAJ_CONFIG_PATH") { return Ok(PathBuf::from(path)); } let config_dir = dirs::config_dir().ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?; @@ -54,8 +54,8 @@ fn initialize_config(root: &Path, config_path: &Path) -> Result { fs::create_dir_all(root).context("Failed to create config root")?; // Determine default profile_dir based on environment to support testing isolation - let profile_dir_str = if let Ok(env_path) = std::env::var("GOSH_CONFIG_PATH") { - // If GOSH_CONFIG_PATH is set, default profile dir should be inside it for isolation + let profile_dir_str = if let Ok(env_path) = std::env::var("NAJ_CONFIG_PATH") { + // If NAJ_CONFIG_PATH is set, default profile dir should be inside it for isolation let p = PathBuf::from(env_path).join("profiles"); // Use forward slashes for TOML consistency if possible, though PathBuf handles it. // On Windows, replace backslashes to avoid escape issues in TOML string if not raw diff --git a/src/git.rs b/src/git.rs index 35fb770..04d64a3 100644 --- a/src/git.rs +++ b/src/git.rs @@ -38,7 +38,7 @@ fn get_profile_path(config: &NajConfig, id: &str) -> Result { } fn is_mocking() -> bool { - std::env::var("GOSH_MOCKING").is_ok() + std::env::var("NAJ_MOCKING").is_ok() } fn run_command(cmd: &mut Command) -> Result<()> { diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 7dc8f88..5013c0a 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -7,9 +7,9 @@ fn test_config_initialization() -> Result<(), Box> { let temp_dir = TempDir::new()?; let config_path = temp_dir.path().join("config"); - // Run naj with GOSH_CONFIG_PATH set to temp dir + // Run naj with NAJ_CONFIG_PATH set to temp dir let mut cmd = Command::new(env!("CARGO_BIN_EXE_naj")); - cmd.env("GOSH_CONFIG_PATH", &config_path) + cmd.env("NAJ_CONFIG_PATH", &config_path) .arg("-l") // Trigger config load using list flag (not positional "list" profile) .assert() .success(); @@ -27,7 +27,7 @@ fn test_profile_creation_and_listing() -> Result<(), Box> let config_path = temp_dir.path(); let mut cmd = Command::new(env!("CARGO_BIN_EXE_naj")); - cmd.env("GOSH_CONFIG_PATH", config_path) + cmd.env("NAJ_CONFIG_PATH", config_path) .args(&["-c", "Test User", "test@example.com", "test_user"]) .assert() .success(); @@ -41,7 +41,7 @@ fn test_profile_creation_and_listing() -> Result<(), Box> // Verify list let mut cmd_list = Command::new(env!("CARGO_BIN_EXE_naj")); - cmd_list.env("GOSH_CONFIG_PATH", config_path) + cmd_list.env("NAJ_CONFIG_PATH", config_path) .arg("-l") .assert() .success() @@ -57,14 +57,14 @@ fn test_duplicate_creation_failure() -> Result<(), Box> { // Create first Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", config_path) + .env("NAJ_CONFIG_PATH", config_path) .args(&["-c", "User", "u@e.com", "dup_test"]) .assert() .success(); // Create duplicate Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", config_path) + .env("NAJ_CONFIG_PATH", config_path) .args(&["-c", "User2", "u2@e.com", "dup_test"]) .assert() .failure(); // Should fail @@ -80,7 +80,7 @@ fn test_remove_profile() -> Result<(), Box> { // Create Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", config_path) + .env("NAJ_CONFIG_PATH", config_path) .args(&["-c", "User", "u@e.com", "rem_test"]) .assert() .success(); @@ -88,7 +88,7 @@ fn test_remove_profile() -> Result<(), Box> { // Remove Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", config_path) + .env("NAJ_CONFIG_PATH", config_path) .args(&["-r", "rem_test"]) .assert() .success(); @@ -96,7 +96,7 @@ fn test_remove_profile() -> Result<(), Box> { // Remove non-existent Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", config_path) + .env("NAJ_CONFIG_PATH", config_path) .args(&["-r", "rem_test"]) .assert() .failure(); @@ -111,15 +111,15 @@ fn test_exec_dry_run_injection_strict() -> Result<(), Box // Create a profile first Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", config_path) + .env("NAJ_CONFIG_PATH", config_path) .args(&["-c", "Test", "test@e.com", "p1"]) .assert() .success(); // Run exec with mocking let mut cmd = Command::new(env!("CARGO_BIN_EXE_naj")); - cmd.env("GOSH_CONFIG_PATH", config_path) - .env("GOSH_MOCKING", "1") + cmd.env("NAJ_CONFIG_PATH", config_path) + .env("NAJ_MOCKING", "1") .args(&["p1", "commit", "-m", "foo"]) .assert() .success() @@ -151,14 +151,14 @@ fn test_switch_mode_persistent() -> Result<(), Box> { // Create profile Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", &config_path) + .env("NAJ_CONFIG_PATH", &config_path) .args(&["-c", "Switch User", "s@e.com", "switch_test"]) .assert() .success(); // Switch Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", &config_path) + .env("NAJ_CONFIG_PATH", &config_path) .current_dir(&repo_dir) .arg("switch_test") .assert() @@ -198,14 +198,14 @@ fn test_switch_force_mode_sanitization() -> Result<(), Box Result<(), Box> { // 2. Create Profile Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", &config_path) + .env("NAJ_CONFIG_PATH", &config_path) .args(&["-c", "CloneUser", "c@e.com", "clone_test"]) .assert() .success(); @@ -255,7 +255,7 @@ fn test_setup_mode_local_clone() -> Result<(), Box> { let dest_repo_name = "dest_repo"; Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", &config_path) + .env("NAJ_CONFIG_PATH", &config_path) .current_dir(temp_dir.path()) // Execute in temp root .args(&["clone_test", "clone", source_repo.to_str().unwrap(), dest_repo_name]) .assert() diff --git a/tests/test_force_mocking.rs b/tests/test_force_mocking.rs index adc8807..0932b11 100644 --- a/tests/test_force_mocking.rs +++ b/tests/test_force_mocking.rs @@ -16,15 +16,15 @@ fn test_switch_force_mocking() -> Result<(), Box> { // Create profile Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", &config_path) + .env("NAJ_CONFIG_PATH", &config_path) .args(&["-c", "User", "u@e.com", "mock_test"]) .assert() .success(); // Run force switch with mocking Command::new(env!("CARGO_BIN_EXE_naj")) - .env("GOSH_CONFIG_PATH", &config_path) - .env("GOSH_MOCKING", "1") + .env("NAJ_CONFIG_PATH", &config_path) + .env("NAJ_MOCKING", "1") .current_dir(&repo_dir) .args(&["mock_test", "-f"]) .assert()