fix: quote quadlet environment values
This commit is contained in:
@@ -222,7 +222,7 @@ impl QuadletUnit {
|
||||
for env in &self.environment {
|
||||
// env entries already arrive shaped as "KEY=VALUE"; quadlet
|
||||
// accepts that form on a single Environment= line per pair.
|
||||
let _ = writeln!(s, "Environment={env}");
|
||||
let _ = writeln!(s, "Environment={}", quote_environment(env));
|
||||
}
|
||||
for dev in &self.devices {
|
||||
let _ = writeln!(s, "AddDevice={dev}");
|
||||
@@ -296,6 +296,19 @@ fn shell_join(parts: &[String]) -> String {
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
fn quote_environment(env: &str) -> String {
|
||||
let env = env.replace(['\r', '\n'], " ");
|
||||
if env.is_empty() || env.chars().any(|c| c.is_whitespace() || "\"\\$`".contains(c)) {
|
||||
let escaped = env
|
||||
.replace('\\', "\\\\")
|
||||
.replace('"', "\\\"")
|
||||
.replace('$', "$$");
|
||||
format!("\"{escaped}\"")
|
||||
} else {
|
||||
env
|
||||
}
|
||||
}
|
||||
|
||||
impl QuadletUnit {
|
||||
/// Build a backend-flavour QuadletUnit from a parsed AppManifest.
|
||||
/// Wired through `prod_orchestrator::install_via_quadlet`, gated by
|
||||
@@ -778,6 +791,19 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quote_environment_quotes_values_with_spaces() {
|
||||
assert_eq!(quote_environment("BITCOIN_RPC_PASS=secret"), "BITCOIN_RPC_PASS=secret");
|
||||
assert_eq!(
|
||||
quote_environment("RELAY_NAME=Archipelago Nostr Relay"),
|
||||
"\"RELAY_NAME=Archipelago Nostr Relay\""
|
||||
);
|
||||
assert_eq!(
|
||||
quote_environment("GREETING=say \"hi\""),
|
||||
"\"GREETING=say \\\"hi\\\"\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restart_policy_emits_correct_systemd_string() {
|
||||
assert_eq!(RestartPolicy::Always.as_systemd(), "always");
|
||||
@@ -797,6 +823,7 @@ mod tests {
|
||||
environment: vec![
|
||||
"BITCOIN_RPC_USER=archipelago".into(),
|
||||
"BITCOIN_RPC_PASS=secret".into(),
|
||||
"RELAY_NAME=Archipelago Nostr Relay".into(),
|
||||
],
|
||||
devices: vec!["/dev/kvm".into()],
|
||||
add_hosts: vec![("host.archipelago".into(), "10.89.0.1".into())],
|
||||
@@ -813,6 +840,7 @@ mod tests {
|
||||
assert!(s.contains("PublishPort=8333:8333/tcp"));
|
||||
assert!(s.contains("Environment=BITCOIN_RPC_USER=archipelago"));
|
||||
assert!(s.contains("Environment=BITCOIN_RPC_PASS=secret"));
|
||||
assert!(s.contains("Environment=\"RELAY_NAME=Archipelago Nostr Relay\""));
|
||||
assert!(s.contains("AddDevice=/dev/kvm"));
|
||||
assert!(s.contains("AddHost=host.archipelago:10.89.0.1"));
|
||||
assert!(s.contains("ReadOnly=true"));
|
||||
|
||||
@@ -160,7 +160,7 @@ export function getStatusLabel(state: PackageState, health?: string | null, exit
|
||||
if (state === PackageState.Running && health === 'unhealthy') return 'unhealthy'
|
||||
if (state === PackageState.Running && health === 'healthy') return 'healthy'
|
||||
if (state === PackageState.Exited) {
|
||||
if (exitCode === 137) return 'killed (OOM)'
|
||||
if (exitCode === 137) return 'killed (SIGKILL)'
|
||||
if (exitCode != null && exitCode !== 0) return 'crashed'
|
||||
return 'stopped'
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ export function getStatusLabel(state: PackageState, health?: string | null, exit
|
||||
if (state === PackageState.Updating) return 'updating...'
|
||||
if (state === PackageState.Running) return 'running'
|
||||
if (state === PackageState.Exited || state === PackageState.Stopped) {
|
||||
if (exitCode === 137) return 'killed (OOM)'
|
||||
if (exitCode === 137) return 'killed (SIGKILL)'
|
||||
if (exitCode != null && exitCode !== 0) return 'crashed'
|
||||
return 'stopped'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user