From 6d34cba4ba1005854752241b43b88e32064e2a67 Mon Sep 17 00:00:00 2001 From: Masaki Yatsu Date: Wed, 3 Dec 2025 16:09:24 +0900 Subject: [PATCH] feat(librechat): install librechat --- README.md | 246 +++++++------- justfile | 1 + librechat/.gitignore | 2 + librechat/README.md | 227 +++++++++++++ librechat/justfile | 309 ++++++++++++++++++ librechat/librechat-config.gomplate.yaml | 89 +++++ .../tavily-external-secret.gomplate.yaml | 22 ++ librechat/values.gomplate.yaml | 150 +++++++++ 8 files changed, 935 insertions(+), 111 deletions(-) create mode 100644 librechat/.gitignore create mode 100644 librechat/README.md create mode 100644 librechat/justfile create mode 100644 librechat/librechat-config.gomplate.yaml create mode 100644 librechat/tavily-external-secret.gomplate.yaml create mode 100644 librechat/values.gomplate.yaml diff --git a/README.md b/README.md index 68efef4..80c6eb8 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ A remotely accessible Kubernetes home lab with OIDC authentication. Build a mode ### LLM & AI Applications (Optional) +- **[Ollama](https://ollama.com/)**: Local LLM inference server with GPU acceleration +- **[LibreChat](https://www.librechat.ai/)**: Web-based chat interface with multi-model support and MCP integration - **[Langfuse](https://langfuse.com/)**: LLM observability and analytics platform for tracking and debugging AI applications ### Orchestration (Optional) @@ -116,21 +118,13 @@ Lightweight Kubernetes distribution optimized for edge computing: - **Production Ready**: Full Kubernetes functionality with minimal overhead - **Easy Deployment**: Single binary installation with built-in ingress -### Longhorn +### PostgreSQL -Enterprise-grade distributed storage system: +Production-ready relational database: -- **Highly Available**: Block storage with no single point of failure -- **Backup & Recovery**: Built-in disaster recovery capabilities -- **NFS Support**: Persistent volumes with NFS compatibility - -### HashiCorp Vault - -Centralized secrets management: - -- **Secure Storage**: Encrypted secret storage with access control -- **Dynamic Secrets**: Automatic credential generation and rotation -- **External Secrets Integration**: Syncs with Kubernetes via External Secrets Operator +- **High Availability**: Clustered deployment with CloudNativePG +- **pgvector Extension**: Vector similarity search for AI/ML workloads +- **Multi-Tenant**: Shared database for Keycloak and applications ### Keycloak @@ -140,13 +134,21 @@ Open-source identity and access management: - **User Federation**: Identity brokering and external provider integration - **Group-Based Access**: Role and permission management -### PostgreSQL +### HashiCorp Vault -Production-ready relational database: +Centralized secrets management: -- **High Availability**: Clustered deployment with CloudNativePG -- **pgvector Extension**: Vector similarity search for AI/ML workloads -- **Multi-Tenant**: Shared database for Keycloak and applications +- **Secure Storage**: Encrypted secret storage with access control +- **Dynamic Secrets**: Automatic credential generation and rotation +- **External Secrets Integration**: Syncs with Kubernetes via External Secrets Operator + +### External Secrets Operator + +Kubernetes operator for secret synchronization: + +- **Vault Integration**: Automatically syncs secrets from Vault to Kubernetes +- **Multiple Backends**: Supports various secret management systems +- **Secure Rotation**: Automatic secret lifecycle management ### Prometheus and Grafana @@ -160,13 +162,27 @@ Comprehensive monitoring and observability stack: [📖 See Prometheus Documentation](./prometheus/README.md) -### External Secrets Operator +### Goldilocks -Kubernetes operator for secret synchronization: +Resource recommendation dashboard for right-sizing workloads: -- **Vault Integration**: Automatically syncs secrets from Vault to Kubernetes -- **Multiple Backends**: Supports various secret management systems -- **Secure Rotation**: Automatic secret lifecycle management +- **VPA Integration**: Powered by Vertical Pod Autoscaler for metrics-based recommendations +- **Visual Dashboard**: User-friendly interface for viewing resource recommendations +- **QoS Guidance**: Recommendations for Guaranteed, Burstable, and BestEffort classes +- **Monitoring-Only Mode**: Observes workloads without automatic scaling +- **Namespace-Based**: Enable monitoring per namespace with labels + +[📖 See Goldilocks Documentation](./goldilocks/README.md) + +[📖 See VPA Documentation](./vpa/README.md) + +### Longhorn + +Enterprise-grade distributed storage system: + +- **Highly Available**: Block storage with no single point of failure +- **Backup & Recovery**: Built-in disaster recovery capabilities +- **NFS Support**: Persistent volumes with NFS compatibility ### MinIO @@ -187,68 +203,6 @@ Multi-user platform for interactive computing: [📖 See JupyterHub Documentation](./jupyterhub/README.md) -### MLflow - -Machine learning lifecycle management platform: - -- **Experiment Tracking**: Log parameters, metrics, and artifacts for ML experiments -- **Model Registry**: Version and manage ML models with deployment lifecycle -- **Keycloak Authentication**: OAuth2 integration with group-based access control - -[📖 See MLflow Documentation](./mlflow/README.md) - -### KServe - -Model serving platform for deploying ML models on Kubernetes: - -- **Multi-Framework Support**: TensorFlow, PyTorch, scikit-learn, XGBoost, MLflow, and more -- **MLflow Integration**: Deploy models directly from MLflow Model Registry -- **Inference Protocols**: REST and gRPC with v2 Open Inference Protocol -- **RawDeployment Mode**: Uses native Kubernetes Deployments without Knative dependency - -[📖 See KServe Documentation](./kserve/README.md) - -### Langfuse - -LLM observability and analytics platform: - -- **Trace Tracking**: Monitor LLM calls, chains, and agent executions with detailed traces -- **Prompt Management**: Version and test prompts with playground interface -- **Analytics**: Track costs, latency, and token usage across all LLM applications -- **Keycloak Authentication**: OAuth2 integration with automatic user provisioning - -[📖 See Langfuse Documentation](./langfuse/README.md) - -### Apache Superset - -Modern business intelligence platform: - -- **Rich Visualizations**: 40+ chart types including mixed charts, treemaps, and heatmaps -- **SQL Lab**: Powerful editor for complex queries and dataset creation -- **Keycloak & Trino**: OAuth2 authentication and Iceberg data lake integration - -[📖 See Superset Documentation](./superset/README.md) - -### Metabase - -Lightweight business intelligence: - -- **Simple Setup**: Quick configuration with clean, modern UI -- **Multiple Databases**: Connect to PostgreSQL, Trino, and more -- **Keycloak Authentication**: OAuth2 integration for user management - -[📖 See Metabase Documentation](./metabase/README.md) - -### Querybook - -Big data querying UI with notebook interface: - -- **Trino Integration**: SQL queries against multiple data sources with user impersonation -- **Notebook Interface**: Shareable datadocs with queries and visualizations -- **Real-time Execution**: WebSocket-based query progress updates - -[📖 See Querybook Documentation](./querybook/README.md) - ### Trino Fast distributed SQL query engine: @@ -259,15 +213,15 @@ Fast distributed SQL query engine: [📖 See Trino Documentation](./trino/README.md) -### DataHub +### Querybook -Modern data catalog and metadata management: +Big data querying UI with notebook interface: -- **OIDC Integration**: Keycloak authentication for unified access -- **Metadata Discovery**: Search and browse data assets across platforms -- **Lineage Tracking**: Visualize data flow and dependencies +- **Trino Integration**: SQL queries against multiple data sources with user impersonation +- **Notebook Interface**: Shareable datadocs with queries and visualizations +- **Real-time Execution**: WebSocket-based query progress updates -[📖 See DataHub Documentation](./datahub/README.md) +[📖 See Querybook Documentation](./querybook/README.md) ### ClickHouse @@ -309,15 +263,88 @@ Apache Iceberg REST Catalog: [📖 See Lakekeeper Documentation](./lakekeeper/README.md) -### Apache Airflow +### Apache Superset -Workflow orchestration platform: +Modern business intelligence platform: -- **DAG-Based**: Define data pipelines as code with Python -- **JupyterHub Integration**: Develop and test workflows in notebooks -- **Keycloak Authentication**: OAuth2 for user management +- **Rich Visualizations**: 40+ chart types including mixed charts, treemaps, and heatmaps +- **SQL Lab**: Powerful editor for complex queries and dataset creation +- **Keycloak & Trino**: OAuth2 authentication and Iceberg data lake integration -[📖 See Airflow Documentation](./airflow/README.md) +[📖 See Superset Documentation](./superset/README.md) + +### Metabase + +Lightweight business intelligence: + +- **Simple Setup**: Quick configuration with clean, modern UI +- **Multiple Databases**: Connect to PostgreSQL, Trino, and more +- **Keycloak Authentication**: OAuth2 integration for user management + +[📖 See Metabase Documentation](./metabase/README.md) + +### DataHub + +Modern data catalog and metadata management: + +- **OIDC Integration**: Keycloak authentication for unified access +- **Metadata Discovery**: Search and browse data assets across platforms +- **Lineage Tracking**: Visualize data flow and dependencies + +[📖 See DataHub Documentation](./datahub/README.md) + +### MLflow + +Machine learning lifecycle management platform: + +- **Experiment Tracking**: Log parameters, metrics, and artifacts for ML experiments +- **Model Registry**: Version and manage ML models with deployment lifecycle +- **Keycloak Authentication**: OAuth2 integration with group-based access control + +[📖 See MLflow Documentation](./mlflow/README.md) + +### KServe + +Model serving platform for deploying ML models on Kubernetes: + +- **Multi-Framework Support**: TensorFlow, PyTorch, scikit-learn, XGBoost, MLflow, and more +- **MLflow Integration**: Deploy models directly from MLflow Model Registry +- **Inference Protocols**: REST and gRPC with v2 Open Inference Protocol +- **RawDeployment Mode**: Uses native Kubernetes Deployments without Knative dependency + +[📖 See KServe Documentation](./kserve/README.md) + +### Ollama + +Local LLM inference server: + +- **Local Inference**: Run LLMs locally without external API dependencies +- **GPU Acceleration**: NVIDIA GPU support with automatic runtime configuration +- **Model Library**: Access to thousands of open-source models (Llama, Qwen, DeepSeek, etc.) +- **OpenAI-Compatible API**: Drop-in replacement for OpenAI API endpoints + +[📖 See Ollama Documentation](./ollama/README.md) + +### LibreChat + +Web-based chat interface for LLMs: + +- **Multi-Model Support**: Connect to Ollama, OpenAI, Anthropic, and custom endpoints +- **MCP Integration**: Model Context Protocol support for web search and tools +- **Keycloak Authentication**: OAuth2 integration for user management + +[📖 See LibreChat Documentation](./librechat/README.md) + +### Langfuse + +LLM observability and analytics platform: + +- **Trace Tracking**: Monitor LLM calls, chains, and agent executions with detailed traces +- **Prompt Management**: Version and test prompts with playground interface +- **Analytics**: Track costs, latency, and token usage across all LLM applications +- **Keycloak Authentication**: OAuth2 integration with automatic user provisioning + +[📖 See Langfuse Documentation](./langfuse/README.md) ### Dagster @@ -329,6 +356,16 @@ Modern data orchestration platform: [📖 See Dagster Documentation](./dagster/README.md) +### Apache Airflow + +Workflow orchestration platform: + +- **DAG-Based**: Define data pipelines as code with Python +- **JupyterHub Integration**: Develop and test workflows in notebooks +- **Keycloak Authentication**: OAuth2 for user management + +[📖 See Airflow Documentation](./airflow/README.md) + ### Fairwinds Polaris Kubernetes configuration validation and best practices auditing: @@ -340,20 +377,6 @@ Kubernetes configuration validation and best practices auditing: [📖 See Fairwinds Polaris Documentation](./fairwinds-polaris/README.md) -### Goldilocks - -Resource recommendation dashboard for right-sizing workloads: - -- **VPA Integration**: Powered by Vertical Pod Autoscaler for metrics-based recommendations -- **Visual Dashboard**: User-friendly interface for viewing resource recommendations -- **QoS Guidance**: Recommendations for Guaranteed, Burstable, and BestEffort classes -- **Monitoring-Only Mode**: Observes workloads without automatic scaling -- **Namespace-Based**: Enable monitoring per namespace with labels - -[📖 See Goldilocks Documentation](./goldilocks/README.md) - -[📖 See VPA Documentation](./vpa/README.md) - ## Common Operations ### User Management @@ -462,6 +485,7 @@ kubectl --context yourpc-oidc get nodes # JupyterHub: https://jupyter.yourdomain.com # MLflow: https://mlflow.yourdomain.com # Langfuse: https://langfuse.yourdomain.com +# LibreChat: https://chat.yourdomain.com ``` ## Customization diff --git a/justfile b/justfile index a23add4..38509c5 100644 --- a/justfile +++ b/justfile @@ -22,6 +22,7 @@ mod k8s mod kserve mod langfuse mod lakekeeper +mod librechat mod longhorn mod metabase mod mlflow diff --git a/librechat/.gitignore b/librechat/.gitignore new file mode 100644 index 0000000..6b3e85b --- /dev/null +++ b/librechat/.gitignore @@ -0,0 +1,2 @@ +values.yaml +librechat-config.yaml diff --git a/librechat/README.md b/librechat/README.md new file mode 100644 index 0000000..28539c4 --- /dev/null +++ b/librechat/README.md @@ -0,0 +1,227 @@ +# LibreChat + +Web-based chat interface for interacting with LLMs: + +- **Multi-Model Support**: Connect to Ollama, OpenAI, Anthropic, and custom endpoints +- **MCP Integration**: Model Context Protocol for web search and external tools +- **Keycloak Authentication**: OAuth2/OIDC integration for user management +- **Conversation History**: MongoDB-backed chat history with search via Meilisearch +- **Persistent Storage**: User-uploaded images stored persistently + +## Prerequisites + +- [Keycloak](../keycloak/README.md) for OIDC authentication +- [Vault](../vault/README.md) for secrets management +- [Ollama](../ollama/README.md) for local LLM inference (optional) + +## Installation + +```bash +just librechat::install +``` + +During installation, you will be prompted for: + +- **LibreChat host**: FQDN for LibreChat (e.g., `chat.example.com`) +- **Keycloak host**: FQDN for Keycloak (e.g., `auth.example.com`) +- **Tavily MCP**: Enable web search via Tavily API (requires API key) + +### Environment Variables + +| Variable | Default | Description | +| -------- | ------- | ----------- | +| `LIBRECHAT_NAMESPACE` | `librechat` | Kubernetes namespace | +| `LIBRECHAT_CHART_VERSION` | `1.9.3` | Helm chart version | +| `LIBRECHAT_HOST` | (prompt) | LibreChat FQDN | +| `LIBRECHAT_OIDC_CLIENT_ID` | `librechat` | Keycloak client ID | +| `KEYCLOAK_HOST` | (prompt) | Keycloak FQDN | +| `KEYCLOAK_REALM` | `buunstack` | Keycloak realm | +| `OLLAMA_HOST` | `ollama.ollama.svc.cluster.local` | Ollama service host | +| `TAVILY_MCP_ENABLED` | (prompt) | Enable Tavily MCP (`true`/`false`) | + +### Example with Environment Variables + +```bash +LIBRECHAT_HOST=chat.example.com \ + KEYCLOAK_HOST=auth.example.com \ + TAVILY_MCP_ENABLED=true \ + just librechat::install +``` + +## Ollama Integration + +LibreChat automatically connects to Ollama using the internal Kubernetes service +URL. The default models configured are: + +- `qwen3:8b` +- `deepseek-r1:8b` + +LibreChat fetches the available model list from Ollama, so any models you pull +will be available. + +## MCP (Model Context Protocol) + +LibreChat supports MCP servers for extending model capabilities with external tools. + +### Tavily Web Search + +When `TAVILY_MCP_ENABLED=true`, LibreChat can search the web using Tavily API: + +1. Get a Tavily API key from [tavily.com](https://tavily.com/) +2. During installation, enter the API key when prompted (stored in Vault) +3. In the chat interface, select "tavily" from the MCP Servers dropdown +4. The model can now search the web to answer questions + +### Adding Custom MCP Servers + +Edit `librechat-config.gomplate.yaml` to add additional MCP servers: + +```yaml +mcpServers: + tavily: + command: npx + args: + - "-y" + - "tavily-mcp@latest" + env: + TAVILY_API_KEY: "${TAVILY_API_KEY}" + filesystem: + command: npx + args: + - "-y" + - "@anthropic/mcp-server-filesystem" + - "/app/data" +``` + +## Adding API Providers + +Edit `librechat-config.gomplate.yaml` to add OpenAI, Anthropic, or other providers: + +```yaml +endpoints: + openAI: + apiKey: "${OPENAI_API_KEY}" + models: + default: + - gpt-4o + - gpt-4o-mini + fetch: true + + anthropic: + apiKey: "${ANTHROPIC_API_KEY}" + models: + default: + - claude-sonnet-4-20250514 + - claude-3-5-haiku-20241022 +``` + +Store the API keys in Kubernetes secrets and reference them in `values.gomplate.yaml`. + +## Operations + +### Check Status + +```bash +just librechat::status +``` + +### View Logs + +```bash +just librechat::logs +``` + +### Restart + +```bash +just librechat::restart +``` + +## Upgrade + +```bash +just librechat::upgrade +``` + +## Uninstall + +```bash +just librechat::uninstall +``` + +This removes the Helm release, namespace, and Keycloak client. Vault secrets +are preserved. + +To delete Vault secrets: + +```bash +just vault::delete librechat/credentials +``` + +## Architecture + +LibreChat deployment includes: + +- **LibreChat**: Main application (Node.js) +- **MongoDB**: Conversation and user data storage +- **Meilisearch**: Full-text search for conversations + +All components run with Pod Security Standards set to `restricted`. + +## Troubleshooting + +### OIDC Login Fails + +**Symptom**: Redirect loop or error after Keycloak login + +**Check**: + +1. Verify `DOMAIN_CLIENT` and `DOMAIN_SERVER` match your LibreChat URL +2. Check Keycloak client redirect URI matches `https:///oauth/openid/callback` + +```bash +just keycloak::get-client buunstack librechat +``` + +### Ollama Models Not Showing + +**Symptom**: No models available in the model selector + +**Check**: + +1. Verify Ollama is running: `just ollama::status` +2. Check Ollama has models: `just ollama::list` +3. Check LibreChat logs for connection errors: `just librechat::logs` + +### MCP Not Working + +**Symptom**: MCP server not available in dropdown + +**Check**: + +1. Verify Tavily secret exists: + + ```bash + kubectl get secret tavily-api-key -n librechat + ``` + +2. Check for MCP errors in logs: + + ```bash + just librechat::logs | grep -i mcp + ``` + +3. Verify `librechat-config` ConfigMap has MCP configuration: + + ```bash + kubectl get configmap librechat-config -n librechat -o yaml + ``` + +## References + +- [LibreChat Website](https://www.librechat.ai/) +- [LibreChat Documentation](https://www.librechat.ai/docs) +- [LibreChat GitHub](https://github.com/danny-avila/LibreChat) +- [LibreChat Helm Chart](https://github.com/danny-avila/librechat-helm) +- [Model Context Protocol](https://modelcontextprotocol.io/) +- [Tavily API](https://tavily.com/) diff --git a/librechat/justfile b/librechat/justfile new file mode 100644 index 0000000..766293b --- /dev/null +++ b/librechat/justfile @@ -0,0 +1,309 @@ +set fallback := true + +export LIBRECHAT_NAMESPACE := env("LIBRECHAT_NAMESPACE", "librechat") +export LIBRECHAT_CHART_VERSION := env("LIBRECHAT_CHART_VERSION", "1.9.3") +export LIBRECHAT_HOST := env("LIBRECHAT_HOST", "") +export LIBRECHAT_OIDC_CLIENT_ID := env("LIBRECHAT_OIDC_CLIENT_ID", "librechat") +export KEYCLOAK_REALM := env("KEYCLOAK_REALM", "buunstack") +export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "") +export K8S_VAULT_NAMESPACE := env("K8S_VAULT_NAMESPACE", "vault") +export EXTERNAL_SECRETS_NAMESPACE := env("EXTERNAL_SECRETS_NAMESPACE", "external-secrets") +export OLLAMA_NAMESPACE := env("OLLAMA_NAMESPACE", "ollama") +export OLLAMA_HOST := env("OLLAMA_HOST", "ollama.ollama.svc.cluster.local") +export TAVILY_MCP_ENABLED := env("TAVILY_MCP_ENABLED", "") + +[private] +default: + @just --list --unsorted --list-submodules + +# Create LibreChat namespace +create-namespace: + #!/bin/bash + set -euo pipefail + if ! kubectl get namespace ${LIBRECHAT_NAMESPACE} &>/dev/null; then + kubectl create namespace ${LIBRECHAT_NAMESPACE} + fi + kubectl label namespace ${LIBRECHAT_NAMESPACE} \ + pod-security.kubernetes.io/enforce=restricted \ + pod-security.kubernetes.io/enforce-version=latest \ + pod-security.kubernetes.io/warn=restricted \ + pod-security.kubernetes.io/warn-version=latest \ + --overwrite + +# Delete LibreChat namespace +delete-namespace: + kubectl delete namespace ${LIBRECHAT_NAMESPACE} --ignore-not-found + +# Generate credentials and store in Vault +generate-credentials: + #!/bin/bash + set -euo pipefail + if just vault::exist librechat/credentials &>/dev/null; then + echo "LibreChat credentials already exist in Vault" + exit 0 + fi + echo "Generating LibreChat credentials..." + CREDS_KEY=$(openssl rand -hex 32) + CREDS_IV=$(openssl rand -hex 16) + JWT_SECRET=$(openssl rand -hex 32) + JWT_REFRESH_SECRET=$(openssl rand -hex 32) + MEILI_MASTER_KEY=$(openssl rand -hex 32) + OPENID_SESSION_SECRET=$(openssl rand -hex 32) + + just vault::put librechat/credentials \ + creds_key="${CREDS_KEY}" \ + creds_iv="${CREDS_IV}" \ + jwt_secret="${JWT_SECRET}" \ + jwt_refresh_secret="${JWT_REFRESH_SECRET}" \ + meili_master_key="${MEILI_MASTER_KEY}" \ + openid_session_secret="${OPENID_SESSION_SECRET}" + echo "Credentials stored in Vault" + +# Create Keycloak client for LibreChat +create-keycloak-client: + #!/bin/bash + set -euo pipefail + while [ -z "${LIBRECHAT_HOST}" ]; do + LIBRECHAT_HOST=$( + gum input --prompt="LibreChat host (FQDN): " --width=100 \ + --placeholder="e.g., chat.example.com" + ) + done + + echo "Creating Keycloak client for LibreChat..." + + just keycloak::delete-client ${KEYCLOAK_REALM} ${LIBRECHAT_OIDC_CLIENT_ID} || true + + CLIENT_SECRET=$(just utils::random-password) + + just keycloak::create-client \ + realm=${KEYCLOAK_REALM} \ + client_id=${LIBRECHAT_OIDC_CLIENT_ID} \ + redirect_url="https://${LIBRECHAT_HOST}/oauth/openid/callback" \ + client_secret="${CLIENT_SECRET}" + + just vault::put keycloak/client/librechat \ + client_id="${LIBRECHAT_OIDC_CLIENT_ID}" \ + client_secret="${CLIENT_SECRET}" + + echo "Keycloak client created successfully" + echo "Client ID: ${LIBRECHAT_OIDC_CLIENT_ID}" + echo "Redirect URI: https://${LIBRECHAT_HOST}/oauth/openid/callback" + +# Delete Keycloak client +delete-keycloak-client: + #!/bin/bash + set -euo pipefail + echo "Deleting Keycloak client for LibreChat..." + just keycloak::delete-client ${KEYCLOAK_REALM} ${LIBRECHAT_OIDC_CLIENT_ID} || true + if just vault::exist keycloak/client/librechat &>/dev/null; then + just vault::delete keycloak/client/librechat + fi + +# Create Kubernetes secrets +create-secrets: + #!/bin/bash + set -euo pipefail + just create-namespace + + CREDS_KEY=$(just vault::get librechat/credentials creds_key) + CREDS_IV=$(just vault::get librechat/credentials creds_iv) + JWT_SECRET=$(just vault::get librechat/credentials jwt_secret) + JWT_REFRESH_SECRET=$(just vault::get librechat/credentials jwt_refresh_secret) + MEILI_MASTER_KEY=$(just vault::get librechat/credentials meili_master_key) + OPENID_SESSION_SECRET=$(just vault::get librechat/credentials openid_session_secret) + OPENID_CLIENT_ID=$(just vault::get keycloak/client/librechat client_id) + OPENID_CLIENT_SECRET=$(just vault::get keycloak/client/librechat client_secret) + + kubectl delete secret librechat-credentials-env -n ${LIBRECHAT_NAMESPACE} --ignore-not-found + kubectl create secret generic librechat-credentials-env -n ${LIBRECHAT_NAMESPACE} \ + --from-literal=CREDS_KEY="${CREDS_KEY}" \ + --from-literal=CREDS_IV="${CREDS_IV}" \ + --from-literal=JWT_SECRET="${JWT_SECRET}" \ + --from-literal=JWT_REFRESH_SECRET="${JWT_REFRESH_SECRET}" \ + --from-literal=MEILI_MASTER_KEY="${MEILI_MASTER_KEY}" \ + --from-literal=OPENID_SESSION_SECRET="${OPENID_SESSION_SECRET}" \ + --from-literal=OPENID_CLIENT_ID="${OPENID_CLIENT_ID}" \ + --from-literal=OPENID_CLIENT_SECRET="${OPENID_CLIENT_SECRET}" + + echo "Secrets created successfully" + +# Install LibreChat +install: + #!/bin/bash + set -euo pipefail + + while [ -z "${LIBRECHAT_HOST}" ]; do + LIBRECHAT_HOST=$( + gum input --prompt="LibreChat host (FQDN): " --width=100 \ + --placeholder="e.g., chat.example.com" + ) + done + + while [ -z "${KEYCLOAK_HOST}" ]; do + KEYCLOAK_HOST=$( + gum input --prompt="Keycloak host (FQDN): " --width=100 \ + --placeholder="e.g., auth.example.com" + ) + done + + # Ask about Tavily MCP if not set + if [ -z "${TAVILY_MCP_ENABLED}" ]; then + if gum confirm "Enable Tavily MCP for web search?"; then + TAVILY_MCP_ENABLED="true" + else + TAVILY_MCP_ENABLED="false" + fi + fi + + # Check External Secrets Operator if Tavily is enabled + if [ "${TAVILY_MCP_ENABLED}" = "true" ]; then + if ! helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then + echo "Error: Tavily MCP requires External Secrets Operator, but it is not installed." + echo "Please install External Secrets Operator first or set TAVILY_MCP_ENABLED=false" + exit 1 + fi + # Check if Tavily API key exists in Vault + if ! just vault::exist tavily/api &>/dev/null; then + echo "Tavily API key not found in Vault." + TAVILY_API_KEY=$( + gum input --prompt="Enter Tavily API Key: " --width=100 \ + --placeholder="tvly-xxxxxxxxxxxxxxxx" + ) + just vault::put tavily/api api_key="${TAVILY_API_KEY}" + echo "Tavily API key stored in Vault" + fi + fi + + just create-namespace + just generate-credentials + just create-keycloak-client + just create-secrets + + # Create Tavily ExternalSecret if enabled + if [ "${TAVILY_MCP_ENABLED}" = "true" ]; then + gomplate -f tavily-external-secret.gomplate.yaml | kubectl apply -f - + echo "Waiting for Tavily secret to be synced..." + kubectl wait --for=condition=Ready externalsecret/tavily-api-key \ + -n ${LIBRECHAT_NAMESPACE} --timeout=60s + fi + + gomplate -f values.gomplate.yaml -o values.yaml + gomplate -f librechat-config.gomplate.yaml -o librechat-config.yaml + + kubectl delete configmap librechat-config -n ${LIBRECHAT_NAMESPACE} --ignore-not-found + kubectl create configmap librechat-config -n ${LIBRECHAT_NAMESPACE} \ + --from-file=librechat.yaml=librechat-config.yaml + + helm upgrade --install librechat oci://ghcr.io/danny-avila/librechat-chart/librechat \ + --version ${LIBRECHAT_CHART_VERSION} \ + -n ${LIBRECHAT_NAMESPACE} \ + --wait --timeout=10m \ + -f values.yaml + + echo "" + echo "LibreChat installed successfully" + echo "URL: https://${LIBRECHAT_HOST}" + echo "Login with Keycloak OIDC" + if [ "${TAVILY_MCP_ENABLED}" = "true" ]; then + echo "Tavily MCP: Enabled" + fi + +# Upgrade LibreChat +upgrade: + #!/bin/bash + set -euo pipefail + + while [ -z "${LIBRECHAT_HOST}" ]; do + LIBRECHAT_HOST=$( + gum input --prompt="LibreChat host (FQDN): " --width=100 \ + --placeholder="e.g., chat.example.com" + ) + done + + while [ -z "${KEYCLOAK_HOST}" ]; do + KEYCLOAK_HOST=$( + gum input --prompt="Keycloak host (FQDN): " --width=100 \ + --placeholder="e.g., auth.example.com" + ) + done + + # Ask about Tavily MCP if not set + if [ -z "${TAVILY_MCP_ENABLED}" ]; then + if gum confirm "Enable Tavily MCP for web search?"; then + TAVILY_MCP_ENABLED="true" + else + TAVILY_MCP_ENABLED="false" + fi + fi + + # Check External Secrets Operator if Tavily is enabled + if [ "${TAVILY_MCP_ENABLED}" = "true" ]; then + if ! helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then + echo "Error: Tavily MCP requires External Secrets Operator, but it is not installed." + echo "Please install External Secrets Operator first or set TAVILY_MCP_ENABLED=false" + exit 1 + fi + # Check if Tavily API key exists in Vault + if ! just vault::exist tavily/api &>/dev/null; then + echo "Tavily API key not found in Vault." + TAVILY_API_KEY=$( + gum input --prompt="Enter Tavily API Key: " --width=100 \ + --placeholder="tvly-xxxxxxxxxxxxxxxx" + ) + just vault::put tavily/api api_key="${TAVILY_API_KEY}" + echo "Tavily API key stored in Vault" + fi + # Create/update Tavily ExternalSecret + gomplate -f tavily-external-secret.gomplate.yaml | kubectl apply -f - + echo "Waiting for Tavily secret to be synced..." + kubectl wait --for=condition=Ready externalsecret/tavily-api-key \ + -n ${LIBRECHAT_NAMESPACE} --timeout=60s + fi + + gomplate -f values.gomplate.yaml -o values.yaml + gomplate -f librechat-config.gomplate.yaml -o librechat-config.yaml + + kubectl delete configmap librechat-config -n ${LIBRECHAT_NAMESPACE} --ignore-not-found + kubectl create configmap librechat-config -n ${LIBRECHAT_NAMESPACE} \ + --from-file=librechat.yaml=librechat-config.yaml + + helm upgrade librechat oci://ghcr.io/danny-avila/librechat-chart/librechat \ + --version ${LIBRECHAT_CHART_VERSION} \ + -n ${LIBRECHAT_NAMESPACE} \ + --wait --timeout=10m \ + -f values.yaml + + echo "LibreChat upgraded successfully" + if [ "${TAVILY_MCP_ENABLED}" = "true" ]; then + echo "Tavily MCP: Enabled" + fi + +# Uninstall LibreChat +uninstall: + #!/bin/bash + set -euo pipefail + helm uninstall librechat -n ${LIBRECHAT_NAMESPACE} --wait --ignore-not-found + just delete-namespace + + just delete-keycloak-client || true + + echo "LibreChat uninstalled successfully" + echo "" + echo "Note: Vault secrets were NOT deleted:" + echo " - librechat/credentials" + echo "" + echo "To delete, run:" + echo " just vault::delete librechat/credentials" + +# Show LibreChat logs +logs: + kubectl logs -n ${LIBRECHAT_NAMESPACE} -l app.kubernetes.io/name=librechat -f + +# Get pod status +status: + kubectl get pods -n ${LIBRECHAT_NAMESPACE} + +# Restart LibreChat +restart: + kubectl rollout restart deployment/librechat-librechat -n ${LIBRECHAT_NAMESPACE} diff --git a/librechat/librechat-config.gomplate.yaml b/librechat/librechat-config.gomplate.yaml new file mode 100644 index 0000000..df2c266 --- /dev/null +++ b/librechat/librechat-config.gomplate.yaml @@ -0,0 +1,89 @@ +version: 1.2.1 + +cache: true + +interface: + endpointsMenu: true + modelSelect: true + parameters: true + sidePanel: true + presets: true + +registration: + socialLogins: + - openid + +endpoints: + # Ollama - Local LLM (configured as custom endpoint) + # Note: Name must NOT start with "ollama" to avoid legacy code issues + custom: + - name: "LocalLLM" + apiKey: "ollama" + baseURL: "http://{{ .Env.OLLAMA_HOST }}:11434/v1/" + models: + default: + - "qwen3:8b" + - "deepseek-r1:8b" + fetch: true + titleConvo: true + titleModel: "current_model" + summarize: false + summaryModel: "current_model" + forcePrompt: false + modelDisplayLabel: "LocalLLM" + + # OpenAI - Optional, requires API key + # openAI: + # apiKey: "${OPENAI_API_KEY}" + # models: + # default: + # - gpt-4o + # - gpt-4o-mini + # fetch: true + + # Anthropic - Optional, requires API key + # anthropic: + # apiKey: "${ANTHROPIC_API_KEY}" + # models: + # default: + # - claude-sonnet-4-20250514 + # - claude-3-5-haiku-20241022 + + # Additional custom endpoints example (OpenRouter, etc.) + # - name: "OpenRouter" + # apiKey: "${OPENROUTER_KEY}" + # baseURL: "https://openrouter.ai/api/v1" + # models: + # default: + # - "anthropic/claude-sonnet-4" + # fetch: true + # titleConvo: true + # modelDisplayLabel: "OpenRouter" + +# MCP Servers configuration +{{- if eq .Env.TAVILY_MCP_ENABLED "true" }} +mcpServers: + tavily: + command: npx + args: + - "-y" + - "tavily-mcp@latest" + env: + TAVILY_API_KEY: "${TAVILY_API_KEY}" +{{- end }} + +# Additional MCP Servers (examples) +# mcpServers: +# filesystem: +# command: npx +# args: +# - "-y" +# - "@anthropic/mcp-server-filesystem" +# - "/app/data" +# brave-search: +# command: npx +# args: +# - "-y" +# - "@anthropic/mcp-server-brave-search" +# env: +# BRAVE_API_KEY: "${BRAVE_API_KEY}" diff --git a/librechat/tavily-external-secret.gomplate.yaml b/librechat/tavily-external-secret.gomplate.yaml new file mode 100644 index 0000000..d4c914b --- /dev/null +++ b/librechat/tavily-external-secret.gomplate.yaml @@ -0,0 +1,22 @@ +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: tavily-api-key + namespace: {{ .Env.LIBRECHAT_NAMESPACE }} +spec: + refreshInterval: 1h + secretStoreRef: + name: vault-secret-store + kind: ClusterSecretStore + target: + name: tavily-api-key + creationPolicy: Owner + template: + type: Opaque + data: + TAVILY_API_KEY: "{{ `{{ .api_key }}` }}" + data: + - secretKey: api_key + remoteRef: + key: tavily/api + property: api_key diff --git a/librechat/values.gomplate.yaml b/librechat/values.gomplate.yaml new file mode 100644 index 0000000..d704e9f --- /dev/null +++ b/librechat/values.gomplate.yaml @@ -0,0 +1,150 @@ +replicaCount: 1 + +global: + librechat: + existingSecretName: "librechat-credentials-env" + existingSecretApiKey: OPENAI_API_KEY +{{- if eq .Env.TAVILY_MCP_ENABLED "true" }} + env: + - name: TAVILY_API_KEY + valueFrom: + secretKeyRef: + name: tavily-api-key + key: TAVILY_API_KEY +{{- end }} + +librechat: + configEnv: + # Domain configuration (required for OIDC redirects) + DOMAIN_CLIENT: "https://{{ .Env.LIBRECHAT_HOST }}" + DOMAIN_SERVER: "https://{{ .Env.LIBRECHAT_HOST }}" + + # Ollama endpoint (internal k8s service) + OLLAMA_BASE_URL: "http://{{ .Env.OLLAMA_HOST }}:11434" + + # OpenID Connect / Keycloak + ALLOW_SOCIAL_LOGIN: "true" + OPENID_BUTTON_LABEL: "Login with Keycloak" + OPENID_ISSUER: "https://{{ .Env.KEYCLOAK_HOST }}/realms/{{ .Env.KEYCLOAK_REALM }}" + OPENID_CALLBACK_URL: "/oauth/openid/callback" + OPENID_SCOPE: "openid profile email" + + # Optional: Role-based access control + # OPENID_REQUIRED_ROLE_PARAMETER_PATH: "realm_access.roles" + # OPENID_REQUIRED_ROLE_TOKEN_KIND: "access" + + # Optional: Group sync from Keycloak roles + # OPENID_SYNC_GROUPS_FROM_TOKEN: "true" + # OPENID_GROUPS_CLAIM_PATH: "realm_access.roles" + # OPENID_GROUPS_TOKEN_KIND: "access" + + # Disable email registration (use Keycloak only) + ALLOW_EMAIL_LOGIN: "false" + ALLOW_REGISTRATION: "false" + ALLOW_SOCIAL_REGISTRATION: "true" + + # Debug (set to true for troubleshooting) + DEBUG_OPENID_REQUESTS: "false" + DEBUG_PLUGINS: "false" + + existingSecretName: "librechat-credentials-env" + + # Use external configmap for librechat.yaml + existingConfigYaml: "librechat-config" + + imageVolume: + enabled: true + size: 10Gi + accessModes: ReadWriteOnce + +image: + repository: danny-avila/librechat + registry: ghcr.io + pullPolicy: IfNotPresent + +podSecurityContext: + fsGroup: 2000 + +securityContext: + capabilities: + drop: + - ALL + runAsNonRoot: true + runAsUser: 1000 + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + +service: + type: ClusterIP + port: 3080 + +ingress: + enabled: true + className: "traefik" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + hosts: + - host: {{ .Env.LIBRECHAT_HOST }} + paths: + - path: / + pathType: Prefix + tls: [] + +resources: + requests: + cpu: 100m + memory: 512Mi + limits: + cpu: 1000m + memory: 1Gi + +mongodb: + enabled: true + auth: + enabled: false + databases: + - LibreChat + image: + tag: "latest" + persistence: + size: 8Gi + podSecurityContext: + fsGroup: 1001 + seccompProfile: + type: RuntimeDefault + containerSecurityContext: + runAsUser: 1001 + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL + +meilisearch: + enabled: true + persistence: + enabled: true + image: + tag: "v1.7.3" + auth: + existingMasterKeySecret: "librechat-credentials-env" + podSecurityContext: + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + containerSecurityContext: + runAsUser: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL + +librechat-rag-api: + enabled: false