336 lines
9.6 KiB
Bash
Executable File
336 lines
9.6 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Yuxi Initialization Script for Bash/Linux/macOS
|
|
# This script helps set up the environment for the Yuxi project
|
|
|
|
set -e
|
|
umask 077
|
|
|
|
generate_hex() {
|
|
local length="$1"
|
|
if command -v openssl >/dev/null 2>&1; then
|
|
openssl rand -hex "$length"
|
|
else
|
|
tr -dc 'a-f0-9' < /dev/urandom | head -c $((length * 2))
|
|
fi
|
|
}
|
|
|
|
set_env_value() {
|
|
local name="$1"
|
|
local value="$2"
|
|
|
|
if grep -Eq "^${name}=" .env; then
|
|
ENV_VALUE="$value" awk -v name="$name" '
|
|
$0 ~ "^" name "=" {
|
|
if (!updated) {
|
|
print name "=" ENVIRON["ENV_VALUE"]
|
|
updated = 1
|
|
}
|
|
next
|
|
}
|
|
{ print }
|
|
' .env > .env.tmp
|
|
mv .env.tmp .env
|
|
else
|
|
printf '\n%s=%s\n' "$name" "$value" >> .env
|
|
fi
|
|
}
|
|
|
|
get_env_value() {
|
|
local name="$1"
|
|
awk -v name="$name" '
|
|
index($0, name "=") == 1 && !found {
|
|
print substr($0, length(name) + 2)
|
|
found = 1
|
|
}
|
|
' .env
|
|
}
|
|
|
|
trim_whitespace() {
|
|
local value="$1"
|
|
value="${value#"${value%%[![:space:]]*}"}"
|
|
value="${value%"${value##*[![:space:]]}"}"
|
|
printf '%s' "$value"
|
|
}
|
|
|
|
security_secret_is_valid() {
|
|
local value="$1"
|
|
shift
|
|
local trimmed
|
|
trimmed=$(trim_whitespace "$value")
|
|
if [ "$value" != "$trimmed" ] || [ "${#value}" -lt 32 ]; then
|
|
return 1
|
|
fi
|
|
case "$value" in
|
|
\"*|*\"|\'*|*\') return 1 ;;
|
|
esac
|
|
local other
|
|
for other in "$@"; do
|
|
if [ -n "$other" ] && [ "$value" = "$other" ]; then
|
|
return 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
read_security_secret() {
|
|
local name="$1"
|
|
shift
|
|
local value
|
|
while true; do
|
|
read -s -p "Please enter your ${name} (press Enter to auto-generate): " value
|
|
echo ""
|
|
if [ -z "$value" ]; then
|
|
value=$(generate_hex 32)
|
|
echo "Generated ${name} and saved it to .env."
|
|
fi
|
|
if security_secret_is_valid "$value" "$@"; then
|
|
SECURITY_SECRET_VALUE="$value"
|
|
return
|
|
fi
|
|
echo "❌ ${name} must contain at least 32 non-whitespace characters and must not reuse another security secret."
|
|
done
|
|
}
|
|
|
|
ensure_security_secret() {
|
|
local name="$1"
|
|
shift
|
|
local current
|
|
current=$(get_env_value "$name")
|
|
local other_values=()
|
|
local other_name
|
|
for other_name in "$@"; do
|
|
other_values+=("$(get_env_value "$other_name")")
|
|
done
|
|
if security_secret_is_valid "$current" "${other_values[@]}"; then
|
|
return
|
|
fi
|
|
|
|
echo "${name} is missing, too short, or reuses another security secret in .env."
|
|
read_security_secret "$name" "${other_values[@]}"
|
|
set_env_value "$name" "$SECURITY_SECRET_VALUE"
|
|
}
|
|
|
|
validate_security_env() {
|
|
local jwt_secret api_key_secret sandbox_secret
|
|
jwt_secret=$(get_env_value "JWT_SECRET_KEY")
|
|
api_key_secret=$(get_env_value "API_KEY_DERIVATION_SECRET")
|
|
sandbox_secret=$(get_env_value "SANDBOX_PROVISIONER_TOKEN")
|
|
|
|
security_secret_is_valid "$jwt_secret" || {
|
|
echo "JWT_SECRET_KEY must contain at least 32 non-whitespace characters." >&2
|
|
return 1
|
|
}
|
|
security_secret_is_valid "$api_key_secret" "$jwt_secret" || {
|
|
echo "API_KEY_DERIVATION_SECRET must be at least 32 characters and independent from JWT_SECRET_KEY." >&2
|
|
return 1
|
|
}
|
|
security_secret_is_valid "$sandbox_secret" "$jwt_secret" "$api_key_secret" || {
|
|
echo "SANDBOX_PROVISIONER_TOKEN must be at least 32 characters and independent from other security secrets." >&2
|
|
return 1
|
|
}
|
|
}
|
|
|
|
ensure_required_api_env() {
|
|
if grep -Eq '^SILICONFLOW_API_KEY=.+' .env; then
|
|
return
|
|
fi
|
|
|
|
echo "SILICONFLOW_API_KEY is missing in .env."
|
|
while true; do
|
|
read -s -p "Please enter your SILICONFLOW_API_KEY: " SILICONFLOW_API_KEY
|
|
echo ""
|
|
if [ -n "$SILICONFLOW_API_KEY" ]; then
|
|
break
|
|
fi
|
|
echo "❌ API Key cannot be empty. Please try again."
|
|
done
|
|
set_env_value "SILICONFLOW_API_KEY" "$SILICONFLOW_API_KEY"
|
|
}
|
|
|
|
ensure_jwt_env() {
|
|
ensure_security_secret "JWT_SECRET_KEY"
|
|
ensure_security_secret "API_KEY_DERIVATION_SECRET" "JWT_SECRET_KEY"
|
|
|
|
if ! grep -Eq '^YUXI_INSTANCE_ID=.+' .env; then
|
|
echo "YUXI_INSTANCE_ID is missing in .env."
|
|
read -p "Please enter your YUXI_INSTANCE_ID (press Enter to auto-generate): " YUXI_INSTANCE_ID
|
|
if [ -z "$YUXI_INSTANCE_ID" ]; then
|
|
YUXI_INSTANCE_ID="instance-$(generate_hex 8)"
|
|
echo "Generated YUXI_INSTANCE_ID and saved it to .env."
|
|
fi
|
|
|
|
set_env_value "YUXI_INSTANCE_ID" "$YUXI_INSTANCE_ID"
|
|
fi
|
|
}
|
|
|
|
ensure_sandbox_env() {
|
|
ensure_security_secret "SANDBOX_PROVISIONER_TOKEN" "JWT_SECRET_KEY" "API_KEY_DERIVATION_SECRET"
|
|
}
|
|
|
|
if [ "${1:-}" = "--validate-security-env" ]; then
|
|
if [ ! -f ".env" ]; then
|
|
echo ".env does not exist" >&2
|
|
exit 1
|
|
fi
|
|
validate_security_env
|
|
exit 0
|
|
fi
|
|
|
|
skip_existing_image() {
|
|
local image="$1"
|
|
|
|
if ! docker image inspect "$image" >/dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
|
|
echo "⏭️ ${image} already exists. Skipping pull."
|
|
return 0
|
|
}
|
|
|
|
echo "🚀 Initializing Yuxi project..."
|
|
echo "=================================="
|
|
|
|
# Check if .env file exists
|
|
if [ -f ".env" ]; then
|
|
echo "✅ .env file already exists. Checking required settings."
|
|
ensure_required_api_env
|
|
ensure_jwt_env
|
|
ensure_sandbox_env
|
|
validate_security_env
|
|
chmod 600 .env
|
|
else
|
|
echo "📝 .env file not found. Let's set up your environment variables."
|
|
echo ""
|
|
|
|
# Get SILICONFLOW_API_KEY
|
|
echo "🔑 SiliconFlow API Key required"
|
|
echo "Get your API key from: https://cloud.siliconflow.cn/i/Eo5yTHGJ"
|
|
while true; do
|
|
read -s -p "Please enter your SILICONFLOW_API_KEY: " SILICONFLOW_API_KEY
|
|
echo ""
|
|
if [ -z "$SILICONFLOW_API_KEY" ]; then
|
|
echo "❌ API Key cannot be empty. Please try again."
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Get Web Search Provider and API Key (optional)
|
|
echo ""
|
|
echo "🔍 Web Search Provider (optional)"
|
|
echo "1) doubao (Doubao Custom Search)"
|
|
echo "2) tavily (Tavily Search)"
|
|
read -p "Please select web search provider (1 for doubao, 2 for tavily, press Enter to skip): " SEARCH_CHOICE
|
|
|
|
WEB_SEARCH_PROVIDER=""
|
|
DOUBAO_SEARCH_API_KEY=""
|
|
TAVILY_API_KEY=""
|
|
|
|
if [ "$SEARCH_CHOICE" = "1" ] || [ "$SEARCH_CHOICE" = "doubao" ]; then
|
|
WEB_SEARCH_PROVIDER="doubao"
|
|
echo "Get your Doubao API Key from Volcengine Console https://console.volcengine.com/search-infinity/api-key"
|
|
read -s -p "Please enter your DOUBAO_SEARCH_API_KEY: " DOUBAO_SEARCH_API_KEY
|
|
echo ""
|
|
elif [ "$SEARCH_CHOICE" = "2" ] || [ "$SEARCH_CHOICE" = "tavily" ]; then
|
|
WEB_SEARCH_PROVIDER="tavily"
|
|
echo "Get your Tavily API key from: https://app.tavily.com/"
|
|
read -s -p "Please enter your TAVILY_API_KEY: " TAVILY_API_KEY
|
|
echo ""
|
|
fi
|
|
|
|
echo ""
|
|
echo "JWT security settings"
|
|
read_security_secret "JWT_SECRET_KEY"
|
|
JWT_SECRET_KEY="$SECURITY_SECRET_VALUE"
|
|
|
|
read_security_secret "API_KEY_DERIVATION_SECRET" "$JWT_SECRET_KEY"
|
|
API_KEY_DERIVATION_SECRET="$SECURITY_SECRET_VALUE"
|
|
|
|
read -p "Please enter your YUXI_INSTANCE_ID (press Enter to auto-generate): " YUXI_INSTANCE_ID
|
|
if [ -z "$YUXI_INSTANCE_ID" ]; then
|
|
YUXI_INSTANCE_ID="instance-$(generate_hex 8)"
|
|
echo "Generated YUXI_INSTANCE_ID and saved it to .env."
|
|
fi
|
|
|
|
read_security_secret "SANDBOX_PROVISIONER_TOKEN" "$JWT_SECRET_KEY" "$API_KEY_DERIVATION_SECRET"
|
|
SANDBOX_PROVISIONER_TOKEN="$SECURITY_SECRET_VALUE"
|
|
|
|
# Create .env file
|
|
cat > .env << EOF
|
|
# SiliconFlow API Key (required)
|
|
SILICONFLOW_API_KEY=${SILICONFLOW_API_KEY}
|
|
|
|
# Web Search Provider settings
|
|
EOF
|
|
|
|
if [ -n "$WEB_SEARCH_PROVIDER" ]; then
|
|
echo "WEB_SEARCH_PROVIDER=${WEB_SEARCH_PROVIDER}" >> .env
|
|
fi
|
|
if [ -n "$DOUBAO_SEARCH_API_KEY" ]; then
|
|
echo "DOUBAO_SEARCH_API_KEY=${DOUBAO_SEARCH_API_KEY}" >> .env
|
|
fi
|
|
if [ -n "$TAVILY_API_KEY" ]; then
|
|
echo "TAVILY_API_KEY=${TAVILY_API_KEY}" >> .env
|
|
fi
|
|
|
|
cat >> .env << EOF
|
|
|
|
# JWT security settings
|
|
JWT_SECRET_KEY=${JWT_SECRET_KEY}
|
|
API_KEY_DERIVATION_SECRET=${API_KEY_DERIVATION_SECRET}
|
|
YUXI_INSTANCE_ID=${YUXI_INSTANCE_ID}
|
|
SANDBOX_PROVISIONER_TOKEN=${SANDBOX_PROVISIONER_TOKEN}
|
|
EOF
|
|
|
|
validate_security_env
|
|
chmod 600 .env
|
|
echo "✅ .env file created successfully!"
|
|
fi
|
|
|
|
echo ""
|
|
echo "📦 Pulling Docker images..."
|
|
echo "========================="
|
|
|
|
# List of Docker images to pull
|
|
images=(
|
|
"python:3.13-slim"
|
|
"node:24-slim"
|
|
"node:24-alpine"
|
|
"milvusdb/milvus:v2.5.6"
|
|
"neo4j:5.26.29"
|
|
"minio/minio:RELEASE.2023-03-20T20-16-18Z"
|
|
"ghcr.io/astral-sh/uv:0.11.26"
|
|
"nginx:alpine"
|
|
"quay.io/coreos/etcd:v3.5.5"
|
|
"postgres:16"
|
|
"redis:7.4.10-alpine"
|
|
)
|
|
|
|
# Pull each image
|
|
for image in "${images[@]}"; do
|
|
if skip_existing_image "$image"; then
|
|
continue
|
|
fi
|
|
|
|
echo "🔄 Pulling ${image}..."
|
|
if bash scripts/pull_image.sh "$image"; then
|
|
echo "✅ Successfully pulled ${image}"
|
|
else
|
|
echo "❌ Failed to pull ${image}"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
sandbox_image="enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest"
|
|
if ! skip_existing_image "$sandbox_image"; then
|
|
echo "🔄 Pulling ${sandbox_image}..."
|
|
docker pull "$sandbox_image"
|
|
echo "✅ Successfully pulled ${sandbox_image}"
|
|
fi
|
|
|
|
echo ""
|
|
echo "🎉 Initialization complete!"
|
|
echo "=========================="
|
|
echo "You can now run: docker compose up -d --build"
|
|
echo "This will start all services in development mode with hot-reload enabled."
|