310 lines
11 KiB
Makefile
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}
|