diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index b2d0cc5..fdbfe39 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -50,8 +50,8 @@ ENV PATH=/root/.local/bin:$PATH # Copy application code COPY --chown=appuser:appgroup . . -# Create directories for repo and instance storage -RUN mkdir -p /data/repos /data/instances && chown -R appuser:appgroup /data +# Create directories for repo, instance, and workspace storage +RUN mkdir -p /data/repos /data/instances /data/working-copies && chown -R appuser:appgroup /data # Copy wait-for-db script COPY wait-for-db.sh /usr/local/bin/wait-for-db.sh diff --git a/apps/api/src/services/manifest_compiler.py b/apps/api/src/services/manifest_compiler.py index 11f18a8..a971268 100644 --- a/apps/api/src/services/manifest_compiler.py +++ b/apps/api/src/services/manifest_compiler.py @@ -26,7 +26,7 @@ def resolve_base(manifest: dict) -> dict: result = deepcopy(manifest) base_definition_id = result.pop("base_definition_id", None) - base_version = result.pop("base_version", "latest") + result.pop("base_version", None) if base_definition_id: # This will be provided by the caller (they have the DB session) @@ -167,8 +167,11 @@ def compile_dockerfile(manifest: dict) -> str: lines.append(f"ENV HOME={home}") lines.append(f"ENV USER={name}") lines.append("") + # Ensure home directory exists and is writable by the user + lines.append(f"RUN mkdir -p {home} && chown {name}:{name} {home} && chmod 755 {home}") + lines.append("") - # Build scripts + # Build scripts build_scripts = manifest.get("scripts", {}).get("build", []) for script in build_scripts: # Normalize multi-line scripts into single RUN command diff --git a/apps/api/src/services/workspace_manager.py b/apps/api/src/services/workspace_manager.py index a37c703..ba1fb37 100644 --- a/apps/api/src/services/workspace_manager.py +++ b/apps/api/src/services/workspace_manager.py @@ -73,7 +73,13 @@ class WorkspaceManager: RuntimeError: If git clone fails. """ path = self._workspace_path(repo.id, name) - os.makedirs(os.path.dirname(path), exist_ok=True) + parent = os.path.dirname(path) + os.makedirs(parent, exist_ok=True) + # Ensure container users (various UIDs) can write to workspace dirs + try: + os.chmod(parent, 0o777) + except OSError: + pass logger.info( "Creating workspace: name=%s, repo=%s, branch=%s", name, repo.id, branch @@ -99,6 +105,23 @@ class WorkspaceManager: await GitService.clone(repo.remote_url, branch, path, ssh_key=ssh_key) + # Make workspace writable for any container user + try: + os.chmod(path, 0o777) + for root, dirs, files in os.walk(path): + for d in dirs: + try: + os.chmod(os.path.join(root, d), 0o777) + except OSError: + pass + for f in files: + try: + os.chmod(os.path.join(root, f), 0o666) + except OSError: + pass + except OSError: + logger.warning("Failed to chmod workspace path: %s", path) + workspace = Workspace( name=name, repo_id=repo.id, @@ -190,6 +213,24 @@ class WorkspaceManager: return SyncResult(branch_deleted=True) await GitService.pull(workspace.path, workspace.branch, ssh_key=ssh_key) + + # Re-apply permissive permissions after sync + try: + os.chmod(workspace.path, 0o777) + for root, dirs, files in os.walk(workspace.path): + for d in dirs: + try: + os.chmod(os.path.join(root, d), 0o777) + except OSError: + pass + for f in files: + try: + os.chmod(os.path.join(root, f), 0o666) + except OSError: + pass + except OSError: + logger.warning("Failed to chmod workspace after sync: %s", workspace.path) + workspace.last_sync_at = datetime.now() logger.info("Workspace synced: %s", workspace.id) return SyncResult(branch_deleted=False) diff --git a/apps/web/src/components/start-tool-fab.tsx b/apps/web/src/components/start-tool-fab.tsx index 4741fb7..36e0bcc 100644 --- a/apps/web/src/components/start-tool-fab.tsx +++ b/apps/web/src/components/start-tool-fab.tsx @@ -71,16 +71,12 @@ export function StartToolFAB() {
) : !selectedWorkspace ? (