fix: container installs, Tor, kiosk, GRUB, LUKS display, error messages
Critical: - fix: container installs fail with "statfs: no such file or directory" Root cause: NoNewPrivileges=yes in systemd blocks sudo inside backend. Fix: use std::fs::create_dir_all + podman unshare chown (no sudo needed) - fix: Tor services.json never written — \$ARCHY_TOR_DIR escaping bug - fix: kiosk white screen — increase health wait to 60s, add --disable-gpu Improvements: - feat: LUKS encryption badge in Server disk stats (backend detects dm-crypt) - fix: GRUB theme text scaling on 4:3 monitors — explicit fonts, wider menu - fix: suppress default Debian MOTD (custom profile.d welcome is enough) - fix: install error messages now show "Failed to pull/start" instead of generic "Operation failed" (middleware.rs allowlist expanded) - fix: container-tests CI — source cargo env before running tests - docs: interactive container architecture diagram (HTML) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,10 @@ pub(super) fn sanitize_error_message(msg: &str) -> String {
|
||||
"cannot",
|
||||
"Password",
|
||||
"Session",
|
||||
"Failed to pull",
|
||||
"Failed to start",
|
||||
"Container",
|
||||
"Image",
|
||||
];
|
||||
for prefix in &user_facing_prefixes {
|
||||
if msg.starts_with(prefix) {
|
||||
|
||||
@@ -472,18 +472,31 @@ impl RpcHandler {
|
||||
if let Some(host_path) = volume.split(':').next() {
|
||||
if host_path.starts_with("/var/lib/archipelago/") {
|
||||
debug!("Creating directory: {} (owner: {})", host_path, uid_str);
|
||||
let create_dir = tokio::process::Command::new("sudo")
|
||||
.args(["mkdir", "-p", host_path])
|
||||
.output()
|
||||
.await;
|
||||
if let Err(e) = create_dir {
|
||||
debug!("Failed to create directory {}: {}", host_path, e);
|
||||
|
||||
// Create directory directly (service has ReadWritePaths access).
|
||||
// sudo is blocked by NoNewPrivileges=yes in the systemd service.
|
||||
if let Err(e) = std::fs::create_dir_all(host_path) {
|
||||
tracing::warn!("Failed to create directory {}: {}", host_path, e);
|
||||
}
|
||||
// Set ownership to the mapped UID for rootless podman
|
||||
let _ = tokio::process::Command::new("sudo")
|
||||
.args(["chown", "-R", &uid_str, host_path])
|
||||
|
||||
// Set ownership to the mapped UID for rootless podman.
|
||||
// This needs elevated privileges — use podman unshare to run
|
||||
// chown inside the user namespace where UIDs are mapped.
|
||||
let chown_result = tokio::process::Command::new("podman")
|
||||
.args(["unshare", "chown", "-R", &uid_str, host_path])
|
||||
.output()
|
||||
.await;
|
||||
match chown_result {
|
||||
Ok(out) if !out.status.success() => {
|
||||
tracing::warn!(
|
||||
"podman unshare chown failed for {}: {}",
|
||||
host_path,
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
}
|
||||
Err(e) => tracing::warn!("Failed to chown {}: {}", host_path, e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,11 @@ impl RpcHandler {
|
||||
|
||||
/// system.disk-status — Disk usage with warning/critical thresholds.
|
||||
pub(in crate::api::rpc) async fn handle_system_disk_status(&self) -> Result<serde_json::Value> {
|
||||
let (used, total) = read_disk_usage().await.unwrap_or((0, 0));
|
||||
// Prefer the encrypted data partition if it exists
|
||||
let data_path = std::path::Path::new("/var/lib/archipelago");
|
||||
let df_target = if data_path.exists() { "/var/lib/archipelago" } else { "/" };
|
||||
|
||||
let (used, total) = read_disk_usage_path(df_target).await.unwrap_or((0, 0));
|
||||
let percent = if total > 0 {
|
||||
(used as f64 / total as f64) * 100.0
|
||||
} else {
|
||||
@@ -114,12 +118,19 @@ impl RpcHandler {
|
||||
"ok"
|
||||
};
|
||||
|
||||
// Detect LUKS encryption (device name varies by install)
|
||||
let encrypted = std::path::Path::new("/dev/mapper/archipelago_crypt").exists()
|
||||
|| std::path::Path::new("/dev/mapper/archipelago-data").exists()
|
||||
|| std::path::Path::new("/dev/mapper/archipelago_data").exists();
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"used_bytes": used,
|
||||
"total_bytes": total,
|
||||
"free_bytes": total.saturating_sub(used),
|
||||
"used_percent": percent_rounded,
|
||||
"level": level,
|
||||
"encrypted": encrypted,
|
||||
"partition": df_target,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -157,8 +157,13 @@ pub(super) fn parse_meminfo_kb(val: &str) -> Result<u64> {
|
||||
/// Read disk usage via `df` for the root filesystem.
|
||||
/// Returns (used_bytes, total_bytes).
|
||||
pub(super) async fn read_disk_usage() -> Result<(u64, u64)> {
|
||||
read_disk_usage_path("/").await
|
||||
}
|
||||
|
||||
/// Read disk usage via `df` for a given path.
|
||||
pub(super) async fn read_disk_usage_path(path: &str) -> Result<(u64, u64)> {
|
||||
let output = tokio::process::Command::new("df")
|
||||
.args(["--block-size=1", "--output=used,size", "/"])
|
||||
.args(["--block-size=1", "--output=used,size", path])
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to run df")?;
|
||||
|
||||
Reference in New Issue
Block a user