#!/usr/bin/env bash # Auto-deploy: детект изменений через git diff, сборка и деплой только изменённых компонентов. # # Зависимости: bash, git, ssh; rsync (Linux/macOS) или tar (Git Bash). # Конфиг: scripts/deploy.env. # # Примеры: # ./scripts/deploy-auto.sh # деплой изменений # ./scripts/deploy-auto.sh --force # полный деплой всех компонентов # ./scripts/deploy-auto.sh -f # только фронт # ./scripts/deploy-auto.sh -b # только бэкенд set -euo pipefail case "$(uname -s 2>/dev/null)" in MINGW* | MSYS*) export MSYS2_ARG_CONV_EXCL="*" ;; esac SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # --- Config --- if [[ -f "$SCRIPT_DIR/deploy.env" ]]; then set -a source "$SCRIPT_DIR/deploy.env" set +a fi DEPLOY_HOST="${DEPLOY_HOST:-}" DEPLOY_USER="${DEPLOY_USER:-root}" DEPLOY_PATH="${DEPLOY_PATH:-/opt/craftshop}" DEPLOY_FRONTEND_DIST="${DEPLOY_FRONTEND_DIST:-$DEPLOY_PATH/www}" DEPLOY_SSH_IDENTITY="${DEPLOY_SSH_IDENTITY:-}" DEPLOY_RESTART_CMD="${DEPLOY_RESTART_CMD:-systemctl restart craftshop-api}" DEPLOY_SERVER_OWNER="${DEPLOY_SERVER_OWNER:-root}" DEPLOY_SKIP_CHOWN="${DEPLOY_SKIP_CHOWN:-0}" RSYNC_OPTS=(-az --delete --human-readable --progress) SSH_OPTS=() if [[ -n "${DEPLOY_SSH_IDENTITY}" ]]; then SSH_OPTS+=(-i "$DEPLOY_SSH_IDENTITY") fi REMOTE="${DEPLOY_USER}@${DEPLOY_HOST}" SSH_BASE=(ssh "${SSH_OPTS[@]}" -o StrictHostKeyChecking=accept-new "$REMOTE") # --- Flags --- FORCE=false TARGET="auto" usage() { cat <&2; usage >&2; exit 1 ;; esac shift done if [[ -z "$DEPLOY_HOST" ]]; then echo "Укажите DEPLOY_HOST (или добавьте в scripts/deploy.env)" >&2 exit 1 fi # --- Helpers --- remote_exec() { "${SSH_BASE[@]}" "$@" } should_use_tar_transport() { case "$(uname -s 2>/dev/null)" in MINGW*|MSYS*|CYGWIN_NT*) return 0 ;; *) return 1 ;; esac } build_rsync_rsh() { printf '%q ' ssh "${SSH_OPTS[@]}" -o StrictHostKeyChecking=accept-new } # --- Diff detection --- changed_client=false changed_server=false if [[ "$TARGET" == "frontend" ]]; then changed_client=true elif [[ "$TARGET" == "backend" ]]; then changed_server=true elif [[ "$FORCE" == true ]]; then changed_client=true changed_server=true else DEPLOYED_FILE="$ROOT/.deployed-commit" if [[ -f "$DEPLOYED_FILE" ]]; then LAST_DEPLOYED=$(cat "$DEPLOYED_FILE") else LAST_DEPLOYED="HEAD~1" fi echo ">>> Diff: $LAST_DEPLOYED..HEAD" CHANGED_FILES=$(git -C "$ROOT" diff --name-only "$LAST_DEPLOYED" HEAD 2>/dev/null || true) if echo "$CHANGED_FILES" | grep -q "^client/"; then changed_client=true fi if echo "$CHANGED_FILES" | grep -q "^server/"; then changed_server=true fi if echo "$CHANGED_FILES" | grep -q "^shared/"; then changed_client=true changed_server=true fi if [[ "$changed_client" == false && "$changed_server" == false ]]; then echo ">>> Ничего не изменилось с последнего деплоя." exit 0 fi fi echo ">>> Клиент: $changed_client, Сервер: $changed_server" # --- Deploy: Client --- if [[ "$changed_client" == true ]]; then echo ">>> Сборка клиента..." (cd "$ROOT/client" && npm ci && npm run build) remote_exec mkdir -p "$DEPLOY_FRONTEND_DIST" if should_use_tar_transport; then remote_exec "find ${DEPLOY_FRONTEND_DIST} -mindepth 1 -delete 2>/dev/null || true" (cd "$ROOT/client/dist" && tar -czf - .) | \ "${SSH_BASE[@]}" "mkdir -p ${DEPLOY_FRONTEND_DIST} && tar xzf - -C ${DEPLOY_FRONTEND_DIST}" else rsh="$(build_rsync_rsh)" rsync "${RSYNC_OPTS[@]}" -e "$rsh" \ "$ROOT/client/dist/" "${REMOTE}:${DEPLOY_FRONTEND_DIST}/" fi echo ">>> Клиент задеплоен" fi # --- Deploy: Server --- if [[ "$changed_server" == true ]]; then remote_exec mkdir -p "$DEPLOY_PATH/server" "$DEPLOY_PATH/shared" if should_use_tar_transport; then (cd "$ROOT/server" && tar -czf - \ --exclude=node_modules --exclude=uploads --exclude=.git \ --exclude='*.db' --exclude=.env --exclude=.dev_env \ .) | "${SSH_BASE[@]}" "mkdir -p ${DEPLOY_PATH}/server && tar xzf - -C ${DEPLOY_PATH}/server" (cd "$ROOT/shared" && tar -czf - \ --exclude=.git \ .) | "${SSH_BASE[@]}" "mkdir -p ${DEPLOY_PATH}/shared && tar xzf - -C ${DEPLOY_PATH}/shared" else rsh="$(build_rsync_rsh)" rsync "${RSYNC_OPTS[@]}" -e "$rsh" \ --exclude node_modules --exclude uploads --exclude .git \ --exclude '*.db' --exclude .env --exclude .dev_env \ "$ROOT/server/" "${REMOTE}:${DEPLOY_PATH}/server/" rsync "${RSYNC_OPTS[@]}" -e "$rsh" \ --exclude .git \ "$ROOT/shared/" "${REMOTE}:${DEPLOY_PATH}/shared/" fi echo ">>> Сервер: npm ci, prisma generate, migrate deploy" remote_exec bash -lc "set -e cd \"$DEPLOY_PATH/server\" npm ci npx prisma generate npx prisma migrate deploy " if [[ "${DEPLOY_USER}" == "root" && "${DEPLOY_SKIP_CHOWN}" != "1" ]]; then echo ">>> chown ${DEPLOY_SERVER_OWNER}" remote_exec chown -R "${DEPLOY_SERVER_OWNER}:${DEPLOY_SERVER_OWNER}" "$DEPLOY_PATH/server" remote_exec chown -R "${DEPLOY_SERVER_OWNER}:${DEPLOY_SERVER_OWNER}" "$DEPLOY_PATH/shared" fi if [[ -n "${DEPLOY_RESTART_CMD}" ]]; then echo ">>> Рестарт: $DEPLOY_RESTART_CMD" remote_exec bash -lc "$DEPLOY_RESTART_CMD" fi echo ">>> Сервер задеплоен" fi # --- Save deployed commit --- if [[ "$TARGET" == "auto" && "$FORCE" != true ]]; then CURRENT_HEAD=$(git -C "$ROOT" rev-parse HEAD) echo "$CURRENT_HEAD" > "$ROOT/.deployed-commit" echo ">>> Сохранён коммит $CURRENT_HEAD" fi echo ">>> Готово."