fix(orchestrator): map container uids into the subuid range in the chown fallback
chown_for_rootless_container prefers `podman unshare chown` (which maps container uid N through the userns), but when that failed once it fell back to `sudo chown -R <literal>` — writing e.g. host uid 999 for container uid 999 and reporting success. Host-999 maps to nobody inside the userns, so the app could not open its own data while everything claimed the chown worked: botfights on framework-pt crash-looped every 10s on SQLITE_CANTOPEN over a data dir the daemon itself had just "fixed". The sudo fallback now translates container ids (1..99999) to subuid_base + id - 1 (fleet base 100000; container root maps to the service user, 1000). Already-mapped ids and uid 0 pass through. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
8469af5f4e
commit
d8748ee7ba
@@ -272,10 +272,15 @@ fn build_fingerprint_stamp_path(data_dir: &Path, tag: &str) -> PathBuf {
|
||||
}
|
||||
|
||||
async fn chown_for_rootless_container(uid_gid: &str, path: &str) -> Result<()> {
|
||||
let uid = uid_gid
|
||||
let (uid, gid) = uid_gid
|
||||
.split_once(':')
|
||||
.and_then(|(uid, _)| uid.parse::<u32>().ok())
|
||||
.unwrap_or(0);
|
||||
.map(|(u, g)| {
|
||||
(
|
||||
u.parse::<u32>().unwrap_or(0),
|
||||
g.parse::<u32>().unwrap_or(0),
|
||||
)
|
||||
})
|
||||
.unwrap_or((0, 0));
|
||||
|
||||
if uid > 0 && uid < 100_000 {
|
||||
let output = tokio::process::Command::new("podman")
|
||||
@@ -288,9 +293,22 @@ async fn chown_for_rootless_container(uid_gid: &str, path: &str) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let status = host_sudo(&["chown", "-R", uid_gid, path])
|
||||
// Host-side fallback. A CONTAINER-namespace id must be translated into
|
||||
// the subuid range first: `sudo chown 999` writes literal host uid 999,
|
||||
// which maps to nobody inside the userns — the app then can't open its
|
||||
// own files while the chown reported success (botfights SQLITE_CANTOPEN
|
||||
// crash-loop, framework-pt 2026-08-06). Container uid N (N>=1) lives at
|
||||
// subuid_base + N - 1; the fleet provisions base 100000. uid 0 and
|
||||
// already-mapped ids (>=100000) pass through untouched.
|
||||
let host_uid_gid = if uid > 0 && uid < 100_000 {
|
||||
let map = |id: u32| if id == 0 { 1000 } else { 100_000 + id - 1 };
|
||||
format!("{}:{}", map(uid), map(gid))
|
||||
} else {
|
||||
uid_gid.to_string()
|
||||
};
|
||||
let status = host_sudo(&["chown", "-R", &host_uid_gid, path])
|
||||
.await
|
||||
.with_context(|| format!("sudo chown -R {uid_gid} {path}"))?;
|
||||
.with_context(|| format!("sudo chown -R {host_uid_gid} {path}"))?;
|
||||
if status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user