#!/usr/bin/env bash set -Eeuo pipefail umask 027 LOG_LOCALE=${KAIDI_LOG_LOCALE:-zh-CN} localize_message() { local message=$1 [ "$LOG_LOCALE" = en ] && { printf '%s' "$message"; return; } case "$message" in "Configuration file is not a regular file: "*) printf '配置文件不是普通文件:%s' "${message#Configuration file is not a regular file: }" ;; "Startup user cannot read configuration file: "*) printf '启动用户无法读取配置文件:%s' "${message#Startup user cannot read configuration file: }" ;; "Configuration file is too large: "*) printf '配置文件过大:%s' "${message#Configuration file is too large: }" ;; "Configuration file contains an invalid line: "*) printf '配置文件包含无效行:%s' "${message#Configuration file contains an invalid line: }" ;; "app.jar is missing from "*) printf '缺少 app.jar:%s' "${message#app.jar is missing from }" ;; "public/index.html is missing from "*) printf '缺少前端入口 public/index.html:%s' "${message#public/index.html is missing from }" ;; "VERSION is missing from "*) printf '缺少 VERSION:%s' "${message#VERSION is missing from }" ;; "Java 17 or newer was not found") printf '未找到 Java 17 或更高版本' ;; "Java executable is not available at "*) printf 'Java 可执行文件不可用:%s' "${message#Java executable is not available at }" ;; "Java executable "*" is not available") printf 'Java 可执行文件不可用' ;; "Java 17 or newer is required") printf '需要 Java 17 或更高版本' ;; *" is missing from the protected Kaidi configuration") printf '受保护的 Kaidi 配置缺少:%s' "${message% is missing from the protected Kaidi configuration}" ;; "Storage directory "*" does not exist") printf '存储目录不存在:%s' "${message#Storage directory }" ;; "Startup user cannot write storage directory "*) printf '启动用户无法写入存储目录:%s' "${message#Startup user cannot write storage directory }" ;; "PID directory does not exist") printf 'PID 目录不存在' ;; "Startup user cannot write the PID directory") printf '启动用户无法写入 PID 目录' ;; "KAIDI_PID_FILE must be an absolute path") printf 'KAIDI_PID_FILE 必须是绝对路径' ;; "PID file must not be a symbolic link") printf 'PID 文件不能是符号链接' ;; "Process start time could not be read"*) printf '无法读取进程启动时间' ;; *) printf '%s' "$message" ;; esac } die() { printf '[kaidi-baota] 错误:%s\n' "$(localize_message "$1")" >&2 exit 1 } SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) RELEASE_ROOT=$(cd "$SCRIPT_DIR/.." && pwd -P) APP_JAR="$RELEASE_ROOT/app.jar" PUBLIC_INDEX="$RELEASE_ROOT/public/index.html" VERSION_FILE="$RELEASE_ROOT/VERSION" CONFIG_FILE=${KAIDI_CONFIG_FILE:-/etc/kaidi/kaidi.env} RUNTIME_ENV_FILE=${KAIDI_RUNTIME_ENV_FILE:-/var/lib/kaidi/setup/application.env} decode_env_value() { local raw=$1 result='' char next index=0 length if [[ "$raw" == \"*\" && ${#raw} -ge 2 ]]; then raw=${raw:1:${#raw}-2} length=${#raw} while [ "$index" -lt "$length" ]; do char=${raw:index:1} if [ "$char" = "\\" ] && [ $((index + 1)) -lt "$length" ]; then next=${raw:index+1:1} if [ "$next" = "\\" ] || [ "$next" = '"' ]; then result+="$next" index=$((index + 2)) continue fi fi result+="$char" index=$((index + 1)) done printf '%s' "$result" elif [[ "$raw" == \'*\' && ${#raw} -ge 2 ]]; then printf '%s' "${raw:1:${#raw}-2}" else printf '%s' "$raw" fi } is_managed_env_name() { case "$1" in SPRING_PROFILES_ACTIVE|SERVER_ADDRESS|SERVER_PORT|SESSION_COOKIE_SECURE|\ DB_URL|DB_USERNAME|DB_PASSWORD|FIELD_ENCRYPTION_KEY|\ FILE_STORAGE_ROOT|FILE_STORAGE_TEMP|FILE_SCANNER_ENABLED|FILE_SCANNER_HOST|FILE_SCANNER_PORT|\ FINANCE_BOOTSTRAP_ENABLED|FINANCE_BOOTSTRAP_PASSWORD|FINANCE_SETUP_ENABLED|\ FINANCE_SETUP_TOKEN_SHA256|FINANCE_SETUP_ENV_FILE|FINANCE_SETUP_MARKER_FILE|\ FINANCE_SETUP_RESTART_AFTER_COMPLETE|FINANCE_UPDATE_ENABLED|\ UPDATE_RELEASE_BASE_URL|UPDATE_RELEASE_API_URL|UPDATE_RELEASE_TOKEN|\ UPDATE_REQUEST_FILE|UPDATE_STATUS_FILE|UPDATE_CURRENT_VERSION_FILE|APP_VERSION|KAIDI_PID_FILE|KAIDI_JAVA_BIN) return 0 ;; *) return 1 ;; esac } load_env_file() { local file=$1 line name raw value size [ -e "$file" ] || return 0 [ -f "$file" ] && [ ! -L "$file" ] || die "Configuration file is not a regular file: $file" [ -r "$file" ] || die "Startup user cannot read configuration file: $file" size=$(wc -c < "$file" | tr -d '[:space:]') [ "$size" -le 65536 ] || die "Configuration file is too large: $file" while IFS= read -r line || [ -n "$line" ]; do case "$line" in ''|'#'*) continue ;; esac [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]] \ || die "Configuration file contains an invalid line: $file" name=${BASH_REMATCH[1]} raw=${BASH_REMATCH[2]} is_managed_env_name "$name" || continue [ -n "${!name:-}" ] && continue value=$(decode_env_value "$raw") printf -v "$name" '%s' "$value" export "${name?}" done < "$file" } [ -s "$APP_JAR" ] || die "app.jar is missing from $RELEASE_ROOT" [ -s "$PUBLIC_INDEX" ] || die "public/index.html is missing from $RELEASE_ROOT" [ -s "$VERSION_FILE" ] || die "VERSION is missing from $RELEASE_ROOT" # The setup-generated runtime file has precedence over the base file. Non-empty # variables supplied by Baota have precedence over both; empty panel fields do not # mask the protected configuration written by the setup wizard. The systemd unit # loads both files itself as root, then sets KAIDI_ENV_PRELOADED so the service # user never needs traversal permission for /etc/kaidi (which is intentionally # root-only). if [ "${KAIDI_ENV_PRELOADED:-false}" != true ]; then load_env_file "$RUNTIME_ENV_FILE" load_env_file "$CONFIG_FILE" fi JAVA_BIN=${KAIDI_JAVA_BIN:-} if [ -z "$JAVA_BIN" ] && [ -n "${JAVA_HOME:-}" ]; then JAVA_BIN="$JAVA_HOME/bin/java" fi if [ -z "$JAVA_BIN" ] && [ -x /opt/kaidi/runtime/java/bin/java ]; then # Compatibility for installations created before external Java paths were persisted. JAVA_BIN=/opt/kaidi/runtime/java/bin/java fi if [ -z "$JAVA_BIN" ]; then JAVA_BIN=$(command -v java 2>/dev/null || true) fi [ -n "$JAVA_BIN" ] || die "Java 17 or newer was not found" if [[ "$JAVA_BIN" == */* ]]; then [ -x "$JAVA_BIN" ] || die "Java executable is not available at $JAVA_BIN" else command -v "$JAVA_BIN" >/dev/null 2>&1 || die "Java executable $JAVA_BIN is not available" fi JAVA_VERSION=$("$JAVA_BIN" -version 2>&1 | sed -n 's/.*version "\([0-9][0-9]*\).*/\1/p' | head -n 1) [[ "$JAVA_VERSION" =~ ^[0-9]+$ ]] && [ "$JAVA_VERSION" -ge 17 ] \ || die "Java 17 or newer is required" export SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-production} export SERVER_ADDRESS=${SERVER_ADDRESS:-127.0.0.1} export SERVER_PORT=${SERVER_PORT:-18080} export FINANCE_SETUP_ENABLED=${FINANCE_SETUP_ENABLED:-false} export FINANCE_BOOTSTRAP_ENABLED=${FINANCE_BOOTSTRAP_ENABLED:-false} export FINANCE_UPDATE_ENABLED=${FINANCE_UPDATE_ENABLED:-true} export FILE_SCANNER_ENABLED=${FILE_SCANNER_ENABLED:-false} export FINANCE_STATIC_LOCATIONS=${FINANCE_STATIC_LOCATIONS:-file:$RELEASE_ROOT/public/} export UPDATE_CURRENT_VERSION_FILE=${UPDATE_CURRENT_VERSION_FILE:-$VERSION_FILE} export APP_VERSION=${APP_VERSION:-$(tr -d '\r\n' < "$VERSION_FILE")} export KAIDI_PID_FILE=${KAIDI_PID_FILE:-/var/lib/kaidi/kaidi.pid} if [ "$FINANCE_SETUP_ENABLED" != true ]; then for name in DB_URL DB_USERNAME DB_PASSWORD FIELD_ENCRYPTION_KEY FILE_STORAGE_ROOT FILE_STORAGE_TEMP; do [ -n "${!name:-}" ] || die "$name is missing from the protected Kaidi configuration" done for directory in "$FILE_STORAGE_ROOT" "$FILE_STORAGE_TEMP"; do [ -d "$directory" ] || die "Storage directory $directory does not exist" [ -w "$directory" ] || die "Startup user cannot write storage directory $directory" done fi case "$KAIDI_PID_FILE" in /*) ;; *) die "KAIDI_PID_FILE must be an absolute path" ;; esac [ -d "$(dirname "$KAIDI_PID_FILE")" ] || die "PID directory does not exist" [ -w "$(dirname "$KAIDI_PID_FILE")" ] || die "Startup user cannot write the PID directory" [ ! -L "$KAIDI_PID_FILE" ] || die "PID file must not be a symbolic link" PID_START_TIME=$(awk '{print $22}' "/proc/$$/stat" 2>/dev/null || true) [[ "$PID_START_TIME" =~ ^[0-9]+$ ]] || die "Process start time could not be read from /proc" PID_TEMP="${KAIDI_PID_FILE}.next.$$" printf '%s %s\n' "$$" "$PID_START_TIME" > "$PID_TEMP" chmod 0640 "$PID_TEMP" mv -f "$PID_TEMP" "$KAIDI_PID_FILE" cd "$RELEASE_ROOT" exec "$JAVA_BIN" -XX:MaxRAMPercentage=70 -Dfile.encoding=UTF-8 \ "-Dkaidi.release.path=$RELEASE_ROOT" "-Dkaidi.release.version=$APP_VERSION" \ -jar "$APP_JAR"