refactor(detmir): retire python runtime paths

This commit is contained in:
igor04091968
2026-06-03 03:27:52 +03:00
parent 109c31f291
commit dbef90a09e
103 changed files with 914 additions and 19800 deletions
@@ -56,9 +56,6 @@ struct Cli {
#[arg(long, default_value = "/etc/systemd/system/aw-health-check.service")]
health_service_target: PathBuf,
#[arg(long, default_value = "/opt/activitywatch/aw-rus-ops/health-check.sh")]
health_script_source: PathBuf,
#[arg(long, default_value_t = false)]
apply: bool,
@@ -72,7 +69,6 @@ enum StepKind {
Check,
Chown,
Chmod,
Copy,
Write,
Systemd,
Sleep,
@@ -138,11 +134,10 @@ fn build_report(cli: &Cli) -> Report {
if !cli.env_file.is_file() {
missing_required.push(format!("env file missing: {}", cli.env_file.display()));
}
if !cli.health_script_target.is_file() && !cli.health_script_source.is_file() {
if !cli.health_script_target.is_file() {
missing_required.push(format!(
"health script target and source missing: {}, {}",
cli.health_script_target.display(),
cli.health_script_source.display()
"health script target missing: {}",
cli.health_script_target.display()
));
}
@@ -193,17 +188,13 @@ fn build_report(cli: &Cli) -> Report {
);
push_step(
&mut steps,
"install-health-script",
StepKind::Copy,
format!(
"copy {} {}",
shell_quote(&cli.health_script_source),
shell_quote(&cli.health_script_target)
),
true,
"check-health-script",
StepKind::Check,
format!("test -x {}", shell_quote(&cli.health_script_target)),
false,
!cli.health_script_target.is_file(),
if cli.health_script_target.is_file() {
"health script already installed".to_string()
"health script already installed by Ansible".to_string()
} else {
"health script target missing".to_string()
},
@@ -331,8 +322,16 @@ fn apply_steps(report: &mut Report) -> Result<()> {
1
}),
},
"check-health-script" => {
let target = Path::new("/usr/local/bin/aw-health-check");
ExecResult {
order: step.order,
name: step.name.clone(),
ok: target.is_file(),
exit_code: Some(if target.is_file() { 0 } else { 1 }),
}
}
"install-logrotate" => write_file_result(step, report, LOGROTATE_CONTENT, 0o644)?,
"install-health-script" => copy_health_script(step, report)?,
"install-health-timer" => write_file_result(step, report, HEALTH_TIMER_CONTENT, 0o644)?,
"install-health-service" => {
write_file_result(step, report, HEALTH_SERVICE_CONTENT, 0o644)?
@@ -371,24 +370,6 @@ fn write_file_result(step: &Step, report: &Report, content: &str, mode: u32) ->
})
}
fn copy_health_script(step: &Step, report: &Report) -> Result<ExecResult> {
let source = source_from_copy_command(&step.command)?;
let target = copy_target_from_command(&step.command)?;
let _ = report;
if let Some(parent) = target.parent() {
fs::create_dir_all(parent).with_context(|| format!("create {}", parent.display()))?;
}
fs::copy(&source, &target)
.with_context(|| format!("copy {} -> {}", source.display(), target.display()))?;
set_mode(&target, 0o755).with_context(|| format!("chmod 0755 {}", target.display()))?;
Ok(ExecResult {
order: step.order,
name: step.name.clone(),
ok: true,
exit_code: Some(0),
})
}
fn run_shell_step(step: &Step) -> Result<ExecResult> {
let status = Command::new("sh")
.arg("-c")
@@ -412,22 +393,6 @@ fn target_from_command(command: &str) -> Result<PathBuf> {
Ok(PathBuf::from(raw.trim_matches('\'')))
}
fn source_from_copy_command(command: &str) -> Result<PathBuf> {
let raw = command
.split_whitespace()
.nth(1)
.context("parse source from copy command")?;
Ok(PathBuf::from(raw.trim_matches('\'')))
}
fn copy_target_from_command(command: &str) -> Result<PathBuf> {
let raw = command
.split_whitespace()
.nth(2)
.context("parse target from copy command")?;
Ok(PathBuf::from(raw.trim_matches('\'')))
}
#[cfg(unix)]
fn set_mode(path: &Path, mode: u32) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
@@ -494,19 +459,19 @@ mod tests {
fn dry_run_marks_legacy_mutations() {
let dir = tempfile::tempdir().unwrap();
let env_file = dir.path().join("aw-server.env");
let health = dir.path().join("health-check.sh");
let health_target = dir.path().join("bin/aw-health-check");
fs::write(&env_file, "AW_BASE_URL=http://127.0.0.1:5600\n").unwrap();
fs::write(&health, "#!/bin/sh\nexit 0\n").unwrap();
fs::create_dir_all(health_target.parent().unwrap()).unwrap();
fs::write(&health_target, "#!/bin/sh\nexit 0\n").unwrap();
let cli = Cli {
env_file,
data_dir: dir.path().join("data"),
log_dir: dir.path().join("log"),
opt_dir: dir.path().join("opt"),
logrotate_target: dir.path().join("logrotate/activitywatch"),
health_script_target: dir.path().join("bin/aw-health-check"),
health_script_target: health_target,
health_timer_target: dir.path().join("systemd/aw-health-check.timer"),
health_service_target: dir.path().join("systemd/aw-health-check.service"),
health_script_source: health,
apply: false,
json: true,
};
@@ -525,18 +490,18 @@ mod tests {
#[test]
fn missing_env_blocks_apply() {
let dir = tempfile::tempdir().unwrap();
let health = dir.path().join("health-check.sh");
fs::write(&health, "#!/bin/sh\nexit 0\n").unwrap();
let health_target = dir.path().join("bin/aw-health-check");
fs::create_dir_all(health_target.parent().unwrap()).unwrap();
fs::write(&health_target, "#!/bin/sh\nexit 0\n").unwrap();
let cli = Cli {
env_file: dir.path().join("missing.env"),
data_dir: dir.path().join("data"),
log_dir: dir.path().join("log"),
opt_dir: dir.path().join("opt"),
logrotate_target: dir.path().join("logrotate/activitywatch"),
health_script_target: dir.path().join("bin/aw-health-check"),
health_script_target: health_target,
health_timer_target: dir.path().join("systemd/aw-health-check.timer"),
health_service_target: dir.path().join("systemd/aw-health-check.service"),
health_script_source: health,
apply: true,
json: true,
};
+1 -1
View File
@@ -20,7 +20,7 @@ use serde_json::{Value, json};
const ENV_FILE: &str = "/etc/activitywatch/aw-server.env";
const DEFAULT_STATE_DIR: &str = "/var/lib/activitywatch/slo";
const DEFAULT_HEALTHD_CMD: &str = "/usr/local/bin/aw-rus-healthd.py --json";
const DEFAULT_HEALTHD_CMD: &str = "/usr/local/bin/aw-rus-healthd-rust --json";
const DEFAULT_HEALTHD_STATE_FILE: &str = "/var/lib/activitywatch/health/aw-rus-health.json";
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
+25 -26
View File
@@ -13,7 +13,7 @@ const DEFAULT_SERVER_USER: &str = "igor";
const DEFAULT_LEGACY_DB: &str = "/root/.local/share/activitywatch/aw-server-rust/sqlite.db";
const DEFAULT_TARGET_DB: &str =
"/var/lib/activitywatch/.local/share/activitywatch/aw-server-rust/sqlite.db";
const DEFAULT_REMOTE_MERGE_SCRIPT: &str = "/tmp/merge_aw_server_dbs.py";
const DEFAULT_REMOTE_MERGE_BIN: &str = "/tmp/merge-aw-server-dbs";
#[derive(Debug, Parser)]
#[command(about = "Safe planner/checker for the destructive prod backup restore flow")]
@@ -45,8 +45,8 @@ struct Cli {
#[arg(long, default_value = DEFAULT_TARGET_DB)]
target_db: String,
#[arg(long, default_value = DEFAULT_REMOTE_MERGE_SCRIPT)]
remote_merge_script: String,
#[arg(long, default_value = DEFAULT_REMOTE_MERGE_BIN)]
remote_merge_bin: String,
#[arg(long)]
apply: bool,
@@ -63,7 +63,7 @@ struct Plan {
remote_backup_dir: String,
legacy_db: String,
target_db: String,
remote_merge_script: String,
remote_merge_bin: String,
required_env: Vec<Requirement>,
required_commands: Vec<Requirement>,
required_files: Vec<Requirement>,
@@ -101,7 +101,7 @@ fn run() -> Result<i32> {
let cli = Cli::parse();
if cli.apply {
bail!(
"Rust apply is intentionally disabled for this stage; use scripts/prod_backup_restore.sh --apply-legacy only for explicit legacy restore"
"Rust apply is intentionally disabled for this stage; run without --apply to review the safe restore plan"
);
}
let root = cli
@@ -128,7 +128,7 @@ fn run() -> Result<i32> {
} else {
root.join(&cli.inventory)
};
let merge_script = root.join("scripts/merge_aw_server_dbs.py");
let merge_bin = root.join("adk-rust/target/release/merge-aw-server-dbs");
let plan = build_plan(
&root,
&env_file,
@@ -139,9 +139,9 @@ fn run() -> Result<i32> {
&remote_backup_dir,
&cli.legacy_db,
&cli.target_db,
&cli.remote_merge_script,
&cli.remote_merge_bin,
&inventory,
&merge_script,
&merge_bin,
);
if cli.json {
@@ -168,9 +168,9 @@ fn build_plan(
remote_backup_dir: &str,
legacy_db: &str,
target_db: &str,
remote_merge_script: &str,
remote_merge_bin: &str,
inventory: &Path,
merge_script: &Path,
merge_bin: &Path,
) -> Plan {
let required_env = ["AW_SSH_PASSWORD", "AW_WINRM_PASSWORD"]
.into_iter()
@@ -199,24 +199,21 @@ fn build_plan(
},
})
.collect::<Vec<_>>();
let required_files = [
("inventory", inventory),
("merge_aw_server_dbs.py", merge_script),
]
.into_iter()
.map(|(name, path)| Requirement {
name: name.to_string(),
ok: path.is_file(),
detail: path.display().to_string(),
})
.collect::<Vec<_>>();
let required_files = [("inventory", inventory), ("merge-aw-server-dbs", merge_bin)]
.into_iter()
.map(|(name, path)| Requirement {
name: name.to_string(),
ok: path.is_file(),
detail: path.display().to_string(),
})
.collect::<Vec<_>>();
let mut steps = Vec::new();
push_step(
&mut steps,
"scp",
format!(
"sshpass scp scripts/merge_aw_server_dbs.py {server_user}@{server_host}:{remote_merge_script}"
"sshpass scp adk-rust/target/release/merge-aw-server-dbs {server_user}@{server_host}:{remote_merge_bin}"
),
false,
);
@@ -258,7 +255,7 @@ fn build_plan(
&mut steps,
"ssh",
format!(
"sudo python3 '{remote_merge_script}' --base '{legacy_db}' --overlay '{target_db}' --output '{remote_backup_dir}/sqlite.merged.db'"
"sudo chmod 0755 '{remote_merge_bin}' && sudo '{remote_merge_bin}' --base '{legacy_db}' --overlay '{target_db}' --output '{remote_backup_dir}/sqlite.merged.db'"
),
true,
);
@@ -307,7 +304,7 @@ fn build_plan(
remote_backup_dir: remote_backup_dir.to_string(),
legacy_db: legacy_db.to_string(),
target_db: target_db.to_string(),
remote_merge_script: remote_merge_script.to_string(),
remote_merge_bin: remote_merge_bin.to_string(),
required_env,
required_commands,
required_files,
@@ -443,7 +440,9 @@ mod tests {
fn plan_marks_destructive_steps() {
let dir = tempfile::tempdir().unwrap();
let inventory = dir.path().join("ansible/inventory.ini");
let merge = dir.path().join("scripts/merge_aw_server_dbs.py");
let merge = dir
.path()
.join("adk-rust/target/release/merge-aw-server-dbs");
fs::create_dir_all(inventory.parent().unwrap()).unwrap();
fs::create_dir_all(merge.parent().unwrap()).unwrap();
fs::write(&inventory, "").unwrap();
@@ -461,7 +460,7 @@ mod tests {
"/var/lib/activitywatch/backups/prod-restore-20260602-000000",
DEFAULT_LEGACY_DB,
DEFAULT_TARGET_DB,
DEFAULT_REMOTE_MERGE_SCRIPT,
DEFAULT_REMOTE_MERGE_BIN,
&inventory,
&merge,
);
@@ -47,23 +47,18 @@ const AW_SERVER_FILES: &[&str] = &[
"aw-server/apply_webui_ru_patch.sh",
"aw-server/aw-host-groups.json",
"aw-server/aw-ru-patch.js",
"aw-server/aw-rus-healthd.py",
"aw-server/aw-rus-healthd.service",
"aw-server/aw-rus-healthd.timer",
"aw-server/aw-browser-smoke.service",
"aw-server/aw-browser-smoke.timer",
"aw-server/aw-slo-monitor.py",
"aw-server/aw-slo-monitor.service",
"aw-server/aw-slo-monitor.timer",
"aw-server/aw-server.env.example",
"aw-server/aw-sw-cleanup.js",
"aw-server/aw-worktime-api.py",
"aw-server/aw-worktime-api.service",
"aw-server/aw-worktime-prewarm.sh",
"aw-server/aw-worktime-prewarm.service",
"aw-server/aw-worktime-prewarm.timer",
"aw-server/aw-worktime-panel.js",
"aw-server/health-check.sh",
"aw-server/install_aw_server.sh",
"aw-server/settings/classes-worktime.json",
"aw-server/settings/views-default.json",