Files
geo/deploy/scripts/load-and-start.sh
root bdad050a8b
Deployment Config CI / Deployment Config (push) Successful in 12s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 30s
Desktop Client Build / Resolve Build Metadata (push) Successful in 26s
Desktop Client Build / Build Desktop Client (push) Successful in 22m8s
feat(desktop): add type declaration for @brand-logo alias in brand-logo.d.ts
2026-05-31 13:38:37 +08:00

338 lines
11 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# load-and-start.sh — 在【离线目标服务器】上运行
# 加载镜像并使用 Docker Compose 或 k3s 启动服务
#
# 用法(解压部署包后):
# bash deploy.sh compose # 默认,Docker Compose 部署
# bash deploy.sh k3s # k3s / Kubernetes 部署
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MODE="${1:-compose}"
# 读取打包时记录的元数据
if [[ -f "${SCRIPT_DIR}/.package-meta" ]]; then
source "${SCRIPT_DIR}/.package-meta"
fi
IMAGE_TAG="${IMAGE_TAG:-latest}"
LOCAL_PREFIX="${LOCAL_PREFIX:-geo-rankly}"
# ─── 检查 .env ────────────────────────────────────────────────────────────────
if [[ ! -f "${SCRIPT_DIR}/.env" ]]; then
echo "==> 未找到 .env,已从 .env.example 自动创建。"
cp "${SCRIPT_DIR}/.env.example" "${SCRIPT_DIR}/.env"
echo " 如需填写真实 API Key 或修改端口,可编辑 ${SCRIPT_DIR}/.env 后再次执行 bash deploy.sh"
fi
ensure_local_config() {
local file="$1" example="$2"
if [[ -f "${SCRIPT_DIR}/${file}" ]]; then
return
fi
if [[ -f "${SCRIPT_DIR}/${example}" ]]; then
cp "${SCRIPT_DIR}/${example}" "${SCRIPT_DIR}/${file}"
echo "==> 已从 ${example} 创建 ${file}"
echo " 生产多副本部署前请把其中的 password_cipher.private_key_pem 替换为同一把 RSA 私钥。"
fi
}
ensure_config_aliases() {
local yaml yml
for yaml in config.yaml config.local.yaml ops-config.yaml ops-config.local.yaml; do
yml="${yaml%.yaml}.yml"
if [[ ! -f "${SCRIPT_DIR}/${yaml}" && -f "${SCRIPT_DIR}/${yml}" ]]; then
cp "${SCRIPT_DIR}/${yml}" "${SCRIPT_DIR}/${yaml}"
echo "==> 已从 ${yml} 创建 ${yaml},供部署清单挂载使用。"
fi
done
if [[ ! -f "${SCRIPT_DIR}/prompts.yml" && -f "${SCRIPT_DIR}/prompts.yaml" ]]; then
cp "${SCRIPT_DIR}/prompts.yaml" "${SCRIPT_DIR}/prompts.yml"
echo "==> 已从 prompts.yaml 创建 prompts.yml,供部署清单挂载使用。"
fi
}
env_value() {
local key="$1" default_value="$2" value
value="$(grep -E "^${key}=" "${SCRIPT_DIR}/.env" | tail -1 | cut -d= -f2- || true)"
value="${value%\"}"
value="${value#\"}"
printf '%s' "${value:-${default_value}}"
}
config_file_path() {
local base="$1"
for file in "${SCRIPT_DIR}/${base}.yaml" "${SCRIPT_DIR}/${base}.yml"; do
if [[ -f "${file}" ]]; then
printf '%s' "${file}"
return
fi
done
}
config_file_path_in_dir() {
local dir="$1" base="$2"
for file in "${dir}/${base}.yaml" "${dir}/${base}.yml"; do
if [[ -f "${file}" ]]; then
printf '%s' "${file}"
return
fi
done
}
read_object_storage_provider_from_files() {
local provider="minio" file
for file in "$@"; do
if [[ -z "${file}" || ! -f "${file}" ]]; then
continue
fi
local next
next="$(
awk '
/^[[:space:]]*object_storage:[[:space:]]*$/ {
in_section=1
base=match($0, /[^[:space:]]/) - 1
next
}
in_section {
indent=match($0, /[^[:space:]]/) - 1
if ($0 ~ /^[[:space:]]*$/ || $0 ~ /^[[:space:]]*#/) {
next
}
if (indent <= base) {
in_section=0
next
}
if ($0 ~ /^[[:space:]]*provider[[:space:]]*:/) {
value=$0
sub(/^[[:space:]]*provider[[:space:]]*:[[:space:]]*/, "", value)
sub(/[[:space:]]+#.*$/, "", value)
gsub(/^["'\'']|["'\'']$/, "", value)
print value
}
}
' "${file}" | tail -1
)"
if [[ -n "${next}" ]]; then
provider="${next}"
fi
done
printf '%s' "$(printf '%s' "${provider}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
}
read_object_storage_provider() {
read_object_storage_provider_from_files "$(config_file_path config)" "$(config_file_path config.local)"
}
read_k3s_object_storage_provider() {
read_object_storage_provider_from_files \
"$(config_file_path_in_dir "${SCRIPT_DIR}/k3s/config" config)" \
"$(config_file_path config.local)"
}
use_minio_provider() {
local provider="$1"
case "${provider}" in
""|minio|mino)
return 0
;;
aliyun|aliyun_oss|aliyun-oss|oss|r2|cloudflare_r2|cloudflare-r2|s3|aws_s3|aws-s3|custom|custom_s3|custom-s3)
return 1
;;
*)
return 0
;;
esac
}
use_minio_for_object_storage() {
use_minio_provider "$(read_object_storage_provider)"
}
sync_k3s_config() {
mkdir -p "${SCRIPT_DIR}/k3s/config"
for name in config ops-config prompts; do
local output="${name}.yaml"
if [[ "${name}" == "prompts" ]]; then
output="prompts.yml"
fi
if [[ -f "${SCRIPT_DIR}/k3s/config/${output}" ]]; then
continue
fi
local source=""
for candidate in "${SCRIPT_DIR}/${name}.yaml" "${SCRIPT_DIR}/${name}.yml"; do
if [[ -f "${candidate}" ]]; then
source="${candidate}"
break
fi
done
if [[ -n "${source}" ]]; then
cp "${source}" "${SCRIPT_DIR}/k3s/config/${output}"
fi
done
}
prepare_k3s_runtime_overlay() {
local base_dir="$1"
local runtime_dir="${SCRIPT_DIR}/.k3s-runtime"
rm -rf "${runtime_dir}"
mkdir -p "${runtime_dir}"
cp "$(config_file_path config.local)" "${runtime_dir}/config.local.yaml"
cp "$(config_file_path ops-config.local)" "${runtime_dir}/ops-config.local.yaml"
cat > "${runtime_dir}/secret.env" <<EOF
POSTGRES_PASSWORD=$(env_value POSTGRES_PASSWORD geo_dev)
RABBITMQ_DEFAULT_PASS=$(env_value RABBITMQ_DEFAULT_PASS geo_dev)
MINIO_ROOT_USER=$(env_value MINIO_ROOT_USER minioadmin)
MINIO_ROOT_PASSWORD=$(env_value MINIO_ROOT_PASSWORD minioadmin)
JWT_SECRET=$(env_value JWT_SECRET geo-web-change-me-in-production)
OPS_JWT_SECRET=$(env_value OPS_JWT_SECRET geo-ops-change-me-in-production)
OPS_DEFAULT_ADMIN_PASSWORD=$(env_value OPS_DEFAULT_ADMIN_PASSWORD Aa123456)
SCHEDULER_INTERNAL_METRICS_TOKEN=$(env_value SCHEDULER_INTERNAL_METRICS_TOKEN geo-metrics-token)
BROWSER_FETCH_TOKEN=$(env_value BROWSER_FETCH_TOKEN geo-browser-fetch-change-me)
EOF
cat > "${runtime_dir}/kustomization.yaml" <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../$(basename "${base_dir}")
secretGenerator:
- name: geo-rankly-app-secret
behavior: merge
envs:
- secret.env
files:
- config.local.yaml=config.local.yaml
- ops-config.local.yaml=ops-config.local.yaml
generatorOptions:
disableNameSuffixHash: true
EOF
printf '%s' "${runtime_dir}"
}
deploy_compose() {
ensure_config_aliases
ensure_local_config "config.local.yaml" "config.local.yaml.example"
ensure_local_config "ops-config.local.yaml" "ops-config.local.yaml.example"
local compose_files=("-f" "docker-compose.yaml" "-f" "docker-compose.offline.yaml")
local minio_enabled="true"
if ! use_minio_for_object_storage; then
compose_files+=("-f" "docker-compose.aliyun-oss.yaml")
minio_enabled="false"
fi
# ─── Step 1: 加载镜像 ───────────────────────────────────────────────────────
echo "==> [1/3] 加载 Docker 镜像(可能需要几分钟)..."
docker load -i "${SCRIPT_DIR}/images.tar"
echo " 镜像加载完成"
docker images | grep -E "geo-rankly|postgres|rabbitmq|redis|qdrant|minio" | head -20
# ─── Step 2: 设置 IMAGE_REGISTRY 为本地前缀 ────────────────────────────────
echo "==> [2/3] 配置离线镜像引用..."
export IMAGE_REGISTRY="${LOCAL_PREFIX}"
export IMAGE_TAG="${IMAGE_TAG}"
# ─── Step 3: 启动服务 ───────────────────────────────────────────────────────
echo "==> [3/3] 启动所有服务..."
if [[ "${minio_enabled}" == "true" ]]; then
echo " object_storage.provider=$(read_object_storage_provider),将部署 MinIO。"
else
echo " object_storage.provider=$(read_object_storage_provider),将跳过 MinIO。"
export COMPOSE_PROFILES=""
docker rm -f geo-minio geo-minio-init >/dev/null 2>&1 || true
fi
cd "${SCRIPT_DIR}"
docker compose "${compose_files[@]}" --env-file .env up -d
echo ""
echo "✅ Docker Compose 部署完成!"
echo ""
echo "服务状态:"
docker compose "${compose_files[@]}" ps
echo ""
echo "查看日志: docker compose ${compose_files[*]} logs -f tenant-api"
echo "停止服务: docker compose ${compose_files[*]} down"
echo "管理界面:"
echo " Tenant Web: http://<server-ip>:$(env_value FRONTEND_PORT 18080)"
echo " Ops Web: http://<server-ip>:$(env_value OPS_WEB_PORT 18082)"
echo " RabbitMQ: http://<server-ip>:$(env_value RABBITMQ_MGMT_PORT 15673) (geo / geo_dev)"
if [[ "${minio_enabled}" == "true" ]]; then
echo " MinIO: http://<server-ip>:$(env_value MINIO_CONSOLE_PORT 19001) ($(env_value MINIO_ROOT_USER minioadmin) / $(env_value MINIO_ROOT_PASSWORD minioadmin))"
fi
}
deploy_k3s() {
local kubectl_cmd="kubectl"
local kustomize_dir="${SCRIPT_DIR}/k3s"
local minio_enabled="true"
ensure_config_aliases
ensure_local_config "config.local.yaml" "config.local.yaml.example"
ensure_local_config "ops-config.local.yaml" "ops-config.local.yaml.example"
sync_k3s_config
local object_storage_provider
object_storage_provider="$(read_k3s_object_storage_provider)"
if ! use_minio_provider "${object_storage_provider}"; then
kustomize_dir="${SCRIPT_DIR}/k3s-aliyun-oss"
minio_enabled="false"
fi
if command -v k3s >/dev/null 2>&1; then
echo "==> [1/3] 导入镜像到 k3s containerd..."
sudo k3s ctr images import "${SCRIPT_DIR}/images.tar"
kubectl_cmd="sudo k3s kubectl"
elif command -v kubectl >/dev/null 2>&1 && command -v ctr >/dev/null 2>&1; then
echo "==> [1/3] 导入镜像到 containerd..."
sudo ctr -n k8s.io images import "${SCRIPT_DIR}/images.tar"
else
echo "❌ 未找到 k3s/kubectl/ctr。请先安装 k3s,或改用:bash deploy.sh compose"
exit 1
fi
echo "==> [2/3] 应用 k3s 清单..."
kustomize_dir="$(prepare_k3s_runtime_overlay "${kustomize_dir}")"
if [[ "${minio_enabled}" == "true" ]]; then
echo " object_storage.provider=${object_storage_provider},将部署 MinIO。"
else
echo " object_storage.provider=${object_storage_provider},将跳过 MinIO。"
${kubectl_cmd} -n geo-rankly delete statefulset minio --ignore-not-found
${kubectl_cmd} -n geo-rankly delete service minio --ignore-not-found
${kubectl_cmd} -n geo-rankly delete job minio-init --ignore-not-found
for svc in tenant-api worker-generate kol-assist-worker scheduler; do
${kubectl_cmd} -n geo-rankly set env "deployment/${svc}" OBJECT_STORAGE_ACCESS_KEY- OBJECT_STORAGE_SECRET_KEY- >/dev/null 2>&1 || true
done
fi
${kubectl_cmd} apply -k "${kustomize_dir}"
echo "==> [3/3] 查看服务状态..."
${kubectl_cmd} -n geo-rankly get pods,svc
echo ""
echo "✅ k3s 部署命令已执行。等待 Pod 全部 Running"
echo " ${kubectl_cmd} -n geo-rankly get pods -w"
echo ""
echo "查看日志:"
echo " ${kubectl_cmd} -n geo-rankly logs deploy/tenant-api -f"
}
case "${MODE}" in
compose|docker-compose|docker)
deploy_compose
;;
k3s|kubernetes)
deploy_k3s
;;
*)
echo "用法:bash deploy.sh [compose|k3s]"
exit 1
;;
esac