feat: schema version tracking, SHA256 integrity, setup scripts, bug fixes
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use anyhow::{Context, Result};
|
||||
use libc;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
use std::time::Duration;
|
||||
@@ -45,6 +46,41 @@ impl RetryConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load SHA256 checksums from checksums.sha256 manifest.
|
||||
/// Returns a map of relative path → expected hash.
|
||||
fn load_checksums(scripts_dir: &PathBuf) -> HashMap<String, String> {
|
||||
let path = scripts_dir.join("checksums.sha256");
|
||||
let mut map = HashMap::new();
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(content) => {
|
||||
for line in content.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Some((hash, rel_path)) = line.split_once(' ') {
|
||||
let hash = hash.trim().to_string();
|
||||
let rel_path = rel_path.trim().to_string();
|
||||
if !hash.is_empty() && !rel_path.is_empty() {
|
||||
map.insert(rel_path, hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
tracing::info!(
|
||||
"[CHECKSUMS] Loaded {} entries from checksums.sha256",
|
||||
map.len()
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
"[CHECKSUMS] Could not load checksums.sha256: {} — integrity checks disabled",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
pub fn validate_python_env() -> Result<()> {
|
||||
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let venv_python = manifest.join("venv").join("bin").join("python");
|
||||
@@ -85,6 +121,7 @@ pub fn validate_python_env() -> Result<()> {
|
||||
pub struct PythonExecutor {
|
||||
venv_python: PathBuf,
|
||||
scripts_dir: PathBuf,
|
||||
checksums: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl PythonExecutor {
|
||||
@@ -104,12 +141,47 @@ impl PythonExecutor {
|
||||
anyhow::bail!("Scripts directory not found at {:?}", scripts_dir);
|
||||
}
|
||||
|
||||
// Load SHA256 checksums manifest
|
||||
let checksums = load_checksums(&scripts_dir);
|
||||
|
||||
Ok(Self {
|
||||
venv_python,
|
||||
scripts_dir,
|
||||
checksums,
|
||||
})
|
||||
}
|
||||
|
||||
/// Verify a script's SHA256 against the checksums manifest before execution.
|
||||
fn verify_script_integrity(&self, script_name: &str) -> Result<()> {
|
||||
let script_path = self.scripts_dir.join(script_name);
|
||||
let rel_path = format!("./{}", script_name);
|
||||
|
||||
if let Some(expected_hash) = self.checksums.get(&rel_path) {
|
||||
let output = std::process::Command::new("shasum")
|
||||
.arg("-a").arg("256")
|
||||
.arg(&script_path)
|
||||
.output()
|
||||
.context("Failed to run shasum for integrity check")?;
|
||||
|
||||
let actual = String::from_utf8_lossy(&output.stdout);
|
||||
let actual_hash = actual.split(' ').next().unwrap_or("").trim().to_string();
|
||||
|
||||
if actual_hash.is_empty() || actual_hash != *expected_hash {
|
||||
anyhow::bail!(
|
||||
"Script integrity check FAILED for {}: hash mismatch\n Expected: {}\n Actual: {}\n Run: bash scripts/setup/check_momentry.sh",
|
||||
script_name, expected_hash, actual_hash
|
||||
);
|
||||
}
|
||||
tracing::debug!("[INTEGRITY] {} checksum OK", script_name);
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"[INTEGRITY] {} not in checksums.sha256 manifest — skipping verification",
|
||||
script_name
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_env(&self) -> Result<()> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
let output = rt
|
||||
@@ -147,6 +219,10 @@ impl PythonExecutor {
|
||||
anyhow::bail!("Script not found: {:?}", script_path);
|
||||
}
|
||||
|
||||
// Verify script integrity via SHA256 checksum before execution
|
||||
self.verify_script_integrity(script_name)
|
||||
.context("Pre-execution integrity check failed — possible version mismatch or corruption")?;
|
||||
|
||||
// 標記輸出檔為處理中(add .tmp suffix)
|
||||
let output_path = args.get(1).map(|p| std::path::PathBuf::from(p));
|
||||
let tmp_path = output_path.as_ref().map(|p| {
|
||||
|
||||
Reference in New Issue
Block a user