| 910 |
910 |
|
let sha = crate::domain::GitSha::parse(&body.sha)
|
| 911 |
911 |
|
.map_err(|e| crate::error::Error::BadRequest(format!("invalid sha: {e}")))?;
|
| 912 |
912 |
|
|
| 913 |
|
- |
// Don't restart the controller out from under an in-flight server build —
|
| 914 |
|
- |
// the restart would SIGKILL it mid-deploy. Make the operator retry once idle.
|
|
913 |
+ |
let unit = self_update_unit(&sha);
|
|
914 |
+ |
|
|
915 |
+ |
// Don't restart the controller out from under an in-flight server build — the
|
|
916 |
+ |
// restart would SIGKILL it mid-deploy. Hold the active_build slot across the
|
|
917 |
+ |
// trigger, not just the check: releasing it before triggering let a /rebuild
|
|
918 |
+ |
// claim the slot in the gap and start a build the updater then killed (the
|
|
919 |
+ |
// Run 2 TOCTOU). With the guard held, no build can start until the updater is
|
|
920 |
+ |
// enqueued.
|
| 915 |
921 |
|
{
|
| 916 |
922 |
|
let slot = s.active_build.lock().await;
|
| 917 |
923 |
|
if slot.as_ref().is_some_and(|b| !b.handle.is_finished()) {
|
| 919 |
925 |
|
"a server build is in flight; retry /self-update once it settles".into(),
|
| 920 |
926 |
|
));
|
| 921 |
927 |
|
}
|
| 922 |
|
- |
}
|
| 923 |
928 |
|
|
| 924 |
|
- |
let unit = self_update_unit(&sha);
|
| 925 |
|
- |
tracing::warn!(sha = %sha, unit = %unit, "self-update requested; triggering privileged updater");
|
| 926 |
|
- |
// `--no-block`: return as soon as the job is enqueued. The build+restart
|
| 927 |
|
- |
// outcome lands in `journalctl -u <unit>`; sandod is restarted out from
|
| 928 |
|
- |
// under this request, so there is nothing more to await here.
|
| 929 |
|
- |
let status = tokio::process::Command::new("systemctl")
|
| 930 |
|
- |
.args(["start", "--no-block", &unit])
|
| 931 |
|
- |
.status()
|
| 932 |
|
- |
.await
|
| 933 |
|
- |
.map_err(|e| crate::error::Error::Other(anyhow::anyhow!("spawning systemctl: {e}")))?;
|
| 934 |
|
- |
if !status.success() {
|
| 935 |
|
- |
return Err(crate::error::Error::Other(anyhow::anyhow!(
|
| 936 |
|
- |
"systemctl start {unit} exited {status}; is sando-update@.service installed and the sando-user polkit rule in place?"
|
| 937 |
|
- |
)));
|
|
929 |
+ |
tracing::warn!(sha = %sha, unit = %unit, "self-update requested; triggering privileged updater");
|
|
930 |
+ |
// `--no-block`: return as soon as the job is enqueued. The build+restart
|
|
931 |
+ |
// outcome lands in `journalctl -u <unit>`; sandod is restarted out from
|
|
932 |
+ |
// under this request, so there is nothing more to await here.
|
|
933 |
+ |
let status = tokio::process::Command::new("systemctl")
|
|
934 |
+ |
.args(["start", "--no-block", &unit])
|
|
935 |
+ |
.status()
|
|
936 |
+ |
.await
|
|
937 |
+ |
.map_err(|e| crate::error::Error::Other(anyhow::anyhow!("spawning systemctl: {e}")))?;
|
|
938 |
+ |
if !status.success() {
|
|
939 |
+ |
return Err(crate::error::Error::Other(anyhow::anyhow!(
|
|
940 |
+ |
"systemctl start {unit} exited {status}; is sando-update@.service installed and the sando-user polkit rule in place?"
|
|
941 |
+ |
)));
|
|
942 |
+ |
}
|
| 938 |
943 |
|
}
|
| 939 |
944 |
|
Ok(Json(serde_json::json!({ "accepted": true, "sha": sha.to_string(), "unit": unit })))
|
| 940 |
945 |
|
}
|