fix: container installs, Tor, kiosk, GRUB, LUKS display, error messages
All checks were successful
Build Archipelago ISO (dev) / build-iso (push) Successful in 52m6s
Container Orchestration Tests / unit-tests (push) Successful in 29m20s
Container Orchestration Tests / smoke-tests (push) Successful in 6m18s

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:
Dorian
2026-03-30 16:35:06 +01:00
parent e65b039914
commit 031b3c34f4
9 changed files with 522 additions and 37 deletions

View File

@@ -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) {

View File

@@ -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),
_ => {}
}
}
}
}

View File

@@ -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,
}))
}

View File

@@ -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")?;