本次同步 09-12 至 09-28 的代码变更,主要内容: 新增功能 - 修改密码:后端新增 ChangePasswordRequest DTO 与UserController/UserService 对应接口,前端新增 ChangePasswordDialog 并在 Header 接入入口 - 文件预览组件重构:file-preview 目录拆分出 VideoPreview(支持 blobUrl 直传)、 ImagePreview、AudioPreview,index.tsx 统一调度各类型预览器 - 新增 my_ops 算子模块:markitdown_parser、mineru_pdf_parser、text_quality_evaluator - 新增 tools/minio-file-server MinIO 文件服务 功能增强 - 数据清洗:cleaning_task_service/scheduler/routes 重构, data_juicer_executor 与 wrapper 增强,OperatorTable/LogsTable/TaskDetail 同步调整 - 数据标注:annotation sync.py、project.py 接口与schema 更新 - 数据采集、算子市场、首页与菜单路由相关页面调整 - DataJuicer 算子接入(useOperatorOperations、dj_ops_schema) 结构调整 - deepflow/ 更名为 deep-flow/(419 个文件重命名) - 新增 helm 部署编排、GitHub Actions 之外的自建镜像构建脚本 未纳入本次提交 - scripts-backup/、runtime/python-executor-backup/、components_backup/(备份目录) - *.backup*、__pycache__ 产物 - zh_core_web_sm-3.8.0-py3-none-any.whl(46MB 构建产物) - DataX/(内嵌独立仓库,远程亦未收录其源码)
249 lines
11 KiB
Bash
249 lines
11 KiB
Bash
#!/bin/bash
|
|
### Collect secrets for DeepData installation.
|
|
###
|
|
### Usage:
|
|
### scripts/k8s/collect-secrets.sh [--component deepdata|milvus] [--namespace <ns>]
|
|
###
|
|
### Modes (auto-detected):
|
|
### 1. Sealed-secrets mode: controller running + kubeseal available → encrypt → apply SealedSecret
|
|
### 2. Plain mode: no controller → Helm creates Secret with base64 (dev mode)
|
|
###
|
|
### Password priority: .env file values > interactive prompt > auto-generated
|
|
###
|
|
### Output: writes shell script variables to stdout (eval by caller)
|
|
|
|
# NOTE: NOT using 'set -e' because interactive prompts (read) can fail
|
|
# when stdin is not a TTY. We validate critical values explicitly instead.
|
|
|
|
COMPONENT="${1:-deepdata}"
|
|
NAMESPACE="${NAMESPACE:-deepdata}"
|
|
ENV_FILE="${ENV_FILE:-.env}"
|
|
|
|
# Parse --component and --namespace from args if passed differently
|
|
while [[ "$#" -gt 0 ]]; do
|
|
case $1 in
|
|
--component) COMPONENT="$2"; shift 2 ;;
|
|
--namespace|-n) NAMESPACE="$2"; shift 2 ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
TMP_DIR=$(mktemp -d)
|
|
trap "rm -rf $TMP_DIR" EXIT
|
|
|
|
# ========== Detect Mode ==========
|
|
detect_sealed_secrets() {
|
|
kubectl get deployment -n "$NAMESPACE" sealed-secrets >/dev/null 2>&1 && return 0
|
|
kubectl get deployment -n kube-system sealed-secrets >/dev/null 2>&1 && return 0
|
|
kubectl get pod -n "$NAMESPACE" -l app.kubernetes.io/name=sealed-secrets --no-headers 2>/dev/null | grep -q Running && return 0
|
|
return 1
|
|
}
|
|
|
|
KUBESEAL="$(command -v kubeseal 2>/dev/null || echo "")"
|
|
[ -z "$KUBESEAL" ] && KUBESEAL="$HOME/bin/kubeseal"
|
|
[ ! -x "$KUBESEAL" ] && [ -x "./tools/bin/kubeseal" ] && KUBESEAL="./tools/bin/kubeseal"
|
|
[ ! -x "$KUBESEAL" ] && [ -x "../tools/bin/kubeseal" ] && KUBESEAL="../tools/bin/kubeseal"
|
|
|
|
# Auto-download kubeseal if controller exists but binary is missing
|
|
if detect_sealed_secrets && [ ! -x "$KUBESEAL" ]; then
|
|
echo "[INFO] Sealed Secrets controller detected, but kubeseal not found. Downloading..." >&2
|
|
KUBESEAL_URL="https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.2/kubeseal-0.27.2-$(uname -s | tr '[:upper:]' '[:lower:]')-$(dpkg --print-architecture 2>/dev/null || uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')"
|
|
KUBESEAL_DIR="$HOME/bin"
|
|
mkdir -p "$KUBESEAL_DIR"
|
|
curl -sSL "$KUBESEAL_URL" -o "$KUBESEAL_DIR/kubeseal" && chmod +x "$KUBESEAL_DIR/kubeseal"
|
|
KUBESEAL="$KUBESEAL_DIR/kubeseal"
|
|
echo "[INFO] Installed kubeseal to $KUBESEAL" >&2
|
|
fi
|
|
|
|
if detect_sealed_secrets && [ -x "$KUBESEAL" ]; then
|
|
MODE="sealed"
|
|
else
|
|
MODE="plain"
|
|
fi
|
|
|
|
echo "# DeepData secrets collection — component: $COMPONENT, mode: $MODE" >&2
|
|
|
|
# ========== Helper ==========
|
|
random_hex() {
|
|
openssl rand -hex 16 2>/dev/null || python3 -c "import secrets;print(secrets.token_hex(16))"
|
|
}
|
|
|
|
seal_secret() {
|
|
local name="$1" output="$2"
|
|
shift 2
|
|
local raw="${TMP_DIR}/${name}-raw.yaml"
|
|
{
|
|
echo "apiVersion: v1"
|
|
echo "kind: Secret"
|
|
echo "metadata:"
|
|
echo " name: ${name}"
|
|
echo " namespace: ${NAMESPACE}"
|
|
echo "type: Opaque"
|
|
echo "stringData:"
|
|
for pair in "$@"; do
|
|
key="${pair%%=*}"
|
|
value="${pair#*=}"
|
|
echo " ${key}: \"${value}\""
|
|
done
|
|
} > "$raw"
|
|
"$KUBESEAL" --controller-name=sealed-secrets --namespace="${NAMESPACE}" -o yaml -f "$raw" > "$output"
|
|
echo "[INFO] Created SealedSecret: $output" >&2
|
|
}
|
|
|
|
# ========== Load .env (targeted: only reads known secret keys) ==========
|
|
if [ -f "$ENV_FILE" ]; then
|
|
echo "[INFO] Loading secrets from ${ENV_FILE}..." >&2
|
|
|
|
# Read .env line by line, only extract vars matching our known keys.
|
|
# Ignores comments, blank lines, and unrelated vars like pgsql_host/shell vars.
|
|
while IFS='=' read -r key value; do
|
|
# Skip blank lines and comments
|
|
[ -z "$key" ] && continue
|
|
case "$key" in
|
|
\#*) continue ;;
|
|
esac
|
|
# Strip leading/trailing whitespace from key
|
|
key="$(echo "$key" | xargs)"
|
|
|
|
case "$key" in
|
|
DB_PASSWORD|CERT_PASS|DOMAIN|HOME_PAGE_URL|JWT_SECRET|\
|
|
LABEL_STUDIO_PASSWORD|LABEL_STUDIO_USER_TOKEN|POSTGRE_PASSWORD|\
|
|
MINIO_ACCESS_KEY|MINIO_SECRET_KEY)
|
|
val="$value"
|
|
case "$val" in
|
|
\'*|\"*) val="${val:1}" ;;
|
|
esac
|
|
case "$val" in
|
|
*\'|*\") val="${val:0:-1}" ;;
|
|
esac
|
|
printf -v "$key" '%s' "$val"
|
|
;;
|
|
esac
|
|
done < "$ENV_FILE"
|
|
|
|
[ -n "$DB_PASSWORD" ] && echo "[INFO] Loaded DB_PASSWORD from $ENV_FILE" >&2
|
|
[ -n "$CERT_PASS" ] && echo "[INFO] Loaded CERT_PASS from $ENV_FILE" >&2
|
|
[ -n "$JWT_SECRET" ] && echo "[INFO] Loaded JWT_SECRET from $ENV_FILE" >&2
|
|
else
|
|
echo "[WARN] No .env file found at $(pwd)/${ENV_FILE} — will prompt for secrets" >&2
|
|
fi
|
|
|
|
# ========== DeepData ==========
|
|
if [ "$COMPONENT" = "deepdata" ]; then
|
|
# Collect
|
|
[ -z "$DB_PASSWORD" ] && read -rsp "Enter DB_PASSWORD: " DB_PASSWORD && echo "" >&2
|
|
[ -z "$JWT_SECRET" ] && JWT_SECRET=$(openssl rand -hex 32 2>/dev/null || python3 -c "import secrets;print(secrets.token_hex(32))") && echo "[INFO] Auto-generated JWT_SECRET" >&2
|
|
[ -z "$DOMAIN" ] && read -rp "Enter DOMAIN (enter to skip): " DOMAIN >&2
|
|
[ -z "$CERT_PASS" ] && read -rsp "Enter CERT_PASS (enter to skip): " CERT_PASS && echo "" >&2
|
|
HOME_PAGE_URL="${HOME_PAGE_URL:-/data/management}"
|
|
[ -z "$LABEL_STUDIO_PASSWORD" ] && read -rsp "Enter LABEL_STUDIO_PASSWORD (enter to skip): " LABEL_STUDIO_PASSWORD && echo "" >&2
|
|
[ -z "$LABEL_STUDIO_USER_TOKEN" ] && LABEL_STUDIO_USER_TOKEN=$(random_hex)$(random_hex) && echo "[INFO] Auto-generated LABEL_STUDIO_USER_TOKEN" >&2
|
|
|
|
# ===== MANDATORY CHECK: DB_PASSWORD must not be empty =====
|
|
if [ -z "$DB_PASSWORD" ]; then
|
|
echo "[FATAL] DB_PASSWORD is empty! Cannot install without a database password." >&2
|
|
echo "[FATAL] Set DB_PASSWORD in .env or ensure interactive prompts work." >&2
|
|
echo "SECRETS_CREATE=SKIP"
|
|
echo "HELM_VALUES_FILE="
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$MODE" = "sealed" ]; then
|
|
# Clean up any old stale Secret (prevents Helm conflict)
|
|
if kubectl get secret deepdata-conf -n "$NAMESPACE" >/dev/null 2>&1; then
|
|
echo "[INFO] Removing old deepdata-conf Secret..." >&2
|
|
kubectl delete secret deepdata-conf -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1
|
|
fi
|
|
# Clean up old SealedSecret (apply is idempotent, but belt-and-suspenders)
|
|
kubectl delete sealedsecret deepdata-conf -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
|
|
|
|
# deepdata-conf
|
|
seal_secret "deepdata-conf" "${TMP_DIR}/deepdata-sealed.yaml" \
|
|
"DB_PASSWORD=${DB_PASSWORD}" \
|
|
"CERT_PASS=${CERT_PASS}" \
|
|
"DOMAIN=${DOMAIN}" \
|
|
"HOME_PAGE_URL=${HOME_PAGE_URL}" \
|
|
"JWT_SECRET=${JWT_SECRET}" \
|
|
"LABEL_STUDIO_PASSWORD=${LABEL_STUDIO_PASSWORD}" \
|
|
"LABEL_STUDIO_USER_TOKEN=${LABEL_STUDIO_USER_TOKEN}"
|
|
kubectl apply -f "${TMP_DIR}/deepdata-sealed.yaml" -n "$NAMESPACE" >/dev/null
|
|
# Add Helm ownership labels so 'helm install --force' accepts the Secret
|
|
kubectl wait --for=jsonpath='{.data.DB_PASSWORD}' --timeout=30s secret/deepdata-conf -n "$NAMESPACE" >/dev/null 2>&1 || true
|
|
kubectl annotate secret deepdata-conf -n "$NAMESPACE" \
|
|
meta.helm.sh/release-name=deepdata \
|
|
meta.helm.sh/release-namespace="${NAMESPACE}" \
|
|
--overwrite >/dev/null 2>&1
|
|
kubectl label secret deepdata-conf -n "$NAMESPACE" \
|
|
app.kubernetes.io/managed-by=Helm \
|
|
--overwrite >/dev/null 2>&1
|
|
|
|
# Label Studio
|
|
if [ -n "$LABEL_STUDIO_PASSWORD" ]; then
|
|
POSTGRE_PASSWORD="${POSTGRE_PASSWORD:-$DB_PASSWORD}"
|
|
seal_secret "label-studio-env" "${TMP_DIR}/label-studio-sealed.yaml" \
|
|
"POSTGRE_PASSWORD=${POSTGRE_PASSWORD}" \
|
|
"LABEL_STUDIO_PASSWORD=${LABEL_STUDIO_PASSWORD}" \
|
|
"LABEL_STUDIO_USER_TOKEN=${LABEL_STUDIO_USER_TOKEN}"
|
|
kubectl apply -f "${TMP_DIR}/label-studio-sealed.yaml" -n "$NAMESPACE" >/dev/null
|
|
fi
|
|
|
|
echo "SECRETS_CREATE=false"
|
|
echo "HELM_VALUES_FILE="
|
|
else
|
|
# Plain — delete any old SealedSecret and stale Secret first
|
|
kubectl delete sealedsecret deepdata-conf -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
|
|
if kubectl get secret deepdata-conf -n "$NAMESPACE" >/dev/null 2>&1; then
|
|
echo "[INFO] Removing old deepdata-conf Secret..." >&2
|
|
kubectl delete secret deepdata-conf -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1
|
|
fi
|
|
|
|
# Write extra values to /tmp (survives script exit for Helm to read)
|
|
values_file="/tmp/deepdata-secret-values-$$.yaml"
|
|
cat > "$values_file" <<VALUES_EOF
|
|
public:
|
|
secrets:
|
|
data:
|
|
DB_PASSWORD: "${DB_PASSWORD}"
|
|
JWT_SECRET: "${JWT_SECRET}"
|
|
CERT_PASS: "${CERT_PASS}"
|
|
DOMAIN: "${DOMAIN}"
|
|
HOME_PAGE_URL: "${HOME_PAGE_URL}"
|
|
LABEL_STUDIO_PASSWORD: "${LABEL_STUDIO_PASSWORD}"
|
|
LABEL_STUDIO_USER_TOKEN: "${LABEL_STUDIO_USER_TOKEN}"
|
|
VALUES_EOF
|
|
|
|
echo "[INFO] Wrote plain secret values to ${values_file}" >&2
|
|
echo "SECRETS_CREATE=true"
|
|
echo "HELM_VALUES_FILE=${values_file}"
|
|
fi
|
|
|
|
# ========== Milvus ==========
|
|
elif [ "$COMPONENT" = "milvus" ]; then
|
|
# Only needed if secret doesn't already exist
|
|
if kubectl get secret milvus-minio-secret -n "$NAMESPACE" >/dev/null 2>&1; then
|
|
echo "# milvus-minio-secret already exists — skipping" >&2
|
|
exit 0
|
|
fi
|
|
|
|
[ -z "$MINIO_ACCESS_KEY" ] && MINIO_ACCESS_KEY=$(random_hex) && echo "[INFO] Auto-generated MINIO_ACCESS_KEY" >&2
|
|
[ -z "$MINIO_SECRET_KEY" ] && MINIO_SECRET_KEY=$(random_hex)$(random_hex) && echo "[INFO] Auto-generated MINIO_SECRET_KEY" >&2
|
|
|
|
if [ "$MODE" = "sealed" ]; then
|
|
seal_secret "milvus-minio-secret" "${TMP_DIR}/milvus-sealed.yaml" \
|
|
"accesskey=${MINIO_ACCESS_KEY}" \
|
|
"secretkey=${MINIO_SECRET_KEY}"
|
|
kubectl apply -f "${TMP_DIR}/milvus-sealed.yaml" -n "$NAMESPACE" >/dev/null
|
|
kubectl wait --for=jsonpath='{.data.accesskey}' --timeout=30s secret/milvus-minio-secret -n "$NAMESPACE" >/dev/null 2>&1 || true
|
|
kubectl annotate secret milvus-minio-secret -n "$NAMESPACE" \
|
|
meta.helm.sh/release-name=milvus-minio \
|
|
meta.helm.sh/release-namespace="${NAMESPACE}" \
|
|
--overwrite >/dev/null 2>&1
|
|
else
|
|
kubectl create secret generic milvus-minio-secret \
|
|
--from-literal=accesskey="$MINIO_ACCESS_KEY" \
|
|
--from-literal=secretkey="$MINIO_SECRET_KEY" \
|
|
-n "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f - >/dev/null
|
|
echo "[INFO] Created milvus-minio-secret (plain)" >&2
|
|
fi
|
|
fi
|