#!/usr/bin/env bash set -Eeuo pipefail export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}" CONFIG_FILE="${CONFIG_FILE:-/etc/tiku-saas/deploy.env}" if [[ ! -r "$CONFIG_FILE" ]]; then echo "Missing deploy config: $CONFIG_FILE" >&2 exit 1 fi # shellcheck disable=SC1090 source "$CONFIG_FILE" : "${GIT_REPO:?GIT_REPO is required}" : "${GIT_BRANCH:=main}" : "${APP_ROOT:=/opt/tiku-saas}" : "${REPO_DIR:=$APP_ROOT/repo}" : "${WWW_ROOT:=/srv/tiku-saas/www}" : "${RUNTIME_CONFIG_DIR:=/etc/tiku-saas/runtime-config}" : "${RUN_SECURITY_CHECKS:=true}" : "${RUN_LAUNCH_GATE:=true}" : "${RESTART_SERVICES:=true}" LOCK_FILE="${LOCK_FILE:-/tmp/tiku-saas-deploy.lock}" mkdir -p "$APP_ROOT" "$WWW_ROOT/student" "$WWW_ROOT/tenant-admin" "$WWW_ROOT/platform-admin" exec 9>"$LOCK_FILE" if ! flock -n 9; then echo "Another deployment is already running." >&2 exit 1 fi log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" } cleanup() { if [[ -n "${ASKPASS_FILE:-}" && -f "$ASKPASS_FILE" ]]; then rm -f "$ASKPASS_FILE" fi } trap cleanup EXIT prepare_git_auth() { if [[ -n "${GIT_SSH_COMMAND:-}" ]]; then export GIT_SSH_COMMAND return fi if [[ -z "${GIT_USERNAME:-}" || -z "${GITEA_TOKEN:-}" ]]; then return fi export GIT_USERNAME export GITEA_TOKEN ASKPASS_FILE="$(mktemp)" chmod 700 "$ASKPASS_FILE" cat > "$ASKPASS_FILE" <<'ASKPASS' #!/usr/bin/env bash case "$1" in *Username*) printf '%s\n' "$GIT_USERNAME" ;; *Password*) printf '%s\n' "$GITEA_TOKEN" ;; *) printf '\n' ;; esac ASKPASS export GIT_ASKPASS="$ASKPASS_FILE" export GIT_TERMINAL_PROMPT=0 } prepare_git_auth if [[ ! -d "$REPO_DIR/.git" ]]; then log "Cloning repository..." git clone --branch "$GIT_BRANCH" "$GIT_REPO" "$REPO_DIR" fi cd "$REPO_DIR" log "Fetching $GIT_BRANCH..." git fetch origin "$GIT_BRANCH" --prune git checkout "$GIT_BRANCH" if ! git diff --quiet || ! git diff --cached --quiet; then echo "Repository has local changes. Refusing to deploy until the server checkout is clean." >&2 exit 1 fi git merge --ff-only "origin/$GIT_BRANCH" CURRENT_SHA="$(git rev-parse --short=12 HEAD)" log "Deploying commit $CURRENT_SHA" log "Installing dependencies with npm ci..." npm ci if [[ "$RUN_SECURITY_CHECKS" == "true" ]]; then log "Running repository security scan..." npm run security:repo fi if [[ "$RUN_LAUNCH_GATE" == "true" ]]; then log "Running production launch gate test..." node scripts/production-launch-gate-test.js fi log "Building API and worker..." npm run build:api npm run build:worker log "Building H5 portals..." npm run build:taro:h5:student npm run build:taro:h5:tenant npm run build:taro:h5:platform log "Publishing H5 static assets..." rsync -a --delete apps/taro/dist/h5-student/ "$WWW_ROOT/student/" rsync -a --delete apps/taro/dist/h5-tenant-admin/ "$WWW_ROOT/tenant-admin/" rsync -a --delete apps/taro/dist/h5-platform-admin/ "$WWW_ROOT/platform-admin/" install_runtime_config() { local source_file="$1" local target_dir="$2" if [[ ! -r "$source_file" ]]; then echo "Missing runtime config: $source_file" >&2 exit 1 fi install -m 0644 "$source_file" "$target_dir/runtime-config.json" } log "Installing H5 runtime config files..." install_runtime_config "$RUNTIME_CONFIG_DIR/h5-student.runtime-config.json" "$WWW_ROOT/student" install_runtime_config "$RUNTIME_CONFIG_DIR/h5-tenant-admin.runtime-config.json" "$WWW_ROOT/tenant-admin" install_runtime_config "$RUNTIME_CONFIG_DIR/h5-platform-admin.runtime-config.json" "$WWW_ROOT/platform-admin" if [[ "$RESTART_SERVICES" == "true" ]]; then log "Restarting systemd services..." systemctl restart tiku-api.service systemctl restart tiku-worker.service systemctl --no-pager --full status tiku-api.service tiku-worker.service >/dev/null fi log "Deployment finished: $CURRENT_SHA"