feat(terminal): use project name for in-container cwd and clone directory

This commit is contained in:
Developer
2026-06-17 13:26:16 +00:00
parent 93da9b42a2
commit 3e59a257dc
9 changed files with 196 additions and 146 deletions
@@ -236,18 +236,16 @@ def compile_dockerfile(manifest: dict) -> str:
lines.append(f"RUN chown -R {user['name']}:{user['name']} {dir_str}")
lines.append("")
# Create the /workspace compatibility symlink only when the workspace name
# is known at image-build time. Otherwise the entrypoint creates it at
# runtime from the WORKSPACE_NAME environment variable.
# Ensure the project directory exists so the WORKDIR below succeeds. The
# compatibility /workspace symlink is no longer created for new images.
workspace_target = f"{home_dir}/{workspace_name}"
if "{{WORKSPACE_NAME}}" not in workspace_name:
workspace_is_placeholder = "{{WORKSPACE_NAME}}" in workspace_name
if not workspace_is_placeholder:
lines.append(f"RUN mkdir -p {workspace_target}")
if user:
lines.append(
f"RUN ln -sfn {workspace_target} /workspace && chown -R {user['name']}:{user['name']} {home_dir}"
f"RUN chown -R {user['name']}:{user['name']} {home_dir}"
)
else:
lines.append(f"RUN ln -sfn {workspace_target} /workspace")
lines.append("")
# Entrypoint for startup scripts
@@ -260,18 +258,21 @@ def compile_dockerfile(manifest: dict) -> str:
lines.append("")
# Do not switch to the runtime user in the Dockerfile. The entrypoint
# starts as root so it can create the /workspace compatibility symlink
# (which lives under /) and fix mount ownership, then it drops privileges
# starts as root so it can fix mount ownership, then it drops privileges
# to the container user before exec-ing the real command.
# Set WORKDIR to the configured home directory unless runtime.working_dir
# explicitly overrides it.
# Set WORKDIR to the project directory unless runtime.working_dir
# explicitly overrides it. When the workspace name is a runtime
# placeholder, the Dockerfile cannot know the literal directory, so fall
# back to the home directory; compose supplies the exact working_dir.
runtime = manifest.get("runtime", {})
working_dir = runtime.get("working_dir")
if working_dir:
lines.append(f"WORKDIR {expand_container_path(working_dir, home_dir)}")
else:
elif workspace_is_placeholder:
lines.append(f"WORKDIR {home_dir}")
else:
lines.append(f"WORKDIR {workspace_target}")
lines.append("")
# Entrypoint and CMD
@@ -290,8 +291,8 @@ def compile_entrypoint(manifest: dict) -> str:
Injects a permission-fixer preamble that runs as root (or via sudo) before
any user-defined startup script. It chowns the home directory and a safe
subset of mount parents to the container user, creates the /workspace
compatibility symlink, and avoids recursive chown of large repo subtrees.
subset of mount parents to the container user, ensures the project
directory exists, and avoids recursive chown of large repo subtrees.
Args:
manifest: Fully resolved manifest JSON.
@@ -346,7 +347,7 @@ def compile_entrypoint(manifest: dict) -> str:
lines.append('mkdir -p "$HOME_DIR"')
lines.append('fix_owner "$HOME_DIR"')
lines.append("")
lines.append("# Ensure workspace target exists and is owned by the container user")
lines.append("# Ensure project directory exists and is owned by the container user")
lines.append('mkdir -p "$WORKSPACE_TARGET"')
lines.append('fix_owner "$WORKSPACE_TARGET"')
lines.append("")
@@ -355,16 +356,6 @@ def compile_entrypoint(manifest: dict) -> str:
lines.append(' rm -rf "${HOME_DIR}/{{WORKSPACE_NAME}}"')
lines.append('fi')
lines.append("")
lines.append("# Create /workspace compatibility symlink")
lines.append("# / is owned by root, so we need root or passwordless sudo.")
lines.append('if [ "$(id -u)" = "0" ]; then')
lines.append(' ln -sfn "$WORKSPACE_TARGET" /workspace')
lines.append('elif [ -n "$SUDO" ]; then')
lines.append(' sudo ln -sfn "$WORKSPACE_TARGET" /workspace')
lines.append('else')
lines.append(' ln -sfn "$WORKSPACE_TARGET" /workspace 2>/dev/null || true')
lines.append('fi')
lines.append("")
lines.append("# Fix ownership of declared mount targets (top-level only)")
for mount in manifest.get("mounts", []):
target = mount.get("target")
@@ -442,11 +433,13 @@ def compile_compose(manifest: dict, variables: dict[str, Any]) -> str:
service["working_dir"] = expand_container_path(
runtime["working_dir"], home_dir
)
else:
service["working_dir"] = f"{home_dir}/{workspace_name}"
# The entrypoint starts as root (Dockerfile does not set USER) so it can
# create the /workspace compatibility symlink and fix mount ownership. It
# drops privileges to the container user internally before exec-ing the
# real command, so do not set compose-level user override here.
# fix mount ownership. It drops privileges to the container user internally
# before exec-ing the real command, so do not set compose-level user
# override here.
if user:
service["user"] = "0:0"
@@ -460,8 +453,8 @@ def compile_compose(manifest: dict, variables: dict[str, Any]) -> str:
if env:
service["environment"] = dict(env)
# Expose the workspace/repo name so the entrypoint can finalize the
# /workspace compatibility symlink at container startup.
# Expose the project name so the entrypoint can create the project
# directory at container startup.
if "environment" not in service:
service["environment"] = {}
service["environment"]["WORKSPACE_NAME"] = workspace_name