From 20289c5bec1c6bfc8bc96504ef507159c19ec258 Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 29 Mar 2026 14:48:37 +0100 Subject: [PATCH] fix: rootless podman UID mapping for container data dirs create_data_dirs now chowns data directories to the correct mapped UID for rootless podman (host_uid = 100000 + container_uid). Previously only Grafana (UID 472) was handled. Now all containers get the correct ownership: - Bitcoin Knots: 100101 (container UID 101) - Grafana: 100472 (UID 472) - LND: 101000 (UID 1000) - MariaDB: 100999 (UID 999) - Postgres: 100070 (UID 70) - All others: 100000 (UID 0, root) Without this, containers fail with "Operation not permitted" on chown during startup because rootless podman restricts UID operations. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/api/rpc/package/install.rs | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) 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; } } }