From aca84fe16a4f46545ebd250493d27cea11b1c8fc Mon Sep 17 00:00:00 2001 From: webdevcody Date: Mon, 5 Jan 2026 10:44:47 -0500 Subject: [PATCH] chore: update Docker configuration and entrypoint script - Enhanced .dockerignore to exclude additional build outputs and dependencies. - Modified dev.mjs and start.mjs to change Docker container startup behavior, removing the --build flag to preserve volumes. - Updated docker-compose.yml to add a new volume for persisting Claude CLI OAuth session keys. - Introduced docker-entrypoint.sh to fix permissions on the Claude CLI config directory. - Adjusted Dockerfile to include the entrypoint script and ensure proper user permissions. These changes improve the Docker setup and streamline the development workflow. --- .dockerignore | 20 +++++++++++++++++++- Dockerfile | 22 ++++++++++++++++------ apps/ui/src/lib/workspace-config.ts | 20 ++++++++++++++++++-- dev.mjs | 9 ++++++--- docker-compose.yml | 9 +++++++++ docker-entrypoint.sh | 19 +++++++++++++++++++ package.json | 2 +- start.mjs | 9 ++++++--- 8 files changed, 94 insertions(+), 16 deletions(-) create mode 100755 docker-entrypoint.sh diff --git a/.dockerignore b/.dockerignore index 40b878db..8163526b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,19 @@ -node_modules/ \ No newline at end of file +# Dependencies +node_modules/ +**/node_modules/ + +# Build outputs +dist/ +**/dist/ +dist-electron/ +**/dist-electron/ +build/ +**/build/ +.next/ +**/.next/ +.nuxt/ +**/.nuxt/ +out/ +**/out/ +.cache/ +**/.cache/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 3f110451..84ddc49a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,8 +53,8 @@ RUN npm run build:packages && npm run build --workspace=apps/server # ============================================================================= FROM node:22-alpine AS server -# Install git, curl, bash (for terminal), and GitHub CLI (pinned version, multi-arch) -RUN apk add --no-cache git curl bash && \ +# Install git, curl, bash (for terminal), su-exec (for user switching), and GitHub CLI (pinned version, multi-arch) +RUN apk add --no-cache git curl bash su-exec && \ GH_VERSION="2.63.2" && \ ARCH=$(uname -m) && \ case "$ARCH" in \ @@ -72,9 +72,11 @@ RUN npm install -g @anthropic-ai/claude-code WORKDIR /app -# Create non-root user +# Create non-root user with home directory RUN addgroup -g 1001 -S automaker && \ - adduser -S automaker -u 1001 + adduser -S automaker -u 1001 -h /home/automaker && \ + mkdir -p /home/automaker && \ + chown automaker:automaker /home/automaker # Copy root package.json (needed for workspace resolution) COPY --from=server-builder /app/package*.json ./ @@ -98,12 +100,17 @@ RUN git config --system --add safe.directory '*' && \ # Use gh as credential helper (works with GH_TOKEN env var) git config --system credential.helper '!gh auth git-credential' -# Switch to non-root user -USER automaker +# Copy entrypoint script for fixing permissions on mounted volumes +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +# Note: We stay as root here so entrypoint can fix permissions +# The entrypoint script will switch to automaker user before running the command # Environment variables ENV PORT=3008 ENV DATA_DIR=/data +ENV HOME=/home/automaker # Expose port EXPOSE 3008 @@ -112,6 +119,9 @@ EXPOSE 3008 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:3008/api/health || exit 1 +# Use entrypoint to fix permissions before starting +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] + # Start server CMD ["node", "apps/server/dist/index.js"] diff --git a/apps/ui/src/lib/workspace-config.ts b/apps/ui/src/lib/workspace-config.ts index 0726b785..effd442c 100644 --- a/apps/ui/src/lib/workspace-config.ts +++ b/apps/ui/src/lib/workspace-config.ts @@ -7,12 +7,28 @@ import { createLogger } from '@automaker/utils/logger'; import { getHttpApiClient } from './http-api-client'; import { getElectronAPI } from './electron'; import { getItem, setItem } from './storage'; -import path from 'path'; const logger = createLogger('WorkspaceConfig'); const LAST_PROJECT_DIR_KEY = 'automaker:lastProjectDir'; +/** + * Browser-compatible path join utility + * Works in both Node.js and browser environments + */ +function joinPath(...parts: string[]): string { + // Remove empty parts and normalize separators + const normalized = parts + .filter((p) => p) + .map((p) => p.replace(/\\/g, '/')) + .join('/') + .replace(/\/+/g, '/'); // Remove duplicate slashes + + // Preserve leading slash if first part had it + const hasLeadingSlash = parts[0]?.startsWith('/'); + return hasLeadingSlash ? '/' + normalized.replace(/^\//, '') : normalized; +} + /** * Gets the default Documents/Automaker directory path * @returns Promise resolving to Documents/Automaker path, or null if unavailable @@ -21,7 +37,7 @@ async function getDefaultDocumentsPath(): Promise { try { const api = getElectronAPI(); const documentsPath = await api.getPath('documents'); - return path.join(documentsPath, 'Automaker'); + return joinPath(documentsPath, 'Automaker'); } catch (error) { logger.error('Failed to get documents path:', error); return null; diff --git a/dev.mjs b/dev.mjs index 7236d14f..e6a44c30 100644 --- a/dev.mjs +++ b/dev.mjs @@ -172,7 +172,9 @@ async function main() { } else if (choice === '3') { console.log(''); log('Launching Docker Container (Isolated Mode)...', 'blue'); - log('Building and starting Docker containers...', 'yellow'); + log('Starting Docker containers...', 'yellow'); + log('Note: Containers will only rebuild if images are missing.', 'yellow'); + log('To force a rebuild, run: docker compose up --build', 'yellow'); console.log(''); // Check if ANTHROPIC_API_KEY is set @@ -183,8 +185,9 @@ async function main() { console.log(''); } - // Build and start containers with docker-compose - processes.docker = crossSpawn('docker', ['compose', 'up', '--build'], { + // Start containers with docker-compose (without --build to preserve volumes) + // Images will only be built if they don't exist + processes.docker = crossSpawn('docker', ['compose', 'up'], { stdio: 'inherit', cwd: __dirname, env: { diff --git a/docker-compose.yml b/docker-compose.yml index 2026ff0e..b9e51abf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,6 +59,10 @@ services: # This volume persists data between restarts but is container-managed - automaker-data:/data + # Persist Claude CLI OAuth session keys across container restarts + # This allows 'claude login' authentication to persist between restarts + - automaker-claude-config:/home/automaker/.claude + # NO host directory mounts - container cannot access your laptop files # If you need to work on a project, create it INSIDE the container # or use a separate docker-compose override file @@ -72,3 +76,8 @@ volumes: automaker-data: name: automaker-data # Named volume - completely isolated from host filesystem + + automaker-claude-config: + name: automaker-claude-config + # Named volume for Claude CLI OAuth session keys and configuration + # Persists authentication across container restarts diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 00000000..6537a66e --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -e + +# Fix permissions on Claude CLI config directory if it exists +# This handles the case where a volume is mounted and owned by root +if [ -d "/home/automaker/.claude" ]; then + chown -R automaker:automaker /home/automaker/.claude + chmod -R 755 /home/automaker/.claude +fi + +# Ensure the directory exists with correct permissions if volume is empty +if [ ! -d "/home/automaker/.claude" ]; then + mkdir -p /home/automaker/.claude + chown automaker:automaker /home/automaker/.claude + chmod 755 /home/automaker/.claude +fi + +# Switch to automaker user and execute the command +exec su-exec automaker "$@" diff --git a/package.json b/package.json index ddfd3ddf..607ece4a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "dev:electron:wsl": "npm run build:packages && npm run _dev:electron:wsl", "dev:electron:wsl:gpu": "npm run build:packages && npm run _dev:electron:wsl:gpu", "dev:server": "npm run build:packages && npm run _dev:server", - "dev:docker": "docker compose up --build", + "dev:docker": "docker compose up", "dev:full": "npm run build:packages && concurrently \"npm run _dev:server\" \"npm run _dev:web\"", "build": "npm run build:packages && npm run build --workspace=apps/ui", "build:packages": "npm run build -w @automaker/types && npm run build -w @automaker/platform && npm run build -w @automaker/utils && npm run build -w @automaker/prompts -w @automaker/model-resolver -w @automaker/dependency-resolver && npm run build -w @automaker/git-utils", diff --git a/start.mjs b/start.mjs index 22e12428..2eb1739c 100755 --- a/start.mjs +++ b/start.mjs @@ -231,7 +231,9 @@ async function main() { } else if (choice === '3') { console.log(''); log('Launching Docker Container (Isolated Mode)...', 'blue'); - log('Building and starting Docker containers...', 'yellow'); + log('Starting Docker containers...', 'yellow'); + log('Note: Containers will only rebuild if images are missing.', 'yellow'); + log('To force a rebuild, run: docker compose up --build', 'yellow'); console.log(''); // Check if ANTHROPIC_API_KEY is set @@ -242,8 +244,9 @@ async function main() { console.log(''); } - // Build and start containers with docker-compose - processes.docker = crossSpawn('docker', ['compose', 'up', '--build'], { + // Start containers with docker-compose (without --build to preserve volumes) + // Images will only be built if they don't exist + processes.docker = crossSpawn('docker', ['compose', 'up'], { stdio: 'inherit', cwd: __dirname, env: {