5fc8e035e6
- Add design doc / ADR for configurable /home/user home directory - Add implementation plan with phased rollout - Add test plan / QA checklist - Update OpenSpec task for home-path-expansion
8.3 KiB
8.3 KiB
Implementation Plan: Tool Container Home Directory
Overview
Make /home/user the configurable, default workspace/home directory for all tool containers, preserve the repository/workspace directory name in the mount target, migrate legacy tool types, and keep /workspace as a compatibility symlink.
Goals
- Add a
home_directoryfield toToolTypeand the manifest schema. - Use that field to control workspace mount target,
HOME,WORKDIR, and~/$HOMEexpansion. - Preserve
{repo_name}/{workspace_name}in the mount target. - Migrate legacy Dockerfile/Compose templates via Alembic.
- Keep
/workspacesymlink for backward compatibility. - Add an entrypoint permission fixer for runtime-mounted paths.
- Validate with unit and integration tests.
Phases
Phase 1: Database and model changes
Files:
apps/api/src/models/tool/tool_type.pyapps/api/alembic/versions/<new>_add_tool_type_home_directory.py
Tasks:
- Add
home_directory: Mapped[str]column toToolType, non-nullable with server default"/home/user". - Create Alembic migration that:
- Adds the column.
- Updates existing rows to
/home/user. - Rewrites
compose_templateanddockerfile_templateto replace/workspacewith/home/user/{{WORKSPACE_NAME}}(or a compatible template variable).
- Provide downgrade that reverses template rewrites and drops the column.
Quality gate:
cd apps/api && alembic upgrade headsucceeds.alembic downgrade -1succeeds and restores/workspacestrings.- Existing tests still pass.
Phase 2: Manifest schema and compiler
Files:
apps/api/src/services/build/manifest_compiler.pyapps/api/src/services/tool/instance_service.pyapps/api/src/api/tool/tool_types_validation.py(if schema validation is added)
Tasks:
- Accept
home_directoryin the manifest schema (optional; fallback to ToolType value). - In
compile_dockerfile():- Set
ENV HOME={home_directory}andENV USER={user.name}. - Set
WORKDIR {home_directory}(unlessruntime.working_diris present). - Create the home directory and pre-create
.config,.local/share,.cacheunder it. - Add a step to create
/workspaceas a symlink to{home_directory}/{repo_name}(placeholder or startup-time).
- Set
- In
compile_compose():- Use
{home_directory}/{repo_name}as the default repo mount target when the manifest has no explicit repo mount. - Keep
~/$HOMEexpansion base equal tohome_directory.
- Use
- Update
get_manifest_home_dir()to honormanifest.home_directorybefore deriving fromuser.name. - Pass
home_directorythrough the manifest-based instance lifecycle.
Quality gate:
compile_dockerfile()output containsENV HOME=/home/userandWORKDIR /home/userfor default manifests.compile_compose()output mounts repo at/home/user/{repo_name}when no explicit repo mount exists.
Phase 3: Legacy instance generation
Files:
apps/api/src/services/tool/instance_service.pyapps/api/src/services/docker/compose.py
Tasks:
- In
create_tool_instance()fordefinition_type == "dockerfile":- Read
tool_type.home_directory(default/home/user). - Mount
{repo_path}:{home_directory}/{repo_name}instead of{repo_path}:/workspace. - Generate or adjust Dockerfile/Compose to create
/workspacesymlink.
- Read
- For
definition_type == "compose":- Render
{home_directory}and{WORKSPACE_NAME}into the template. - Validate that
{WORKSPACE_NAME}is available as a template variable.
- Render
- Add
WORKSPACE_NAMEto the render variables inrender_compose_template().
Quality gate:
- Legacy
dockerfileinstance compose mounts repo at/home/user/{repo_name}. - Legacy
composetemplate with/home/user/{{WORKSPACE_NAME}}renders correctly.
Phase 4: Config-profile and git mount expansion
Files:
apps/api/src/services/config/config_profile_resolver.pyapps/api/src/services/tool/instance_service.py
Tasks:
- Ensure
expand_container_path()uses the resolvedhome_directory(already present; verify it is threaded through). - In
start_tool_instance(), computehome_dirfrom ToolType/manifest and pass it to:apply_resolved_profile()resolve_git_mounts()/resolve_git_mount_mappings()
- Confirm workspace/repo name is used as the mount target, not a generic
workspacestring.
Quality gate:
- Config profile mount target
~/configexpands to/home/user/config. - Git mount target
~/repoexpands to/home/user/repo.
Phase 5: Entrypoint permission fixer
Files:
apps/api/src/services/build/manifest_compiler.pyapps/api/src/services/shared/permission_fixer.pytool-images/base.dockerfileor generated entrypoint
Tasks:
- Generate an entrypoint script that, before switching to the runtime user:
- Detects the container user name/uid.
- Runs
chownon{home_directory}and key mount points. - Creates
/workspacesymlink if it does not yet exist. - Avoids recursive chown of large subtrees; target top-level dirs and runtime-created files.
- Ensure manifest-generated Dockerfiles install
sudoand configure passwordless sudo for the runtime user (already partially done). - Consider updating
tool-images/base.dockerfileto includesudoand an entrypoint hook, or keep the fixer entirely in generated images.
Quality gate:
- Container starts successfully.
- Container user can write to
{home_directory}and mounted config/git directories. /workspacesymlink resolves to the repo/workspace directory.
Phase 6: Tests
Files:
apps/api/tests/unit/test_home_path_expansion.pyapps/api/tests/unit/test_manifest_compiler.pyapps/api/tests/unit/test_instance_service.py(new or expanded)apps/api/tests/integration/test_tool_instance_lifecycle.py(new or expanded)
Tasks:
- Unit tests:
expand_container_pathwith~,$HOME, absolute, and relative paths.get_manifest_home_dirwith and withouthome_directory, with and withoutuser.name.compile_dockerfileincludes correctENV HOME,WORKDIR, and/workspacesymlink step.compile_composedefaults repo mount to/home/user/{repo_name}when no explicit repo mount exists.- Explicit manifest repo mount target is preserved.
- Integration tests:
- Create a manifest tool instance and verify the running container has
HOME=/home/user, repo at/home/user/{repo_name}, and/workspacesymlink. - Verify a config-profile mount under
~is writable by the container user. - Verify git mount under
~is writable. - Verify legacy dockerfile tool uses
/home/user/{repo_name}after migration.
- Create a manifest tool instance and verify the running container has
Quality gate:
cd apps/api && pytest tests/unit/... tests/integration/... -xvspasses.cd apps/web && npm run typecheckpasses.
Dependencies
- The existing
home-path-expansionhelpers (expand_container_path) must already be in place. - Alembic migration infrastructure must be working.
- Manifest compiler must support generated entrypoints (already partially implemented).
Risks and Mitigations
| Risk | Mitigation |
|---|---|
Alembic migration rewrites commands/env vars that contain /workspace |
Scope the replacement to volume mount lines only; add tests for edge cases. |
/workspace symlink target missing at build time |
Create placeholder directory in Dockerfile; finalize symlink in entrypoint at startup. |
| Entrypoint chown slow on large repos | Chown top-level directories only; rely on runtime user for new files. |
Base image USER user cannot run sudo |
Either install sudo in base image or keep entrypoint running as root before su/gosu to runtime user. |
Legacy templates without {{WORKSPACE_NAME}} break |
Migration injects the variable; validation rejects missing required variables. |
OpenSpec Task Update
After this plan is accepted, update or replace openspec/tasks/home-path-expansion.md to reflect the expanded scope (configurable home_directory, repo-name mount target, migration, compatibility symlink, permission fixer, tests).
Rollout
- Merge Phase 1 (schema + migration) first so the column exists.
- Merge Phase 2 and 3 (compiler + legacy generation) next.
- Merge Phase 4 and 5 (profile/git mounts + permission fixer).
- Merge Phase 6 (tests) with the previous phases as appropriate.
- Run migration in production during a maintenance window; validate a few existing tool types before full rollout.