Enterprise AI Workspace prototype: LobeChat (de-branded) + Enterprise Gateway + Postgres/Redis stack
Revalidate Docs / Revalidate Docs (push) Failing after 2s
E2E CI / Check Duplicate Run (push) Failing after 5s
Test CI / Check Duplicate Run (push) Failing after 6s
E2E CI / Test Web App (push) Has been skipped
Test CI / Test Packages (push) Has been skipped
Test CI / Test App (shard 1/3) (push) Has been skipped
Test CI / Test App (shard 2/3) (push) Has been skipped
Test CI / Test App (shard 3/3) (push) Has been skipped
Test CI / Test Desktop App (push) Has been skipped
🔄 Branch Synchronization / sync-branches (push) Failing after 11s
Test CI / Test Database (push) Has been skipped
Test CI / Merge and Upload App Coverage (push) Has been skipped
Database Schema Visualization CI / build (push) Failing after 4m14s

This commit is contained in:
xiaohei
2026-04-21 12:58:00 +08:00
commit 428644e286
9708 changed files with 2255756 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
# Proxy, if you need it
# HTTP_PROXY=http://localhost:7890
# HTTPS_PROXY=http://localhost:7890
# Allowed email addresses for login, separated by commas
# When set, only emails in the list can log in, other users cannot log in
# Leave empty to allow all users to register
# AUTH_ALLOWED_EMAILS=user1@example.com,user2@example.com
# Disable user registration (SSO-only mode)
# When set to 1, users cannot register via email/password, only SSO login is allowed
# AUTH_DISABLE_EMAIL_PASSWORD=1
# ===========================
# ====== Preset config ======
# ===========================
# if no special requirements, no need to change
LOBE_PORT=3210
RUSTFS_PORT=9000
RUSTFS_ADMIN_PORT=9001
APP_URL=http://localhost:3210
# INTERNAL_APP_URL is used for internal server-to-server communication.
# Required for Docker Compose deployments, otherwise features like
# AI image generation will fail when APP_URL is a host/LAN IP.
INTERNAL_APP_URL=http://localhost:3210
# Secrets (auto-generated by setup.sh)
KEY_VAULTS_SECRET=YOUR_KEY_VAULTS_SECRET
AUTH_SECRET=YOUR_AUTH_SECRET
# Postgres related, which are the necessary environment variables for DB
LOBE_DB_NAME=lobechat
POSTGRES_PASSWORD=uWNZugjBqixf8dxC
# RUSTFS S3 configuration
S3_ENDPOINT=http://localhost:9000
RUSTFS_ACCESS_KEY=admin
RUSTFS_SECRET_KEY=YOUR_RUSTFS_PASSWORD
# Configure the bucket information of RUSTFS
RUSTFS_LOBE_BUCKET=lobe
JWKS_KEY={"keys":[{"d":"PVoFyqyrGstB8wU52S7gqqQQdZLtin_thcEM0nrNtqp9U-NlKLlhgEcWp5t89ycgvhsAzmrRbezGj4JBTr3jn7eWdwQpPJNYiipnsgeJn0pwsB0H2dMqtavxinoPVXkMTOuGHMTFhhyguFBw2JbIL0PTQUcUlXjv40OoJpYHZeggSxgfV-TuxjwW8Ll4-n84M5IOi6A53RvioE-Hm1iyIc2XLBCfyOu-SbAQYi8HzrA64kCxobAB0peLQMiAzfZmwPKiGOhnhKrAlYmG02qFnbUYiJu_-AXwsAyGv9S9i6dwK7QXaGGWYyis8LlPpd_JmPrBnrWomwDlI045NUMWZQ","dp":"OSXI2NBBZl2r0Dpf4-1z44A_jC5lOyXtJhXQYnSXy5eIuxTJcEtkUYagGEwnREO4Q3t-4J-lT_6Y71M1ZlgKG1upwfw1O4aE3vGpHOik9iZYYCjA8fe5uBfOpX1ELmOtHNoHRhMtyjuPxSFXLlSp3bgcF1f3F40ClukdvXCx0Mc","dq":"m6hNdfj-F8E_7nUlX2nG95OffkFrhHTo67ML9aPgpvFwBlzg-hk5LwtxMfUzngqWF78TMl0JDm7vS1bz0xlWqXqu8pFPoTUnUoWgYfvuyHLBwR5TgccQkfoKbkSMzYNy8VJPXZeyIjVXsW98tZvj-NZF-M9Pke_EWJm-jjXCu_8","e":"AQAB","kty":"RSA","n":"piffosMS0HOSgsSr_zQkXYaQt1kOCD73VR0b2XJD6UdQCKPbnBOzTIuA_xowX61QVsl5pCZLTw8ERC3r2Nlxj5Rp_H6RuOT7ioUqlbnxSGnfuAn8dFupY3A-sf9HVDOvtJdlS-nO9yA4wWU-A50zZ1Mf0pPZlUZE6dUQfsJFi5yXaNAybyk3U4VpMO_SXAilWEHVhiO0F0ccpJMCkT47AeXmYH9MlWwIGcay0UiAsdrs8J-q1arZ7Mbq0oxHmUXJG0vwRvAL8KnCEi8cJ3e2kKCRcr-BQCujsHUyUl6f_ATwSVuTHdAR1IzIcW37v27h3WQK_v0ffQM1NstamDX5vQ","p":"4myVm2M5cZGvVXsOmWUTUG87VC1GlQcL5tmMNSGSpQCL8yWZ1vANkmCxSMptrKB4dU9DAB3On6_oMhW1pJ3uYNGSW49BcmJoLkiWKeg5zWFnKPQNuThQmY1sCCubtKhBQgaYUr7TVzN9smrDV3zCu9MlRl-XPwnEmWaDII3g-f8","q":"u9v4IOEsb4l2Y3eWKE2bwJh5fJRR4vivaYA7U-1-OpvDwB3A48Rey9IL1ucXqE5G1Du8BtijPm5oSAar5uzrjtg1bZ9gevif6DnBGaIRE7LnSrUsTPfZwzntJ1rTaGiVe_pAdnTKXXaH6DxygXxH4wvGgA44V3TTfBXQUcjzdEM","qi":"lDBnSPKkRnYqQvbqVD1LxzqBPEeqEA3GyCqMj6fIZNgoEaBSLi0TSsUyGZ5mahX3KO35vKAZa5jvGjhvUGUiXycq8KvRZdeGK45vJdwZT2TiXiDwo9IQgJcbFMpxaB9DhjX2x0yqxgUY5ca75jLqbMuKBKBN0PVqIr9jlHkR8_s","use":"sig","kid":"6823046760c5d460","alg":"RS256"}]}
+40
View File
@@ -0,0 +1,40 @@
# Proxy,如果你需要的话(比如你使用 GitHub 作为鉴权服务提供商)
# HTTP_PROXY=http://localhost:7890
# HTTPS_PROXY=http://localhost:7890
# 允许登录的邮箱地址,用英文逗号分隔
# 设置后只有列表中的邮箱可以登录,其他用户可以注册但无法登录
# 留空则允许所有用户注册登录
# AUTH_ALLOWED_EMAILS=user1@example.com,user2@example.com
# 禁用用户注册(仅允许 SSO 登录)
# 设置为 1 后,用户无法通过邮箱密码注册,只能通过 SSO 登录
# AUTH_DISABLE_EMAIL_PASSWORD=1
# ===================
# ===== 预设配置 =====
# ===================
# 如没有特殊需要不用更改
LOBE_PORT=3210
APP_URL=http://localhost:3210
# 内部应用URL,用于容器内部服务间通信
# Docker Compose 部署时必须配置,否则当 APP_URL 为宿主机 IP 时 AI 生图等功能会失败
INTERNAL_APP_URL=http://localhost:3210
# 密钥配置(由 setup.sh 自动生成)
KEY_VAULTS_SECRET=YOUR_KEY_VAULTS_SECRET
AUTH_SECRET=YOUR_AUTH_SECRET
# PostgreSQL 配置
LOBE_DB_NAME=lobechat
POSTGRES_PASSWORD=uWNZugjBqixf8dxC
# RustFS S3 配置
S3_ENDPOINT=http://localhost:9000
RUSTFS_PORT=9000
RUSTFS_ADMIN_PORT=9001
RUSTFS_ACCESS_KEY=admin
RUSTFS_SECRET_KEY=YOUR_RUSTFS_PASSWORD
RUSTFS_LOBE_BUCKET=lobe
JWKS_KEY={"keys":[{"d":"PVoFyqyrGstB8wU52S7gqqQQdZLtin_thcEM0nrNtqp9U-NlKLlhgEcWp5t89ycgvhsAzmrRbezGj4JBTr3jn7eWdwQpPJNYiipnsgeJn0pwsB0H2dMqtavxinoPVXkMTOuGHMTFhhyguFBw2JbIL0PTQUcUlXjv40OoJpYHZeggSxgfV-TuxjwW8Ll4-n84M5IOi6A53RvioE-Hm1iyIc2XLBCfyOu-SbAQYi8HzrA64kCxobAB0peLQMiAzfZmwPKiGOhnhKrAlYmG02qFnbUYiJu_-AXwsAyGv9S9i6dwK7QXaGGWYyis8LlPpd_JmPrBnrWomwDlI045NUMWZQ","dp":"OSXI2NBBZl2r0Dpf4-1z44A_jC5lOyXtJhXQYnSXy5eIuxTJcEtkUYagGEwnREO4Q3t-4J-lT_6Y71M1ZlgKG1upwfw1O4aE3vGpHOik9iZYYCjA8fe5uBfOpX1ELmOtHNoHRhMtyjuPxSFXLlSp3bgcF1f3F40ClukdvXCx0Mc","dq":"m6hNdfj-F8E_7nUlX2nG95OffkFrhHTo67ML9aPgpvFwBlzg-hk5LwtxMfUzngqWF78TMl0JDm7vS1bz0xlWqXqu8pFPoTUnUoWgYfvuyHLBwR5TgccQkfoKbkSMzYNy8VJPXZeyIjVXsW98tZvj-NZF-M9Pke_EWJm-jjXCu_8","e":"AQAB","kty":"RSA","n":"piffosMS0HOSgsSr_zQkXYaQt1kOCD73VR0b2XJD6UdQCKPbnBOzTIuA_xowX61QVsl5pCZLTw8ERC3r2Nlxj5Rp_H6RuOT7ioUqlbnxSGnfuAn8dFupY3A-sf9HVDOvtJdlS-nO9yA4wWU-A50zZ1Mf0pPZlUZE6dUQfsJFi5yXaNAybyk3U4VpMO_SXAilWEHVhiO0F0ccpJMCkT47AeXmYH9MlWwIGcay0UiAsdrs8J-q1arZ7Mbq0oxHmUXJG0vwRvAL8KnCEi8cJ3e2kKCRcr-BQCujsHUyUl6f_ATwSVuTHdAR1IzIcW37v27h3WQK_v0ffQM1NstamDX5vQ","p":"4myVm2M5cZGvVXsOmWUTUG87VC1GlQcL5tmMNSGSpQCL8yWZ1vANkmCxSMptrKB4dU9DAB3On6_oMhW1pJ3uYNGSW49BcmJoLkiWKeg5zWFnKPQNuThQmY1sCCubtKhBQgaYUr7TVzN9smrDV3zCu9MlRl-XPwnEmWaDII3g-f8","q":"u9v4IOEsb4l2Y3eWKE2bwJh5fJRR4vivaYA7U-1-OpvDwB3A48Rey9IL1ucXqE5G1Du8BtijPm5oSAar5uzrjtg1bZ9gevif6DnBGaIRE7LnSrUsTPfZwzntJ1rTaGiVe_pAdnTKXXaH6DxygXxH4wvGgA44V3TTfBXQUcjzdEM","qi":"lDBnSPKkRnYqQvbqVD1LxzqBPEeqEA3GyCqMj6fIZNgoEaBSLi0TSsUyGZ5mahX3KO35vKAZa5jvGjhvUGUiXycq8KvRZdeGK45vJdwZT2TiXiDwo9IQgJcbFMpxaB9DhjX2x0yqxgUY5ca75jLqbMuKBKBN0PVqIr9jlHkR8_s","use":"sig","kid":"6823046760c5d460","alg":"RS256"}]}
+18
View File
@@ -0,0 +1,18 @@
{
"ID": "",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": ["*"]
},
"Action": ["s3:GetObject"],
"NotAction": [],
"Resource": ["arn:aws:s3:::lobe/*"],
"NotResource": [],
"Condition": {}
}
],
"Version": "2012-10-17"
}
+144
View File
@@ -0,0 +1,144 @@
name: lobehub
services:
lobe:
image: lobehub/lobehub
container_name: lobehub
ports:
- '${LOBE_PORT}:3210'
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
rustfs:
condition: service_healthy
rustfs-init:
condition: service_completed_successfully
environment:
- 'KEY_VAULTS_SECRET=${KEY_VAULTS_SECRET}'
- 'AUTH_SECRET=${AUTH_SECRET}'
- 'DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgresql:5432/${LOBE_DB_NAME}'
- 'INTERNAL_APP_URL=http://localhost:3210'
- 'S3_ENDPOINT=${S3_ENDPOINT}'
- 'S3_BUCKET=${RUSTFS_LOBE_BUCKET}'
- 'S3_ENABLE_PATH_STYLE=1'
- 'S3_ACCESS_KEY=${RUSTFS_ACCESS_KEY}'
- 'S3_ACCESS_KEY_ID=${RUSTFS_ACCESS_KEY}'
- 'S3_SECRET_ACCESS_KEY=${RUSTFS_SECRET_KEY}'
- 'LLM_VISION_IMAGE_USE_BASE64=1'
- 'S3_SET_ACL=0'
- 'SEARXNG_URL=http://searxng:8080'
- 'REDIS_URL=redis://redis:6379'
- 'REDIS_PREFIX=lobechat'
- 'REDIS_TLS=0'
env_file:
- .env
restart: always
networks:
- lobe-network
postgresql:
image: paradedb/paradedb:latest-pg17
container_name: lobe-postgres
# Optional: Remove or change these ports if:
# - You don't need external database access
# - The ports conflict with other services on your host
ports:
- '5432:5432'
volumes:
- './data:/var/lib/postgresql/data'
environment:
- 'POSTGRES_DB=${LOBE_DB_NAME}'
- 'POSTGRES_PASSWORD=${POSTGRES_PASSWORD}'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 5s
timeout: 5s
retries: 5
restart: always
networks:
- lobe-network
redis:
image: redis:7-alpine
container_name: lobe-redis
# Optional: Remove or change these ports if:
# - You don't need external access
# - The ports conflict with other services on your host
ports:
- '6379:6379'
command: redis-server --save 60 1000 --appendonly yes
volumes:
- 'redis_data:/data'
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 3s
retries: 5
restart: always
networks:
- lobe-network
rustfs:
image: rustfs/rustfs:latest
container_name: lobe-rustfs
ports:
- '${RUSTFS_PORT}:9000'
- '${RUSTFS_ADMIN_PORT}:9001'
# Optional: Remove or change RUSTFS_ADMIN_PORT if:
# - You don't need external access to rustfs control panel
# - The ports conflict with other services on your host
# - RUSTFS_PORT is necessary only if you need to upload images to conversations. (avatar uploads are not affected)
# - Uploading images to conversations requires a browser-accessible S3_ENDPOINT.
environment:
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY}
volumes:
- 'rustfs-data:/data'
healthcheck:
test: ['CMD-SHELL', 'wget -qO- http://localhost:9000/health >/dev/null 2>&1 || exit 1']
interval: 5s
timeout: 3s
retries: 30
command:
['--access-key', '${RUSTFS_ACCESS_KEY}', '--secret-key', '${RUSTFS_SECRET_KEY}', '/data']
networks:
- lobe-network
rustfs-init:
image: minio/mc:latest
container_name: lobe-rustfs-init
depends_on:
rustfs:
condition: service_healthy
volumes:
- ./bucket.config.json:/bucket.config.json:ro
entrypoint: /bin/sh
command: -c ' set -eux; echo "S3_ACCESS_KEY=${RUSTFS_ACCESS_KEY}, S3_SECRET_KEY=${RUSTFS_SECRET_KEY}"; mc --version; mc alias set rustfs "http://rustfs:9000" "${RUSTFS_ACCESS_KEY}" "${RUSTFS_SECRET_KEY}"; mc ls rustfs || true; mc mb "rustfs/lobe" --ignore-existing; mc admin info rustfs || true; mc anonymous set-json "/bucket.config.json" "rustfs/lobe"; '
restart: 'no'
networks:
- lobe-network
searxng:
image: searxng/searxng
container_name: lobe-searxng
volumes:
- './searxng-settings.yml:/etc/searxng/settings.yml'
environment:
- 'SEARXNG_SETTINGS_FILE=/etc/searxng/settings.yml'
restart: always
networks:
- lobe-network
env_file:
- .env
networks:
lobe-network:
driver: bridge
volumes:
redis_data:
driver: local
rustfs-data:
driver: local
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
# Proxy, if you need it
# HTTP_PROXY=http://localhost:7890
# HTTPS_PROXY=http://localhost:7890
# Other environment variables, as needed. You can refer to the environment variables configuration for the client version, making sure not to have ACCESS_CODE.
# OPENAI_API_KEY=sk-xxxx
# OPENAI_PROXY_URL=https://api.openai.com/v1
# OPENAI_MODEL_LIST=...
# ===========================
# ====== Preset config ======
# ===========================
# if no special requirements, no need to change
LOBE_PORT=3210
RUSTFS_PORT=9000
# Postgres related, which are the necessary environment variables for DB
LOBE_DB_NAME=lobechat
POSTGRES_PASSWORD=change_this_password_on_production
# RUSTFS S3 configuration
RUSTFS_ACCESS_KEY=admin
RUSTFS_SECRET_KEY=change_this_password_on_production
# Configure the bucket information of RUSTFS
S3_ENDPOINT=http://localhost:9000
RUSTFS_LOBE_BUCKET=lobe
+29
View File
@@ -0,0 +1,29 @@
# Proxy,如果你需要的话(比如你使用 GitHub 作为鉴权服务提供商)
# HTTP_PROXY=http://localhost:7890
# HTTPS_PROXY=http://localhost:7890
# 其他环境变量,视需求而定,可以参照客户端版本的环境变量配置,注意不要有 ACCESS_CODE
# OPENAI_API_KEY=sk-xxxx
# OPENAI_PROXY_URL=https://api.openai.com/v1
# OPENAI_MODEL_LIST=...
# ===================
# ===== 预设配置 =====
# ===================
# 如没有特殊需要不用更改
LOBE_PORT=3210
RUSTFS_PORT=9000
# Postgres 相关,也即 DB 必须的环境变量
LOBE_DB_NAME=lobechat
POSTGRES_PASSWORD=change_this_password_on_production
# RustFS S3 配置
RUSTFS_ACCESS_KEY=admin
RUSTFS_SECRET_KEY=change_this_password_on_production
# RustFS bucket 配置
S3_ENDPOINT=http://localhost:9000
RUSTFS_LOBE_BUCKET=lobe
+2
View File
@@ -0,0 +1,2 @@
data/
s3_data/
+18
View File
@@ -0,0 +1,18 @@
{
"ID": "",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": ["*"]
},
"Action": ["s3:GetObject"],
"NotAction": [],
"Resource": ["arn:aws:s3:::lobe/*"],
"NotResource": [],
"Condition": {}
}
],
"Version": "2012-10-17"
}
+122
View File
@@ -0,0 +1,122 @@
name: lobehub
services:
network-service:
image: alpine
container_name: lobe-network
restart: always
ports:
- '${RUSTFS_PORT}:9000' # RustFS API
- '9001:9001' # RustFS Console
- '${LOBE_PORT}:3210' # LobeChat
command: tail -f /dev/null
networks:
- lobe-network
env_file:
- .env
postgresql:
image: paradedb/paradedb:latest-pg17
container_name: lobe-postgres
ports:
- '5432:5432'
volumes:
- './data:/var/lib/postgresql/data'
environment:
- 'POSTGRES_DB=${LOBE_DB_NAME}'
- 'POSTGRES_PASSWORD=${POSTGRES_PASSWORD}'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 5s
timeout: 5s
retries: 5
restart: always
networks:
- lobe-network
env_file:
- .env
redis:
image: redis:7-alpine
container_name: lobe-redis
ports:
- '6379:6379'
command: redis-server --save 60 1000 --appendonly yes
volumes:
- 'redis_data:/data'
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 3s
retries: 5
restart: always
networks:
- lobe-network
rustfs:
image: rustfs/rustfs:latest
container_name: lobe-rustfs
network_mode: 'service:network-service'
environment:
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY}
volumes:
- rustfs-data:/data
healthcheck:
test: ['CMD-SHELL', 'wget -qO- http://localhost:9000/health >/dev/null 2>&1 || exit 1']
interval: 5s
timeout: 3s
retries: 30
command: ['--access-key','${RUSTFS_ACCESS_KEY}','--secret-key','${RUSTFS_SECRET_KEY}','/data']
env_file:
- .env
rustfs-init:
image: minio/mc:latest
container_name: lobe-rustfs-init
depends_on:
rustfs:
condition: service_healthy
volumes:
- ./bucket.config.json:/bucket.config.json:ro
entrypoint: /bin/sh
command: -c '
set -eux;
echo "S3_ACCESS_KEY=${RUSTFS_ACCESS_KEY}, S3_SECRET_KEY=${RUSTFS_SECRET_KEY}";
mc --version;
mc alias set rustfs "http://network-service:9000" "${RUSTFS_ACCESS_KEY}" "${RUSTFS_SECRET_KEY}";
mc ls rustfs || true;
mc mb "rustfs/lobe" --ignore-existing;
mc admin info rustfs || true;
mc anonymous set-json "/bucket.config.json" "rustfs/lobe";
'
restart: 'no'
networks:
- lobe-network
env_file:
- .env
searxng:
image: searxng/searxng
container_name: lobe-searxng
ports:
- '8180:8080'
volumes:
- './searxng-settings.yml:/etc/searxng/settings.yml'
environment:
- 'SEARXNG_SETTINGS_FILE=/etc/searxng/settings.yml'
restart: always
networks:
- lobe-network
volumes:
data:
driver: local
redis_data:
driver: local
rustfs-data:
driver: local
networks:
lobe-network:
driver: bridge
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,43 @@
# Proxy, if you need it
# HTTP_PROXY=http://localhost:7890
# HTTPS_PROXY=http://localhost:7890
# Other environment variables, as needed. You can refer to the environment variables configuration for the client version, making sure not to have ACCESS_CODE.
# OPENAI_API_KEY=sk-xxxx
# OPENAI_PROXY_URL=https://api.openai.com/v1
# OPENAI_MODEL_LIST=...
# ===========================
# ====== Preset config ======
# ===========================
# if no special requirements, no need to change
LOBE_PORT=3210
CASDOOR_PORT=8000
MINIO_PORT=9000
APP_URL=http://localhost:3210
AUTH_URL=http://localhost:3210/api/auth
# Postgres related, which are the necessary environment variables for DB
LOBE_DB_NAME=lobechat
POSTGRES_PASSWORD=uWNZugjBqixf8dxC
AUTH_CASDOOR_ISSUER=http://localhost:8000
# Casdoor secret
AUTH_CASDOOR_ID=a387a4892ee19b1a2249
AUTH_CASDOOR_SECRET=dbf205949d704de81b0b5b3603174e23fbecc354
CASDOOR_WEBHOOK_SECRET=casdoor-secret
# MinIO S3 configuration
MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=YOUR_MINIO_PASSWORD
# Configure the bucket information of MinIO
S3_ENDPOINT=http://localhost:9000
MINIO_LOBE_BUCKET=lobe
GF_SECURITY_ADMIN_PASSWORD=YOUR_GRAFANA_PASSWORD
# Configure for casdoor
origin=http://localhost:8000
@@ -0,0 +1,41 @@
# Proxy,如果你需要的话(比如你使用 GitHub 作为鉴权服务提供商)
# HTTP_PROXY=http://localhost:7890
# HTTPS_PROXY=http://localhost:7890
# 其他环境变量,视需求而定,可以参照客户端版本的环境变量配置,注意不要有 ACCESS_CODE
# OPENAI_API_KEY=sk-xxxx
# OPENAI_PROXY_URL=https://api.openai.com/v1
# OPENAI_MODEL_LIST=...
# ===================
# ===== 预设配置 =====
# ===================
# 如没有特殊需要不用更改
LOBE_PORT=3210
CASDOOR_PORT=8000
MINIO_PORT=9000
APP_URL=http://localhost:3210
AUTH_URL=http://localhost:3210/api/auth
# Postgres 相关,也即 DB 必须的环境变量
LOBE_DB_NAME=lobechat
POSTGRES_PASSWORD=uWNZugjBqixf8dxC
AUTH_CASDOOR_ISSUER=http://localhost:8000
# Casdoor secret
AUTH_CASDOOR_ID=a387a4892ee19b1a2249
AUTH_CASDOOR_SECRET=dbf205949d704de81b0b5b3603174e23fbecc354
CASDOOR_WEBHOOK_SECRET=casdoor-secret
# MinIO S3 配置
MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=YOUR_MINIO_PASSWORD
# 在下方配置 minio 中添加的桶
S3_ENDPOINT=http://localhost:9000
MINIO_LOBE_BUCKET=lobe
# 为 casdoor 配置
origin=http://localhost:8000
@@ -0,0 +1,250 @@
name: lobehub
services:
network-service:
image: alpine
container_name: lobe-network
restart: always
ports:
- '${MINIO_PORT}:${MINIO_PORT}' # MinIO API
- '9001:9001' # MinIO Console
- '${CASDOOR_PORT}:${CASDOOR_PORT}' # Casdoor
- '${LOBE_PORT}:3210' # LobeChat
- '3000:3000' # Grafana
- '4318:4318' # otel-collector HTTP
- '4317:4317' # otel-collector gRPC
command: tail -f /dev/null
networks:
- lobe-network
postgresql:
image: paradedb/paradedb:latest-pg17
container_name: lobe-postgres
ports:
- '5432:5432'
volumes:
- './data:/var/lib/postgresql/data'
environment:
- 'POSTGRES_DB=${LOBE_DB_NAME}'
- 'POSTGRES_PASSWORD=${POSTGRES_PASSWORD}'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 5s
timeout: 5s
retries: 5
restart: always
networks:
- lobe-network
minio:
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
container_name: lobe-minio
network_mode: 'service:network-service'
volumes:
- './s3_data:/etc/minio/data'
environment:
- 'MINIO_API_CORS_ALLOW_ORIGIN=*'
env_file:
- .env
restart: always
entrypoint: >
/bin/sh -c "
minio server /etc/minio/data --address ':${MINIO_PORT}' --console-address ':9001' &
MINIO_PID=\$!
while ! curl -s http://localhost:${MINIO_PORT}/minio/health/live; do
echo 'Waiting for MinIO to start...'
sleep 1
done
sleep 5
mc alias set myminio http://localhost:${MINIO_PORT} ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD}
echo 'Creating bucket ${MINIO_LOBE_BUCKET}'
mc mb myminio/${MINIO_LOBE_BUCKET}
wait \$MINIO_PID
"
# version lock ref: https://github.com/lobehub/lobe-chat/pull/7331
casdoor:
image: casbin/casdoor:v2.13.0
container_name: lobe-casdoor
entrypoint: /bin/sh -c './server --createDatabase=true'
network_mode: 'service:network-service'
depends_on:
postgresql:
condition: service_healthy
environment:
httpport: ${CASDOOR_PORT}
RUNNING_IN_DOCKER: 'true'
driverName: 'postgres'
dataSourceName: 'user=postgres password=${POSTGRES_PASSWORD} host=postgresql port=5432 sslmode=disable dbname=casdoor'
runmode: 'dev'
volumes:
- ./init_data.json:/init_data.json
env_file:
- .env
searxng:
image: searxng/searxng
container_name: lobe-searxng
volumes:
- './searxng-settings.yml:/etc/searxng/settings.yml'
environment:
- 'SEARXNG_SETTINGS_FILE=/etc/searxng/settings.yml'
restart: always
networks:
- lobe-network
env_file:
- .env
grafana:
image: grafana/grafana:12.2.0-17419259409
container_name: lobe-grafana
network_mode: 'service:network-service'
restart: always
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD}
- GF_FEATURE_TOGGLES_ENABLE=traceqlEditor
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/dashboards:/etc/grafana/provisioning/dashboards
- ./grafana/datasources:/etc/grafana/provisioning/datasources
depends_on:
- tempo
- prometheus
tempo:
image: grafana/tempo:latest
container_name: lobe-tempo
network_mode: 'service:network-service'
restart: always
volumes:
- ./tempo/tempo.yaml:/etc/tempo.yaml
- tempo_data:/var/tempo
command: ['-config.file=/etc/tempo.yaml']
prometheus:
image: prom/prometheus
container_name: lobe-prometheus
network_mode: 'service:network-service'
restart: always
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--web.enable-otlp-receiver'
- '--web.enable-remote-write-receiver'
- '--enable-feature=exemplar-storage'
otel-collector:
image: otel/opentelemetry-collector
container_name: lobe-otel-collector
network_mode: 'service:network-service'
restart: always
volumes:
- ./otel-collector/collector-config.yaml:/etc/otelcol/config.yaml
command: ['--config', '/etc/otelcol/config.yaml']
depends_on:
- tempo
- prometheus
otel-tracing-test:
profiles:
- otel-test
image: ghcr.io/grafana/xk6-client-tracing:v0.0.9
container_name: lobe-otel-tracing-test
network_mode: 'service:network-service'
restart: always
environment:
- ENDPOINT=127.0.0.1:4317
lobe:
image: lobehub/lobehub
container_name: lobehub
network_mode: 'service:network-service'
depends_on:
postgresql:
condition: service_healthy
network-service:
condition: service_started
minio:
condition: service_started
casdoor:
condition: service_started
environment:
- 'AUTH_SSO_PROVIDERS=casdoor'
- 'KEY_VAULTS_SECRET=Kix2wcUONd4CX51E/ZPAd36BqM4wzJgKjPtz2sGztqQ='
- 'AUTH_SECRET=NX2kaPE923dt6BL2U8e9oSre5RfoT7hg'
- 'DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgresql:5432/${LOBE_DB_NAME}'
- 'S3_BUCKET=${MINIO_LOBE_BUCKET}'
- 'S3_ENABLE_PATH_STYLE=1'
- 'S3_ACCESS_KEY=${MINIO_ROOT_USER}'
- 'S3_ACCESS_KEY_ID=${MINIO_ROOT_USER}'
- 'S3_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}'
- 'LLM_VISION_IMAGE_USE_BASE64=1'
- 'S3_SET_ACL=0'
- 'SEARXNG_URL=http://searxng:8080'
- 'OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/protobuf'
- 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4318/v1/metrics'
- 'OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf'
- 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces'
env_file:
- .env
restart: always
entrypoint: >
/bin/sh -c "
/bin/node /app/startServer.js &
LOBE_PID=\$!
sleep 3
if [ $(wget --timeout=5 --spider --server-response ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration 2>&1 | grep -c 'HTTP/1.1 200 OK') -eq 0 ]; then
echo '⚠️Warning: Unable to fetch OIDC configuration from Casdoor'
echo 'Request URL: ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration'
echo 'Read more at: https://lobehub.com/docs/self-hosting/server-database/docker-compose#necessary-configuration'
echo ''
echo '⚠️注意:无法从 Casdoor 获取 OIDC 配置'
echo '请求 URL: ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration'
echo '了解更多:https://lobehub.com/zh/docs/self-hosting/server-database/docker-compose#necessary-configuration'
echo ''
else
if ! wget -O - --timeout=5 ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration 2>&1 | grep 'issuer' | grep ${AUTH_CASDOOR_ISSUER}; then
printf '❌Error: The Auth issuer is conflict, Issuer in OIDC configuration is: %s' \$(wget -O - --timeout=5 ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration 2>&1 | grep -E 'issuer.*' | awk -F '\"' '{print \$4}')
echo ' , but the issuer in .env file is: ${AUTH_CASDOOR_ISSUER} '
echo 'Request URL: ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration'
echo 'Read more at: https://lobehub.com/docs/self-hosting/server-database/docker-compose#necessary-configuration'
echo ''
printf '❌错误:Auth 的 issuer 冲突,OIDC 配置中的 issuer 是:%s' \$(wget -O - --timeout=5 ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration 2>&1 | grep -E 'issuer.*' | awk -F '\"' '{print \$4}')
echo ' , 但 .env 文件中的 issuer 是:${AUTH_CASDOOR_ISSUER} '
echo '请求 URL: ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration'
echo '了解更多:https://lobehub.com/zh/docs/self-hosting/server-database/docker-compose#necessary-configuration'
echo ''
fi
fi
if [ $(wget --timeout=5 --spider --server-response ${S3_ENDPOINT}/minio/health/live 2>&1 | grep -c 'HTTP/1.1 200 OK') -eq 0 ]; then
echo '⚠️Warning: Unable to fetch MinIO health status'
echo 'Request URL: ${S3_ENDPOINT}/minio/health/live'
echo 'Read more at: https://lobehub.com/docs/self-hosting/server-database/docker-compose#necessary-configuration'
echo ''
echo '⚠️注意:无法获取 MinIO 健康状态'
echo '请求 URL: ${S3_ENDPOINT}/minio/health/live'
echo '了解更多:https://lobehub.com/zh/docs/self-hosting/server-database/docker-compose#necessary-configuration'
echo ''
fi
wait \$LOBE_PID
"
volumes:
data:
driver: local
s3_data:
driver: local
grafana_data:
driver: local
tempo_data:
driver: local
prometheus_data:
driver: local
networks:
lobe-network:
driver: bridge
@@ -0,0 +1,15 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
uid: prometheus
access: proxy
orgId: 1
url: http://127.0.0.1:9090
basicAuth: false
isDefault: false
version: 1
editable: false
jsonData:
httpMethod: GET
@@ -0,0 +1,20 @@
apiVersion: 1
datasources:
- name: Tempo
type: tempo
access: proxy
orgId: 1
url: http://127.0.0.1:3200
basicAuth: false
isDefault: true
version: 1
editable: false
apiVersion: 1
uid: tempo
jsonData:
httpMethod: GET
serviceMap:
datasourceUid: prometheus
streamingEnabled:
search: true
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,45 @@
extensions:
health_check:
endpoint: 127.0.0.1:13133
receivers:
prometheus:
config:
scrape_configs:
- job_name: otel-collector-metrics
scrape_interval: 10s
static_configs:
- targets: ['127.0.0.1:8888']
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
exporters:
prometheusremotewrite:
endpoint: http://127.0.0.1:9090/api/v1/write
tls:
insecure: true
otlp:
endpoint: 127.0.0.1:14317
tls:
insecure: true
debug:
verbosity: detailed
service:
pipelines:
metrics:
receivers: [prometheus]
exporters: [prometheusremotewrite]
traces:
receivers: [otlp]
exporters: [otlp]
logs:
receivers: [otlp]
exporters: [debug]
@@ -0,0 +1,11 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['127.0.0.1:9090']
- job_name: 'tempo'
static_configs:
- targets: ['127.0.0.1:3200']
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,58 @@
stream_over_http_enabled: true
server:
http_listen_port: 3200
log_level: info
query_frontend:
search:
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09
metadata_slo:
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09
trace_by_id:
duration_slo: 5s
distributor:
max_attribute_bytes: 10485760
receivers:
otlp:
protocols:
grpc:
endpoint: 127.0.0.1:14317
http:
endpoint: 127.0.0.1:14318
ingester:
max_block_duration: 5m # cut the headblock when this much time passes. this is being set for demo purposes and should probably be left alone normally
compactor:
compaction:
block_retention: 1h # overall Tempo trace retention. set for demo purposes
metrics_generator:
registry:
external_labels:
source: tempo
cluster: docker-compose
storage:
path: /var/tempo/generator/wal
remote_write:
- url: http://127.0.0.1:9090/api/v1/write
send_exemplars: true
traces_storage:
path: /var/tempo/generator/traces
storage:
trace:
backend: local # backend configuration to use
wal:
path: /var/tempo/wal # where to store the wal locally
local:
path: /var/tempo/blocks
overrides:
defaults:
metrics_generator:
processors: [service-graphs, span-metrics, local-blocks] # enables metrics generator
generate_native_histograms: both
+772
View File
@@ -0,0 +1,772 @@
#!/bin/bash
# ==================
# == Env settings ==
# ==================
# check operating system
# ref: https://github.com/lobehub/lobe-chat/pull/5247
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
SED_INPLACE_ARGS=('-i' '')
else
# not macOS
SED_INPLACE_ARGS=('-i')
fi
# ======================
# == Process the args ==
# ======================
# 1. Default values of arguments
# Arg: -l or --lang
# Determine the language to show, default is en
# Arg: --url
# Determine the source URL to download files
SOURCE_URL="https://raw.githubusercontent.com/lobehub/lobe-chat/main"
# Arg: --host
# Determine the server host
HOST=""
# 2. Parse script arguments
while getopts "l:-:" opt; do
case $opt in
l)
LANGUAGE=$OPTARG
;;
-)
case "${OPTARG}" in
lang)
LANGUAGE="${!OPTIND}"
OPTIND=$(($OPTIND + 1))
;;
url)
SOURCE_URL="${!OPTIND}"
OPTIND=$(($OPTIND + 1))
;;
host)
HOST="${!OPTIND}"
OPTIND=$(($OPTIND + 1))
;;
*)
echo "Usage: $0 [-l language|--lang language] [--url source] [--host serverhost]" >&2
exit 1
;;
esac
;;
*)
echo "Usage: $0 [-l language|--lang language] [--url source]" >&2
exit 1
;;
esac
done
#######################
## Helper Functions ##
#######################
# Supported languages and messages
# Arg: -l --lang
# If the language is not supported, default to English
# Function to show messages
show_message() {
local key="$1"
case $key in
choose_language)
echo "Please choose a language / 请选择语言:"
echo "(0) English"
echo "(1) 简体中文"
;;
downloading)
case $LANGUAGE in
zh_CN)
echo "正在下载文件..."
;;
*)
echo "Downloading files..."
;;
esac
;;
extracted_success)
case $LANGUAGE in
zh_CN)
echo " 解压成功到目录:"
;;
*)
echo " extracted successfully to directory: "
;;
esac
;;
extracted_failed)
case $LANGUAGE in
zh_CN)
echo " 解压失败。"
;;
*)
echo " extraction failed."
;;
esac
;;
file_not_exists)
case $LANGUAGE in
zh_CN)
echo " 不存在。"
;;
*)
echo " does not exist."
;;
esac
;;
security_secrect_regenerate)
case $LANGUAGE in
zh_CN)
echo "重新生成安全密钥..."
;;
*)
echo "Regenerate security secrets..."
;;
esac
;;
security_secrect_regenerate_failed)
case $LANGUAGE in
zh_CN)
echo "无法重新生成安全密钥:"
;;
*)
echo "Failed to regenerate security secrets: "
;;
esac
;;
host_regenerate)
case $LANGUAGE in
zh_CN)
echo "✔️ 已更新部署模式配置"
;;
*)
echo "✔️ Updated deployment mode configuration"
;;
esac
;;
host_regenerate_failed)
case $LANGUAGE in
zh_CN)
echo "无法重新生成服务器域名:"
;;
*)
echo "Failed to regenerate server host: "
;;
esac
;;
security_secrect_regenerate_report)
case $LANGUAGE in
zh_CN)
echo "安全密钥生成结果如下:"
;;
*)
echo "Security secret generation results are as follows:"
;;
esac
;;
tips_download_failed)
case $LANGUAGE in
zh_CN)
echo "$2 下载失败,请检查网络连接。"
;;
*)
echo "$2 Download failed, please check the network connection."
;;
esac
;;
tips_already_installed)
case $LANGUAGE in
zh_CN)
echo "检测到您已经运行过 LobeHub,本安装程序只能完成初始化配置,并不能重复安装。如果你需要重新安装,请删除 data 和 s3_data 文件夹。"
;;
*)
echo "It is detected that you have run LobeHub. This installation program can only complete the initialization configuration and cannot be reinstalled. If you need to reinstall, please delete the data and s3_data folders."
;;
esac
;;
tips_run_command)
case $LANGUAGE in
zh_CN)
echo "您已经完成了所有配置。请运行以下命令启动 LobeHub 尝试启动:"
;;
*)
echo "You have completed all configurations. Please run this command to start LobeHub:"
;;
esac
;;
tips_if_want_searxng_logs)
case $LANGUAGE in
zh_CN)
echo "在上述命令中已屏蔽 SearXNG 的日志。如果你想查看 SearXNG 的日志,可以去除选项: --no-attach searxng 或运行以下命令:"
;;
*)
echo "In the above command, the logs of SearXNG are blocked by default. If you want to view the logs of SearXNG, you can remove the option: --no-attach searxng or run the following command:"
;;
esac
;;
tips_if_run_normally)
case $LANGUAGE in
zh_CN)
echo "如果一切运行正常,你可以使用以下指令在 daemon 模式下启动 LobeHub:"
;;
*)
echo "If everything runs normally, you can use the following command to start LobeHub in daemon mode:"
;;
esac
;;
tips_regen_jwks)
case $LANGUAGE in
zh_CN)
echo "在完成部署测试后,请前往 https://lobehub.com/zh/docs/self-hosting/environment-variables/auth#jwks_key 生成新的 JWKS_KEY 并替换 .env 中的值,以确保安全性。"
;;
*)
echo "After completing the deployment test, please go to https://lobehub.com/docs/self-hosting/environment-variables/auth#jwks_key to generate a new JWKS_KEY and replace the value in .env to ensure security."
;;
esac
;;
tips_disable_registration)
case $LANGUAGE in
zh_CN)
echo "如需限制用户注册,可在 .env 中配置:"
echo " - 使用 SSO 登录时,设置 AUTH_DISABLE_EMAIL_PASSWORD=1 可禁用邮箱密码注册"
echo " - 使用邮箱密码登录时,设置 AUTH_ALLOWED_EMAILS=user1@example.com,user2@example.com 可限制允许登录的邮箱"
;;
*)
echo "To restrict user registration, configure in .env:"
echo " - For SSO login: set AUTH_DISABLE_EMAIL_PASSWORD=1 to disable email/password registration"
echo " - For email/password login: set AUTH_ALLOWED_EMAILS=user1@example.com,user2@example.com to allow specific emails"
;;
esac
;;
tips_show_documentation)
case $LANGUAGE in
zh_CN)
echo "完整的环境变量在'.env'中可以在文档中找到:"
;;
*)
echo "Full environment variables in the '.env' can be found at the documentation on "
;;
esac
;;
tips_show_documentation_url)
case $LANGUAGE in
zh_CN)
echo "https://lobehub.com/zh/docs/self-hosting/environment-variables"
;;
*)
echo "https://lobehub.com/docs/self-hosting/environment-variables"
;;
esac
;;
tips_no_executable)
case $LANGUAGE in
zh_CN)
echo "没有找到,请先安装。"
;;
*)
echo "not found, please install it first."
;;
esac
;;
tips_allow_ports)
case $LANGUAGE in
zh_CN)
echo "请确保服务器以下端口未被占用且能被访问:3210, 9000, 9001"
;;
*)
echo "Please make sure the following ports on the server are not occupied and can be accessed: 3210, 9000, 9001"
;;
esac
;;
tips_auto_detected)
case $LANGUAGE in
zh_CN)
echo "已自动识别"
;;
*)
echo "Auto-detected"
;;
esac
;;
tips_private_ip_detected)
case $LANGUAGE in
zh_CN)
echo "注意,当前识别到内网 IP,如果需要外部访问,请替换为公网 IP 地址"
;;
*)
echo "Note that the current internal IP is detected. If you need external access, please replace it with the public IP address."
;;
esac
;;
tips_add_reverse_proxy)
case $LANGUAGE in
zh_CN)
echo "请在你的反向代理中完成域名到端口的映射:"
;;
*)
echo "Please complete the mapping of domain to port in your reverse proxy:"
;;
esac
;;
tips_no_docker_permission)
case $LANGUAGE in
zh_CN)
echo "WARN: 看起来当前用户没有 Docker 权限。"
echo "使用 'sudo usermod -aG docker $USER' 为用户分配 Docker 权限(可能需要重新启动 shell)。"
;;
*)
echo "WARN: It look like the current user does not have Docker permissions."
echo "Use 'sudo usermod -aG docker $USER' to assign Docker permissions to the user (may require restarting shell)."
;;
esac
;;
tips_init_database_failed)
case $LANGUAGE in
zh_CN)
echo "无法初始化数据库"
;;
*)
echo "Failed to initialize the database."
;;
esac
;;
ask_regenerate_secrets)
case $LANGUAGE in
zh_CN)
echo "是否要重新生成安全密钥?"
;;
*)
echo "Do you want to regenerate security secrets?"
;;
esac
;;
ask_deploy_mode)
case $LANGUAGE in
zh_CN)
echo "请选择部署模式:"
echo "(0) 域名模式(访问时无需指明端口),需要使用反向代理服务 LobeHub, RustFS,并分别分配一个域名;"
echo "(1) 端口模式(访问时需要指明端口,如使用IP访问,或域名+端口访问),需要放开指定端口;"
echo "(2) 本地模式(仅供本地测试使用)"
echo "如果你对这些内容疑惑,可以先选择使用本地模式进行部署,稍后根据文档指引再进行修改。"
echo "https://lobehub.com/docs/self-hosting/server-database/docker-compose"
;;
*)
echo "Please select the deployment mode:"
echo "(0) Domain mode (no need to specify the port when accessing), you need to use the reverse proxy service LobeHub, RustFS, and assign a domain name respectively;"
echo "(1) Port mode (need to specify the port when accessing, such as using IP access, or domain name + port access), you need to open the specified port;"
echo "(2) Local mode (for local testing only)"
echo "If you are confused about these contents, you can choose to deploy in local mode first, and then modify according to the document guide later."
echo "https://lobehub.com/docs/self-hosting/server-database/docker-compose"
;;
esac
;;
ask_host)
case $LANGUAGE in
zh_CN)
echo " 部署IP/域名"
;;
*)
echo " Deploy IP/Domain"
;;
esac
;;
ask_domain)
case $LANGUAGE in
zh_CN)
echo "服务的域名(例如 $2 ,不要包含协议前缀):"
;;
*)
echo "The domain of the service (e.g. $2, do not include the protocol prefix):"
;;
esac
;;
ask_protocol)
case $LANGUAGE in
zh_CN)
echo "域名是否使用 https 协议? (所有服务需要使用同一协议)"
;;
*)
echo "Does the domain use the https protocol? (All services need to use the same protocol)"
;;
esac
;;
ask_init_database)
case $LANGUAGE in
zh_CN)
echo "是否初始化数据库?"
;;
*)
echo "Do you want to initialize the database?"
;;
esac
;;
esac
}
# Function to download files
download_file() {
wget "$1" -O "$2"
# If run failed, exit
if [ $? -ne 0 ]; then
show_message "tips_download_failed" "$2"
exit 1
fi
}
print_centered() {
local text="$1" # Get input texts
local color="${2:-reset}" # Get color, default to reset
local term_width=$(tput cols) # Get terminal width
local text_length=${#text} # Get text length
local padding=$(((term_width - text_length) / 2)) # Get padding
# Get color code (compatible with bash 3.x)
local color_code=""
local reset_code="\e[0m"
case "$color" in
black) color_code="\e[30m" ;;
red) color_code="\e[31m" ;;
green) color_code="\e[32m" ;;
yellow) color_code="\e[33m" ;;
blue) color_code="\e[34m" ;;
magenta) color_code="\e[35m" ;;
cyan) color_code="\e[36m" ;;
white) color_code="\e[37m" ;;
reset) color_code="\e[0m" ;;
*)
echo "Invalid color specified. Available colors: black red green yellow blue magenta cyan white reset"
return 1
;;
esac
# Print the text with padding
printf "%*s${color_code}%s${reset_code}\n" $padding "" "$text"
}
# Usage:
# ```sh
# ask "prompt" "default" "description"
# echo $ask_result
# ```
# "prompt" ["description" "default"]:
ask() {
local prompt="$1"
local default="$2"
local description="$3"
# Add a space after the description if it is not empty
if [ -n "$description" ]; then
description="$description "
fi
local result
if [ -n "$default" ]; then
read -p "$prompt [${description}${default}]: " result
result=${result:-$default}
else
read -p "$prompt: " result
fi
# trim and assign to global variable
ask_result=$(echo "$result" | xargs)
}
####################
## Main Process ##
####################
# ===============
# == Variables ==
# ===============
# File list
SUB_DIR="docker-compose/deploy"
FILES=(
"$SUB_DIR/docker-compose.yml"
"$SUB_DIR/searxng-settings.yml"
"$SUB_DIR/bucket.config.json"
)
ENV_EXAMPLES=(
"$SUB_DIR/.env.zh-CN.example"
"$SUB_DIR/.env.example"
)
# Default values
RUSTFS_SECRET_KEY="YOUR_RUSTFS_PASSWORD"
RUSTFS_HOST="localhost:9000"
PROTOCOL="http"
# If no language is specified, ask the user to choose
if [ -z "$LANGUAGE" ]; then
show_message "choose_language"
ask "(0,1)" "0"
case $ask_result in
0)
LANGUAGE="en_US"
;;
1)
LANGUAGE="zh_CN"
;;
*)
echo "Invalid language: $ask_result"
exit 1
;;
esac
fi
section_download_files(){
# Download files asynchronously
if ! command -v wget &> /dev/null ; then
echo "wget" $(show_message "tips_no_executable")
exit 1
fi
download_file "$SOURCE_URL/${FILES[0]}" "docker-compose.yml"
download_file "$SOURCE_URL/${FILES[1]}" "searxng-settings.yml"
download_file "$SOURCE_URL/${FILES[2]}" "bucket.config.json"
# Download .env.example with the specified language
if [ "$LANGUAGE" = "zh_CN" ]; then
download_file "$SOURCE_URL/${ENV_EXAMPLES[0]}" ".env"
else
download_file "$SOURCE_URL/${ENV_EXAMPLES[1]}" ".env"
fi
}
# If the folder `data` or `s3_data` exists, warn the user
if [ -d "data" ] || [ -d "s3_data" ]; then
show_message "tips_already_installed"
exit 0
else
section_download_files
fi
section_configurate_host() {
DEPLOY_MODE=$ask_result
show_message "host_regenerate"
# If run in local mode, skip this step
if [[ "$DEPLOY_MODE" == "2" ]]; then
HOST="localhost:3210"
LOBE_HOST="$HOST"
return 0
fi
# Configurate protocol for domain
if [[ "$DEPLOY_MODE" == "0" ]]; then
# Ask if enable https
echo $(show_message "ask_protocol")
ask "(y/n)" "y"
if [[ "$ask_result" == "y" ]]; then
PROTOCOL="https"
# Replace all http with https
sed "${SED_INPLACE_ARGS[@]}" "s#http://#https://#" .env
fi
fi
# Check if sed is installed
if ! command -v sed "${SED_INPLACE_ARGS[@]}" &> /dev/null ; then
echo "sed" $(show_message "tips_no_executable")
exit 1
fi
# If user not specify host, try to get the server ip
if [ -z "$HOST" ]; then
HOST=$(hostname -I | awk '{print $1}')
# If the host is a private ip and the deploy mode is port mode
if [[ "$DEPLOY_MODE" == "1" ]] && ([[ "$HOST" == "192.168."* ]] || [[ "$HOST" == "172."* ]] || [[ "$HOST" == "10."* ]]); then
echo $(show_message "tips_private_ip_detected")
fi
fi
case $DEPLOY_MODE in
0)
DEPLOY_MODE="domain"
echo "LobeHub" $(show_message "ask_domain" "example.com")
ask "(example.com)"
LOBE_HOST="$ask_result"
# If user use domain mode, ask for the domain of RustFS
echo "RustFS S3 API" $(show_message "ask_domain" "s3.example.com")
ask "(s3.example.com)"
RUSTFS_HOST="$ask_result"
;;
1)
DEPLOY_MODE="ip"
ask $(printf "%s%s" "LobeHub" $(show_message "ask_host")) "$HOST" $(printf "%s" $(show_message "tips_auto_detected"))
LOBE_HOST="$ask_result"
# If user use ip mode, use ask_result as the host
HOST="$ask_result"
# If user use ip mode, append the port to the host
LOBE_HOST="${HOST}:3210"
RUSTFS_HOST="${HOST}:9000"
;;
*)
echo "Invalid deploy mode: $ask_result"
exit 1
;;
esac
# lobe host
sed "${SED_INPLACE_ARGS[@]}" "s#^APP_URL=.*#APP_URL=$PROTOCOL://$LOBE_HOST#" .env
# s3 related
sed "${SED_INPLACE_ARGS[@]}" "s#^S3_ENDPOINT=.*#S3_ENDPOINT=$PROTOCOL://$RUSTFS_HOST#" .env
# Check if env modified success
if [ $? -ne 0 ]; then
echo $(show_message "host_regenerate_failed") "$HOST in \`.env\`"
fi
}
show_message "ask_deploy_mode"
ask "(0,1,2)" "2"
if [[ "$ask_result" == "0" ]] || [[ "$ask_result" == "1" ]] || [[ "$ask_result" == "2" ]]; then
section_configurate_host
else
echo "Invalid deploy mode: $ask_result, please select 0, 1 or 2."
exit 1
fi
# ==========================
# === Regenerate Secrets ===
# ==========================
section_regenerate_secrets() {
# Check if openssl is installed
if ! command -v openssl &> /dev/null ; then
echo "openssl" $(show_message "tips_no_executable")
exit 1
fi
if ! command -v tr &> /dev/null ; then
echo "tr" $(show_message "tips_no_executable")
exit 1
fi
if ! command -v fold &> /dev/null ; then
echo "fold" $(show_message "tips_no_executable")
exit 1
fi
if ! command -v head &> /dev/null ; then
echo "head" $(show_message "tips_no_executable")
exit 1
fi
generate_key() {
if [[ -z "$1" ]]; then
echo "Usage: generate_key <length>"
return 1
fi
echo $(openssl rand -hex $1 | tr -d '\n' | fold -w $1 | head -n 1)
}
if ! command -v sed &> /dev/null ; then
echo "sed" $(show_message "tips_no_executable")
exit 1
fi
echo $(show_message "security_secrect_regenerate")
# Generate RUSTFS S3 User Password
RUSTFS_SECRET_KEY=$(generate_key 8)
if [ $? -ne 0 ]; then
echo $(show_message "security_secrect_regenerate_failed") "RUSTFS_SECRET_KEY"
RUSTFS_SECRET_KEY="YOUR_RUSTFS_PASSWORD"
else
sed "${SED_INPLACE_ARGS[@]}" "s#^RUSTFS_SECRET_KEY=.*#RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY}#" .env
if [ $? -ne 0 ]; then
echo $(show_message "security_secrect_regenerate_failed") "RUSTFS_SECRET_KEY in \`.env\`"
fi
fi
# Generate KEY_VAULTS_SECRET (base64 encoded 32 bytes)
KEY_VAULTS_SECRET=$(openssl rand -base64 32)
if [ $? -ne 0 ]; then
echo $(show_message "security_secrect_regenerate_failed") "KEY_VAULTS_SECRET"
else
sed "${SED_INPLACE_ARGS[@]}" "s#^KEY_VAULTS_SECRET=.*#KEY_VAULTS_SECRET=${KEY_VAULTS_SECRET}#" .env
if [ $? -ne 0 ]; then
echo $(show_message "security_secrect_regenerate_failed") "KEY_VAULTS_SECRET in \`.env\`"
fi
fi
# Generate AUTH_SECRET (base64 encoded 32 bytes)
AUTH_SECRET=$(openssl rand -base64 32)
if [ $? -ne 0 ]; then
echo $(show_message "security_secrect_regenerate_failed") "AUTH_SECRET"
else
sed "${SED_INPLACE_ARGS[@]}" "s#^AUTH_SECRET=.*#AUTH_SECRET=${AUTH_SECRET}#" .env
if [ $? -ne 0 ]; then
echo $(show_message "security_secrect_regenerate_failed") "AUTH_SECRET in \`.env\`"
fi
fi
}
show_message "ask_regenerate_secrets"
ask "(y/n)" "y"
if [[ "$ask_result" == "y" ]]; then
section_regenerate_secrets
fi
section_init_database() {
if ! command -v docker &> /dev/null ; then
echo "docker" $(show_message "tips_no_executable")
return 1
fi
if ! docker compose &> /dev/null ; then
echo "docker compose" $(show_message "tips_no_executable")
return 1
fi
# Check if user has permissions to run Docker by trying to get the status of Docker (docker status).
# If this fails, the user probably does not have permissions for Docker.
# ref: https://github.com/paperless-ngx/paperless-ngx/blob/89e5c08a1fe4ca0b7641ae8fbd5554502199ae40/install-paperless-ngx.sh#L64-L72
if ! docker stats --no-stream &> /dev/null ; then
echo $(show_message "tips_no_docker_permission")
return 1
fi
docker compose pull
docker compose up --detach postgresql
# hopefully enough time for even the slower systems
sleep 15
docker compose stop
}
show_message "ask_init_database"
ask "(y/n)" "y"
if [[ "$ask_result" == "y" ]]; then
# If return 1 means failed
section_init_database
if [ $? -ne 0 ]; then
echo $(show_message "tips_init_database_failed")
fi
else
show_message "tips_init_database_failed"
fi
section_display_configurated_report() {
# Display configuration reports
echo $(show_message "security_secrect_regenerate_report")
echo -e "LobeHub: \n - URL: $PROTOCOL://$LOBE_HOST"
echo -e "RustFS: \n - URL: $PROTOCOL://$RUSTFS_HOST \n - Username: admin\n - Password: ${RUSTFS_SECRET_KEY}\n"
# if user run in domain mode, diplay reverse proxy configuration
if [[ "$DEPLOY_MODE" == "domain" ]]; then
echo $(show_message "tips_add_reverse_proxy")
printf "\n%s\t->\t%s\n" "$LOBE_HOST" "127.0.0.1:3210"
printf "%s\t->\t%s\n" "$RUSTFS_HOST" "127.0.0.1:9000"
fi
# Display final message
printf "\n%s\n\n" "$(show_message "tips_run_command")"
print_centered "docker compose up --no-attach searxng" "green"
printf "\n%s\n" "$(show_message "tips_if_run_normally")"
printf "\n%s\n" "$(show_message "tips_regen_jwks")"
printf "\n%s\n\n" "$(show_message "tips_disable_registration")"
print_centered "docker compose up -d --no-attach searxng" "green"
printf "\n%s\n" "$(show_message "tips_if_want_searxng_logs")"
print_centered "docker compose logs -f searxng" "white"
printf "\n%s\n" "$(show_message "tips_allow_ports")"
printf "\n%s" "$(show_message "tips_show_documentation")"
printf "%s\n" $(show_message "tips_show_documentation_url")
}
section_display_configurated_report