Files
buun-stack/librechat/justfile
2025-12-03 16:09:24 +09:00

310 lines
11 KiB
Makefile

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}