fix: force interactive login shell for bash containers

Detached containers with tty: true still exited immediately because a
plain /bin/bash invocation exits with code 0 when stdin is not connected.

- Detect when the container CMD is /bin/bash or bash and exec an
  interactive login shell () after dropping privileges
- Keep the generic  path for non-shell commands
- Bump compiler_version to v4 to force a fresh image build

Quality gates:
- pytest tests/unit: 219 passed
- ruff: clean on changed files
- mypy: clean on changed files
This commit is contained in:
Developer
2026-06-15 11:18:28 +00:00
parent ed1f7805f6
commit 29e48cdb65
12 changed files with 24 additions and 16 deletions
@@ -393,6 +393,13 @@ def compile_entrypoint(manifest: dict) -> str:
if user:
name = user["name"]
lines.append("# Drop privileges to the container user")
# When the container command is a shell, force an interactive login
# shell. Detached containers may not have stdin connected, and a plain
# /bin/bash invocation exits immediately with code 0. -il keeps it
# alive so the container stays running for docker exec/web terminals.
lines.append('if [ "$1" = "/bin/bash" ] || [ "$1" = "bash" ]; then')
lines.append(f' exec runuser -u {name} -- /bin/bash -il')
lines.append('fi')
lines.append(f'exec runuser -u {name} -- "$@"')
else:
lines.append('exec "$@"')
@@ -582,7 +589,7 @@ def compute_image_tag(tool_name: str, manifest: dict) -> str:
Returns:
Docker image tag string.
"""
compiler_version = "v3" # bump when compile_dockerfile/entrypoint/compose change
compiler_version = "v4" # bump when compile_dockerfile/entrypoint/compose change
canonical = json.dumps(manifest, sort_keys=True, separators=(",", ":"))
hash_suffix = hashlib.sha256(
f"{compiler_version}:{canonical}".encode()