diff --git a/Dockerfile b/Dockerfile index c32b17644..c1e84179f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -78,9 +78,25 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Install Claude CLI globally (available to all users via npm global bin) RUN npm install -g @anthropic-ai/claude-code -# Create non-root user with home directory BEFORE installing Cursor CLI -RUN groupadd -g 1001 automaker && \ - useradd -u 1001 -g automaker -m -d /home/automaker -s /bin/bash automaker && \ +# Create non-root user with host UID/GID to avoid permission issues when mounting volumes +ARG USER_ID=1000 +ARG GROUP_ID=1000 +RUN set -e; \ + # Safety: refuse to run as root + if [ "${USER_ID}" = "0" ] || [ "${GROUP_ID}" = "0" ]; then \ + echo "ERROR: Cannot create container user with UID or GID 0 (root)" >&2; \ + exit 1; \ + fi; \ + # Remove existing node user/group if they conflict with our desired IDs + if getent passwd ${USER_ID} >/dev/null 2>&1; then \ + userdel -f $(getent passwd ${USER_ID} | cut -d: -f1) || true; \ + fi; \ + if getent group ${GROUP_ID} >/dev/null 2>&1; then \ + groupdel $(getent group ${GROUP_ID} | cut -d: -f1) || true; \ + fi; \ + # Create automaker group and user + groupadd -g ${GROUP_ID} automaker && \ + useradd -u ${USER_ID} -g automaker -m -d /home/automaker -s /bin/bash automaker && \ mkdir -p /home/automaker/.local/bin && \ mkdir -p /home/automaker/.cursor && \ chown -R automaker:automaker /home/automaker && \ diff --git a/Dockerfile.dev b/Dockerfile.dev index 87ac6bf66..1c2178a83 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -27,9 +27,25 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Install Claude CLI globally RUN npm install -g @anthropic-ai/claude-code -# Create non-root user -RUN groupadd -g 1001 automaker && \ - useradd -u 1001 -g automaker -m -d /home/automaker -s /bin/bash automaker && \ +# Create non-root user with host UID/GID to avoid permission issues +ARG USER_ID=1000 +ARG GROUP_ID=1000 +RUN set -e; \ + # Safety: refuse to run as root + if [ "${USER_ID}" = "0" ] || [ "${GROUP_ID}" = "0" ]; then \ + echo "ERROR: Cannot create container user with UID or GID 0 (root)" >&2; \ + exit 1; \ + fi; \ + # Remove existing node user/group if they conflict with our desired IDs + if getent passwd ${USER_ID} >/dev/null 2>&1; then \ + userdel -f $(getent passwd ${USER_ID} | cut -d: -f1) || true; \ + fi; \ + if getent group ${GROUP_ID} >/dev/null 2>&1; then \ + groupdel $(getent group ${GROUP_ID} | cut -d: -f1) || true; \ + fi; \ + # Create automaker group and user + groupadd -g ${GROUP_ID} automaker && \ + useradd -u ${USER_ID} -g automaker -m -d /home/automaker -s /bin/bash automaker && \ mkdir -p /home/automaker/.local/bin && \ mkdir -p /home/automaker/.cursor && \ chown -R automaker:automaker /home/automaker && \ diff --git a/README.md b/README.md index 8bfd2a0af..73a8f1792 100644 --- a/README.md +++ b/README.md @@ -240,9 +240,12 @@ services: server: volumes: # Mount your project directories - - /path/to/your/project:/projects/your-project + # On Fedora/RHEL with SELinux, add :z flag to allow container access + - /path/to/your/project:/projects/your-project:z ``` +**Note for Fedora/RHEL users:** The `:z` flag is required for SELinux to allow container access to mounted volumes. On other systems, this flag is ignored. + ##### Claude CLI Authentication (Optional) To use Claude Code CLI authentication instead of an API key, mount your Claude CLI config directory: @@ -293,18 +296,20 @@ GH_TOKEN=gho_your_github_token_here services: server: volumes: - # Your projects - - /path/to/project1:/projects/project1 - - /path/to/project2:/projects/project2 + # Your projects (use :z flag on Fedora/RHEL for SELinux) + - /path/to/project1:/projects/project1:z + - /path/to/project2:/projects/project2:z - # Authentication configs - - ~/.claude:/home/automaker/.claude - - ~/.config/gh:/home/automaker/.config/gh + # Authentication configs (use :z flag on Fedora/RHEL for SELinux) + - ~/.claude:/home/automaker/.claude:z + - ~/.config/gh:/home/automaker/.config/gh:z - ~/.gitconfig:/home/automaker/.gitconfig:ro environment: - GH_TOKEN=${GH_TOKEN} ``` +**Note:** The `:z` flag is required on Fedora/RHEL with SELinux enforcing. On other systems it's safely ignored. + ##### Architecture Support The Docker image supports both AMD64 and ARM64 architectures. The GitHub CLI and Claude CLI are automatically downloaded for the correct architecture during build. diff --git a/apps/server/src/lib/sdk-options.ts b/apps/server/src/lib/sdk-options.ts index ff3d60678..cc1df2f57 100644 --- a/apps/server/src/lib/sdk-options.ts +++ b/apps/server/src/lib/sdk-options.ts @@ -129,10 +129,30 @@ export const TOOL_PRESETS = { specGeneration: ['Read', 'Glob', 'Grep'] as const, /** Full tool access for feature implementation */ - fullAccess: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch', 'TodoWrite'] as const, + fullAccess: [ + 'Read', + 'Write', + 'Edit', + 'Glob', + 'Grep', + 'Bash', + 'WebSearch', + 'WebFetch', + 'TodoWrite', + ] as const, /** Tools for chat/interactive mode */ - chat: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch', 'TodoWrite'] as const, + chat: [ + 'Read', + 'Write', + 'Edit', + 'Glob', + 'Grep', + 'Bash', + 'WebSearch', + 'WebFetch', + 'TodoWrite', + ] as const, } as const; /** diff --git a/docker-compose.dev-server.yml b/docker-compose.dev-server.yml index 7765dcb5d..63e711467 100644 --- a/docker-compose.dev-server.yml +++ b/docker-compose.dev-server.yml @@ -17,6 +17,9 @@ services: build: context: . dockerfile: Dockerfile.dev + args: + USER_ID: ${USER_ID:-1000} + GROUP_ID: ${GROUP_ID:-1000} container_name: automaker-dev-server-only restart: unless-stopped ports: @@ -47,7 +50,8 @@ services: - IS_CONTAINERIZED=true volumes: # Mount source code for live reload - - .:/app:cached + # :z flag is required for SELinux (Fedora/RHEL) to allow container access + - .:/app:z # Use named volume for node_modules to avoid platform conflicts # This ensures native modules are built for the container's architecture diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 28ef08daf..3359f787b 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -18,6 +18,9 @@ services: build: context: . dockerfile: Dockerfile.dev + args: + USER_ID: ${USER_ID:-1000} + GROUP_ID: ${GROUP_ID:-1000} container_name: automaker-dev-server restart: unless-stopped ports: @@ -48,7 +51,8 @@ services: - IS_CONTAINERIZED=true volumes: # Mount source code for live reload - - .:/app:cached + # :z flag is required for SELinux (Fedora/RHEL) to allow container access + - .:/app:z # Use named volume for node_modules to avoid platform conflicts # This ensures native modules are built for the container's architecture @@ -94,6 +98,9 @@ services: build: context: . dockerfile: Dockerfile.dev + args: + USER_ID: ${USER_ID:-1000} + GROUP_ID: ${GROUP_ID:-1000} container_name: automaker-dev-ui restart: unless-stopped ports: @@ -106,7 +113,8 @@ services: - VITE_APP_MODE=3 volumes: # Mount source code for live reload - - .:/app:cached + # :z flag is required for SELinux (Fedora/RHEL) to allow container access + - .:/app:z # Share node_modules with server container - automaker-dev-node-modules:/app/node_modules diff --git a/docker-compose.override.yml.example b/docker-compose.override.yml.example index b4ef6c47b..15b7d8748 100644 --- a/docker-compose.override.yml.example +++ b/docker-compose.override.yml.example @@ -2,19 +2,21 @@ services: server: volumes: # Mount your workspace directory to /projects inside the container + # The :z flag relabels for SELinux (required on Fedora/RHEL, ignored elsewhere) # Example: mount your local /workspace to /projects inside the container - - /Users/webdevcody/Workspace/automaker-workspace:/projects:rw + - /Users/webdevcody/Workspace/automaker-workspace:/projects:rw,z # ===== CLI Authentication (Optional) ===== # Mount host CLI credentials to avoid re-authenticating in container + # Note: :z flag is required for SELinux (Fedora/RHEL), safe to use on all systems # Claude CLI - mount your ~/.claude directory (Linux/Windows) # This shares your 'claude login' OAuth session with the container - # - ~/.claude:/home/automaker/.claude + # - ~/.claude:/home/automaker/.claude:z # Cursor CLI - mount your ~/.cursor directory (Linux/Windows) # This shares your 'cursor-agent login' OAuth session with the container - # - ~/.cursor:/home/automaker/.cursor + # - ~/.cursor:/home/automaker/.cursor:z environment: # Set root directory for all projects and file operations diff --git a/docker-compose.yml b/docker-compose.yml index 227450ada..3111d78fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,9 @@ services: context: . dockerfile: Dockerfile target: server + args: + USER_ID: ${USER_ID:-1000} + GROUP_ID: ${GROUP_ID:-1000} container_name: automaker-server restart: unless-stopped ports: diff --git a/docs/docker-isolation.md b/docs/docker-isolation.md index eb8fe7e1b..024681d27 100644 --- a/docs/docker-isolation.md +++ b/docs/docker-isolation.md @@ -46,7 +46,8 @@ If you need to work on a host project, create `docker-compose.project.yml`: services: server: volumes: - - ./my-project:/projects/my-project:ro # :ro = read-only + # :ro,z = read-only + SELinux relabel (safe on all systems) + - ./my-project:/projects/my-project:ro,z ``` Then run: @@ -55,7 +56,11 @@ Then run: docker-compose -f docker-compose.yml -f docker-compose.project.yml up -d ``` -**Tip**: Use `:ro` (read-only) when possible for extra safety. +**Tips**: + +- Use `:ro` (read-only) when possible for extra safety +- **Fedora/RHEL users**: Add `:z` flag for SELinux compatibility (e.g., `./my-project:/projects/my-project:z` or `./my-project:/projects/my-project:ro,z` for read-only) +- The `:z` flag is safely ignored on systems without SELinux ## CLI Authentication (macOS) @@ -105,10 +110,13 @@ echo "CURSOR_AUTH_TOKEN=$(jq -r '.accessToken' ~/.config/cursor/auth.json)" >> . ```yaml # In docker-compose.override.yml volumes: - - ~/.claude:/home/automaker/.claude - - ~/.config/cursor:/home/automaker/.config/cursor + # On Fedora/RHEL: add :z flag for SELinux + - ~/.claude:/home/automaker/.claude:z + - ~/.config/cursor:/home/automaker/.config/cursor:z ``` +**Note for Fedora/RHEL users**: The `:z` flag is required for SELinux. On other systems it's ignored. + ## Troubleshooting | Problem | Solution | diff --git a/package-lock.json b/package-lock.json index 7662b3ab5..072e2b70c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1479,7 +1479,7 @@ }, "node_modules/@electron/node-gyp": { "version": "10.2.0-electron.1", - "resolved": "git+ssh://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", + "resolved": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", "integrity": "sha512-4MSBTT8y07YUDqf69/vSh80Hh791epYqGtWHO3zSKhYFwQg+gx9wi1PqbqP6YqC4WMsNxZ5l9oDmnWdK5pfCKQ==", "dev": true, "license": "MIT", @@ -11553,7 +11553,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11575,7 +11574,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11597,7 +11595,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11619,7 +11616,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11641,7 +11637,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11663,7 +11658,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11685,7 +11679,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11707,7 +11700,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11729,7 +11721,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11751,7 +11742,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11773,7 +11763,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -14016,7 +14005,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, diff --git a/scripts/launcher-utils.mjs b/scripts/launcher-utils.mjs index 1dcdab7f7..8aa1a55df 100644 --- a/scripts/launcher-utils.mjs +++ b/scripts/launcher-utils.mjs @@ -824,6 +824,36 @@ function checkImageExists(imageName) { } } +/** + * Get host user credentials for Docker volume permission matching + * @returns {{USER_ID: string, GROUP_ID: string}} Host user and group IDs + * @throws {Error} If running as root user (UID/GID 0) + */ +function getHostUserCredentials() { + const uid = process.getuid?.(); + const gid = process.getgid?.(); + + // Check if getuid/getgid are available (Linux/macOS) + if (uid !== undefined && gid !== undefined) { + // Fail if running as root - containers should not run as root + if (uid === 0 || gid === 0) { + log('ERROR: Running as root user (UID/GID 0) is not allowed.', 'red'); + log('For security reasons, Automaker containers must run as a non-root user.', 'red'); + log('Please run this command without sudo or as a non-root user.', 'yellow'); + process.exit(1); + } + + return { + USER_ID: uid.toString(), + GROUP_ID: gid.toString(), + }; + } + + // Fallback for Windows (getuid/getgid not available) + log('Using default UID/GID 1000 (Windows platform)', 'blue'); + return { USER_ID: '1000', GROUP_ID: '1000' }; +} + /** * Launch Docker containers for development with live reload * Uses docker-compose.dev.yml which volume mounts the source code @@ -869,6 +899,8 @@ export async function launchDockerDevContainers({ baseDir, processes }) { cwd: baseDir, env: { ...process.env, + // Pass host UID/GID to avoid permission issues with volume mounts + ...getHostUserCredentials(), }, }); @@ -931,6 +963,8 @@ export async function launchDockerDevServerContainer({ baseDir, processes }) { cwd: baseDir, env: { ...process.env, + // Pass host UID/GID to avoid permission issues with volume mounts + ...getHostUserCredentials(), }, }); @@ -1063,6 +1097,8 @@ export async function launchDockerContainers({ baseDir, processes }) { cwd: baseDir, env: { ...process.env, + // Pass host UID/GID to avoid permission issues with volume mounts + ...getHostUserCredentials(), }, }); } else { @@ -1079,6 +1115,8 @@ export async function launchDockerContainers({ baseDir, processes }) { cwd: baseDir, env: { ...process.env, + // Pass host UID/GID to avoid permission issues with volume mounts + ...getHostUserCredentials(), }, }); }