setup --refresh-service: keep the launcher executable

systemd starts ~/.local/bin/anybot-run directly. install_systemd set 0755,
the refresh path rewrote the file without it, and the unit then died with
status=203/EXEC in a restart loop (seen on a Ubuntu hands box: service
enabled, binary fresh, nothing running). One make_executable for both.
This commit is contained in:
王性驊 2026-11-10 14:46:30 +08:00
parent 983a2c2f02
commit 7edc05ba9b
1 changed files with 15 additions and 5 deletions

View File

@ -42,6 +42,17 @@ fn write_atomic(path: &std::path::Path, body: String) -> std::io::Result<()> {
/// Quote a path for a systemd ExecStart line: a home folder with spaces (or a quote sign) /// 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. /// made the unit fail to start with no way to see why.
/// The launcher is started by systemd (`ExecStart=`) and by the terminal, so it must be executable.
fn make_executable(path: &std::path::Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o755));
}
#[cfg(not(unix))]
let _ = path;
}
fn systemd_quote(p: &std::path::Path) -> String { fn systemd_quote(p: &std::path::Path) -> String {
let mut out = String::from("\""); let mut out = String::from("\"");
for c in p.display().to_string().chars() { for c in p.display().to_string().chars() {
@ -826,11 +837,7 @@ impl Plan {
std::fs::create_dir_all(&bin).map_err(|e| format!("建立 {} 失敗:{e}", bin.display()))?; std::fs::create_dir_all(&bin).map_err(|e| format!("建立 {} 失敗:{e}", bin.display()))?;
let launcher = bin.join("anybot-run"); let launcher = bin.join("anybot-run");
write_atomic(&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)] make_executable(&launcher);
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(&launcher, std::fs::Permissions::from_mode(0o755));
}
let units = home.join(".config/systemd/user"); let units = home.join(".config/systemd/user");
std::fs::create_dir_all(&units).map_err(|e| e.to_string())?; std::fs::create_dir_all(&units).map_err(|e| e.to_string())?;
// An older setup (deploy/linux/hands.sh) used another service name. // An older setup (deploy/linux/hands.sh) used another service name.
@ -1004,6 +1011,9 @@ fn refresh_service() -> i32 {
eprintln!("✗ 寫入 {} 失敗:{e}", launcher.display()); eprintln!("✗ 寫入 {} 失敗:{e}", launcher.display());
return 1; return 1;
} }
// systemd starts it directly: a launcher written without the execute bit fails the unit
// with status=203/EXEC and the service spins in a restart loop.
make_executable(&launcher);
match systemctl(&["restart", UNIT]) { match systemctl(&["restart", UNIT]) {
Ok(_) => { Ok(_) => {
println!("已更新並重新啟動 AnyBot 服務。"); println!("已更新並重新啟動 AnyBot 服務。");