UI changes to update and support auto updating (#6075)

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
Anthony Stirling
2026-06-02 23:11:37 +01:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 919f0ade99
commit 256d1a86d2
31 changed files with 3602 additions and 488 deletions
@@ -6,14 +6,37 @@ use std::path::PathBuf;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ProvisioningConfig<'a> {
server_url: &'a str,
lock_connection_mode: bool,
#[serde(skip_serializing_if = "Option::is_none")]
server_url: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
lock_connection_mode: Option<bool>,
/// Optional headless-install update policy.
/// One of `"prompt"` (default), `"auto"`, or `"disabled"`.
#[serde(skip_serializing_if = "Option::is_none")]
update_mode: Option<&'a str>,
}
fn parse_bool(value: &str) -> bool {
matches!(
value.trim().to_lowercase().as_str(),
"1" | "true" | "yes" | "y"
)
}
/// Normalise the `--update-mode` argument into the lowercase tokens the app
/// understands. Empty / whitespace values are treated as "not supplied" so
/// MSI installs that don't pass STIRLING_UPDATE_MODE behave identically to
/// earlier builds.
fn parse_update_mode(value: &str) -> Result<Option<&'static str>, String> {
match value.trim().to_lowercase().as_str() {
"1" | "true" | "yes" | "y" => true,
_ => false,
"" => Ok(None),
"prompt" => Ok(Some("prompt")),
"auto" => Ok(Some("auto")),
"disabled" | "off" | "none" => Ok(Some("disabled")),
other => Err(format!(
"Invalid --update-mode value '{}': expected prompt, auto, or disabled",
other
)),
}
}
@@ -21,6 +44,7 @@ fn main() -> Result<(), String> {
let mut output: Option<PathBuf> = None;
let mut url: Option<String> = None;
let mut lock_value: Option<String> = None;
let mut update_mode_arg: Option<String> = None;
let mut args = env::args().skip(1);
while let Some(arg) = args.next() {
@@ -43,6 +67,12 @@ fn main() -> Result<(), String> {
.ok_or_else(|| "--lock requires a value".to_string())?;
lock_value = Some(value);
}
"--update-mode" => {
let value = args
.next()
.ok_or_else(|| "--update-mode requires a value".to_string())?;
update_mode_arg = Some(value);
}
_ => {
return Err(format!("Unknown argument: {}", arg));
}
@@ -50,11 +80,28 @@ fn main() -> Result<(), String> {
}
let output = output.ok_or_else(|| "Missing --output".to_string())?;
let url = url
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
.ok_or_else(|| "Missing --url".to_string())?;
let lock = lock_value.as_deref().map(parse_bool).unwrap_or(false);
.filter(|value| !value.is_empty());
let update_mode = update_mode_arg
.as_deref()
.map(parse_update_mode)
.transpose()?
.flatten();
// Nothing to write — avoid clobbering an existing provisioning file when
// the MSI is invoked without any of STIRLING_SERVER_URL / STIRLING_UPDATE_MODE.
if url.is_none() && update_mode.is_none() {
return Ok(());
}
let lock = if url.is_some() {
Some(lock_value.as_deref().map(parse_bool).unwrap_or(false))
} else {
None
};
if let Some(parent) = output.parent() {
fs::create_dir_all(parent)
@@ -62,8 +109,9 @@ fn main() -> Result<(), String> {
}
let config = ProvisioningConfig {
server_url: url.as_str(),
server_url: url.as_deref(),
lock_connection_mode: lock,
update_mode,
};
let json = serde_json::to_string_pretty(&config)