Setup quotes service paths and writes launcher and unit files atomically
This commit is contained in:
parent
7391e3a101
commit
dabb4113c2
35
src/setup.rs
35
src/setup.rs
|
|
@ -30,6 +30,33 @@ const DIM: Color = Color::DarkGrey;
|
|||
const SPIN: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
const UNIT: &str = "anybot.service";
|
||||
|
||||
/// Write a file all at once, as far as the filesystem allows: tmp file + rename. A Ctrl+C in
|
||||
/// the middle of installing used to leave a half-written launcher/unit behind, and systemd
|
||||
/// then ran (or refused to run) a broken service with no easy way to see why.
|
||||
fn write_atomic(path: &std::path::Path, body: String) -> std::io::Result<()> {
|
||||
let tmp = path.with_extension("anybot-tmp");
|
||||
std::fs::write(&tmp, body)?;
|
||||
std::fs::rename(&tmp, path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Quote a path for a systemd ExecStart line: a home folder with spaces (or a quote sign)
|
||||
/// made the unit fail to start with no way to see why.
|
||||
fn systemd_quote(p: &std::path::Path) -> String {
|
||||
let mut out = String::from("\"");
|
||||
for c in p.display().to_string().chars() {
|
||||
match c {
|
||||
'"' | '$' | '\\' | '`' => {
|
||||
out.push('\\');
|
||||
out.push(c);
|
||||
}
|
||||
c => out.push(c),
|
||||
}
|
||||
}
|
||||
out.push('"');
|
||||
out
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
enum Role {
|
||||
Hands,
|
||||
|
|
@ -766,7 +793,7 @@ impl Plan {
|
|||
let bin = home.join(".local/bin");
|
||||
std::fs::create_dir_all(&bin).map_err(|e| format!("建立 {} 失敗:{e}", bin.display()))?;
|
||||
let launcher = bin.join("anybot-run");
|
||||
std::fs::write(&launcher, launcher_script(self.role, &exe, &self.env)).map_err(|e| format!("寫入 {} 失敗:{e}", launcher.display()))?;
|
||||
write_atomic(&launcher, launcher_script(self.role, &exe, &self.env)).map_err(|e| format!("寫入 {} 失敗:{e}", launcher.display()))?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
|
@ -785,9 +812,9 @@ impl Plan {
|
|||
};
|
||||
let unit = format!(
|
||||
"[Unit]\nDescription=AnyBot({what})\nAfter=network-online.target graphical-session.target\nWants=network-online.target\n\n[Service]\nExecStart={}\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=default.target\n",
|
||||
launcher.display()
|
||||
systemd_quote(&launcher)
|
||||
);
|
||||
std::fs::write(units.join(UNIT), unit).map_err(|e| e.to_string())?;
|
||||
write_atomic(&units.join(UNIT), unit).map_err(|e| e.to_string())?;
|
||||
systemctl(&["daemon-reload"])?;
|
||||
systemctl(&["enable", UNIT])?;
|
||||
systemctl(&["restart", UNIT])?;
|
||||
|
|
@ -815,7 +842,7 @@ fn refresh_service() -> i32 {
|
|||
return 1;
|
||||
}
|
||||
};
|
||||
if let Err(e) = std::fs::write(&launcher, launcher_script(role, &exe, &env)) {
|
||||
if let Err(e) = write_atomic(&launcher, launcher_script(role, &exe, &env)) {
|
||||
eprintln!("✗ 寫入 {} 失敗:{e}", launcher.display());
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue