diff --git a/Dockerfile.backend b/Dockerfile.backend index 11141b483..c191cd949 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -45,6 +45,9 @@ COPY flows/ ./flows/ # ----------------------------------------------------------------------------- FROM python:3.13-slim AS runtime +ARG OPENRAG_UID=1000 +ARG OPENRAG_GID=1000 + # (+) Create non-root group: "openrag" # (+) Create non-root user: "openrag" RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -52,8 +55,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ openssl \ && rm -rf /var/lib/apt/lists/* \ - && groupadd --gid 103000 openrag \ - && useradd --uid 103000 --gid openrag --no-create-home openrag + && groupadd --gid $OPENRAG_GID openrag \ + && useradd --uid $OPENRAG_UID --gid openrag --no-create-home openrag WORKDIR /app diff --git a/Dockerfile.langflow.dev b/Dockerfile.langflow.dev index 05f5f9298..d8018999d 100644 --- a/Dockerfile.langflow.dev +++ b/Dockerfile.langflow.dev @@ -11,6 +11,8 @@ ENV RUSTFLAGS="--cfg reqwest_unstable" # Accept build arguments for git repository and branch ARG GIT_REPO=https://github.com/langflow-ai/langflow.git ARG GIT_BRANCH=main +ARG OPENRAG_UID=1000 +ARG OPENRAG_GID=1000 # Install system dependencies # (+) Install uv for faster Python package management (needs root, goes to /usr/local/bin) @@ -26,8 +28,8 @@ RUN apt-get update && apt-get install -y \ rustc cargo pkg-config libssl-dev \ && rm -rf /var/lib/apt/lists/* \ && pip install uv \ - && groupadd --gid 103000 langflow \ - && useradd --uid 103000 --gid langflow --no-create-home langflow + && groupadd --gid $OPENRAG_GID langflow \ + && useradd --uid $OPENRAG_UID --gid langflow --no-create-home langflow # Switch to non-root user: "langflow" USER langflow diff --git a/Makefile b/Makefile index c4081bc6a..f2219d349 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,13 @@ GREEN=\033[0;32m # REUSABLE FUNCTIONS ###################### +# Check that a container is running after compose up; fail with a helpful message if not. +# Usage: $(call check_container_running,) +define check_container_running + $(CONTAINER_RUNTIME) container inspect $(1) --format '{{.State.Status}}' 2>/dev/null | grep -q running \ + || (echo "$(RED)ERROR: $(1) failed to start. Run 'make logs' for details.$(NC)" && exit 1) +endef + # JWT OpenSearch test function - tests that JWT authentication works against OpenSearch # Usage: $(call test_jwt_opensearch) define test_jwt_opensearch @@ -326,9 +333,10 @@ help_utils: ## Show utility commands # DEVELOPMENT ENVIRONMENTS ###################### -dev: ## Start full stack with GPU support +dev: ensure-config-dir ensure-rsa-keys ## Start full stack with GPU support @echo "$(YELLOW)Starting OpenRAG with GPU support...$(NC)" $(COMPOSE_CMD) -f docker-compose.yml -f docker-compose.gpu.yml up -d + @$(call check_container_running,openrag-backend) @echo "$(PURPLE)Services started!$(NC)" @echo " $(CYAN)Backend:$(NC) http://openrag-backend" @echo " $(CYAN)Frontend:$(NC) http://localhost:3000" @@ -336,9 +344,10 @@ dev: ## Start full stack with GPU support @echo " $(CYAN)OpenSearch:$(NC) http://localhost:9200" @echo " $(CYAN)Dashboards:$(NC) http://localhost:5601" -dev-cpu: ## Start full stack with CPU only +dev-cpu: ensure-config-dir ensure-rsa-keys ## Start full stack with CPU only @echo "$(YELLOW)Starting OpenRAG with CPU only...$(NC)" $(COMPOSE_CMD) up -d + @$(call check_container_running,openrag-backend) @echo "$(PURPLE)Services started!$(NC)" @echo " $(CYAN)Backend:$(NC) http://openrag-backend" @echo " $(CYAN)Frontend:$(NC) http://localhost:3000" @@ -361,7 +370,10 @@ ensure-rsa-keys: ## Generate RSA keys for JWT signing if absent, or fix permissi fi @if [ ! -f keys/private_key.pem ]; then \ echo "$(YELLOW)Generating RSA keys for JWT signing...$(NC)"; \ - uv run python -c "from src.main import generate_jwt_keys; generate_jwt_keys()"; \ + openssl genrsa -out keys/private_key.pem 2048; \ + openssl rsa -in keys/private_key.pem -pubout -out keys/public_key.pem; \ + chmod 600 keys/private_key.pem; \ + chmod 644 keys/public_key.pem; \ echo "$(PURPLE)RSA keys for JWT signing generated.$(NC)"; \ else \ echo "$(CYAN)RSA keys already exist, ensuring correct permissions...$(NC)"; \ @@ -547,7 +559,7 @@ factory-reset: ## Complete reset (stop, remove volumes, clear data, remove image fi; \ echo ""; \ echo "$(YELLOW)Stopping all services and removing volumes...$(NC)"; \ - $(COMPOSE_CMD) down -v --remove-orphans --rmi local || true; \ + $(COMPOSE_CMD) down -v --remove-orphans || true; \ echo "$(YELLOW)Removing local data directories...$(NC)"; \ if [ -d "opensearch-data" ]; then \ echo "Removing opensearch-data..."; \ diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index aed3933ce..5fec1eb4d 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -12,5 +12,7 @@ services: args: GIT_REPO: ${GIT_REPO:-https://github.com/langflow-ai/langflow.git} GIT_BRANCH: ${GIT_BRANCH:-main} + OPENRAG_UID: ${OPENRAG_UID:-1000} + OPENRAG_GID: ${OPENRAG_GID:-1000} # Increase memory for build (npm build is memory intensive) shm_size: '2gb' \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 98fafd9dc..b60aba332 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,8 +48,11 @@ services: build: context: . dockerfile: Dockerfile.backend + args: + OPENRAG_UID: ${OPENRAG_UID:-1000} + OPENRAG_GID: ${OPENRAG_GID:-1000} container_name: openrag-backend - user: "${OPENRAG_UID:-103000}:${OPENRAG_GID:-103000}" + user: "${OPENRAG_UID:-1000}:${OPENRAG_GID:-1000}" depends_on: - langflow environment: diff --git a/src/main.py b/src/main.py index d264d1959..7d37f31ee 100644 --- a/src/main.py +++ b/src/main.py @@ -358,7 +358,17 @@ def generate_jwt_keys(): logger.info("Generated RSA keys for JWT signing") except subprocess.CalledProcessError as e: - logger.error("Failed to generate RSA keys", error=str(e)) + stderr = e.stderr.decode() if e.stderr else "No stderr" + stdout = e.stdout.decode() if e.stdout else "No stdout" + logger.error( + "Failed to generate RSA keys", + error=str(e), + stderr=stderr, + stdout=stdout, + private_key_path=private_key_path, + keys_dir_exists=os.path.exists(keys_dir), + keys_dir_writable=os.access(keys_dir, os.W_OK), + ) TelemetryClient.send_event_sync( Category.SERVICE_INITIALIZATION, MessageId.ORB_SVC_JWT_KEY_FAIL ) @@ -366,11 +376,25 @@ def generate_jwt_keys(): else: # Ensure correct permissions on existing keys try: - os.chmod(private_key_path, 0o600) - os.chmod(public_key_path, 0o644) - logger.info("RSA keys already exist, ensured correct permissions") + if os.access(private_key_path, os.W_OK): + os.chmod(private_key_path, 0o600) + logger.info("RSA private already exists. Successfully ensured the private key permission.") + else: + logger.warning("Failed to set permissions on RSA private key: Private key is not writable.", + error=str(e), + private_key_path=private_key_path, + ) + + if os.access(public_key_path, os.W_OK): + os.chmod(public_key_path, 0o644) + logger.info("RSA public already exist. Successfully ensured the public key permission.") + else: + logger.warning("Failed to set permissions on RSA private key: Private key is not writable.", + error=str(e), + public_key_path=public_key_path, + ) except OSError as e: - logger.warning("Failed to set permissions on existing keys", error=str(e)) + logger.warning("Failed to set permissions on existing RSA keys.", error=str(e)) def _get_documents_dir():