diff --git a/core/archipelago/src/api/rpc/package/install.rs b/core/archipelago/src/api/rpc/package/install.rs index d10ad526..825eabc4 100644 --- a/core/archipelago/src/api/rpc/package/install.rs +++ b/core/archipelago/src/api/rpc/package/install.rs @@ -431,11 +431,29 @@ impl RpcHandler { } /// Create data directories for volume mounts under /var/lib/archipelago/. + /// Get the mapped host UID for a container's internal UID. + /// Rootless podman maps container UIDs: host_uid = subuid_start + container_uid + /// Default subuid start for archipelago user is 100000. + fn mapped_uid(package_id: &str) -> u32 { + let container_uid = match package_id { + "bitcoin-knots" | "bitcoin" => 101, + "grafana" => 472, + "lnd" => 1000, + "mariadb" | "mysql" => 999, + "postgres" | "btcpay-postgres" | "immich-postgres" | "penpot-postgres" => 70, + _ => 0, // Most containers run as root (UID 0) + }; + 100000 + container_uid + } + async fn create_data_dirs(&self, package_id: &str, volumes: &[String]) { + let uid = Self::mapped_uid(package_id); + let uid_str = format!("{}:{}", uid, uid); + for volume in volumes { if let Some(host_path) = volume.split(':').next() { if host_path.starts_with("/var/lib/archipelago/") { - debug!("Creating directory: {}", host_path); + debug!("Creating directory: {} (owner: {})", host_path, uid_str); let create_dir = tokio::process::Command::new("sudo") .args(["mkdir", "-p", host_path]) .output() @@ -443,13 +461,11 @@ impl RpcHandler { if let Err(e) = create_dir { debug!("Failed to create directory {}: {}", host_path, e); } - // Grafana runs as UID 472 — fix permissions - if package_id == "grafana" && host_path.contains("grafana") { - let _ = tokio::process::Command::new("sudo") - .args(["chown", "-R", "472:472", host_path]) - .output() - .await; - } + // Set ownership to the mapped UID for rootless podman + let _ = tokio::process::Command::new("sudo") + .args(["chown", "-R", &uid_str, host_path]) + .output() + .await; } } }