Files
exAichatbot_agent/git_fetch_all.sh
T
2026-08-11 22:30:48 +09:00

491 lines
19 KiB
Bash
Executable File

#!/usr/bin/env bash
# 4개 submodule: 로컬 변경 → 공식 GitHub chatbot2.0-agent commit + push
#
# ★ 공식 GitHub(KoreaExpressWayCorp) 반영은 이 스크립트로만 수행한다.
# start-all-server.sh / push-to-gitea.sh 는 개발서버(Gitea/exdev)만 다룬다.
#
# ./git_fetch_all.sh # fetch/merge + commit + push (공식 GitHub)
# ./git_fetch_all.sh --init # submodule 미초기화 시 clone 포함
# ./git_fetch_all.sh --dry-run # 실행 없이 상태만 출력
# ./git_fetch_all.sh --no-push # commit까지, push 생략
# ./git_fetch_all.sh --no-commit # fetch/merge만 (commit·push 생략)
#
# ★ 운영(GitHub) push 전용 — 개발서버(exdev) 주소를 커밋/push 하지 않는다.
# start-all-server.sh 가 런타임 ENV 로만 exdev(127.0.0.1 Mongo / localhost Oracle TROAD /
# host.docker.internal RAG)를 적용한다.
# 이 스크립트는 commit 직전에 운영 기본값(172.16.*)을 파일에 복원한 뒤 push 한다.
# (.env / EXDEV_LOCAL / server-dev 오버레이는 GitHub 대상이 아님)
#
# 대상 (.gitmodules) — 모두 branch=chatbot2.0-agent (main 사용 안 함):
# chatbotApi-chatbot2.0-agent
# chatbotApp-chatbot2.0-agent
# exAiChatBot-chatbot2.0-agent
# kakaoChatbotSkill-chatbot2.0-agent
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# bare repo 배포 시 상위에서 GIT_DIR 이 export 되면 submodule git 이 깨짐
MONOREPO_GIT_DIR="${MONOREPO_GIT_DIR:-${GIT_DIR:-}}"
unset GIT_DIR GIT_WORK_TREE
INIT=false
DRY_RUN=false
DO_COMMIT=true
DO_PUSH=true
TARGET_BRANCH="chatbot2.0-agent"
# 명시 대상 (순서 고정). .gitmodules 와 일치해야 함.
TARGET_PATHS=(
"chatbotApi-chatbot2.0-agent"
"chatbotApp-chatbot2.0-agent"
"exAiChatBot-chatbot2.0-agent"
"kakaoChatbotSkill-chatbot2.0-agent"
)
for arg in "$@"; do
case "$arg" in
--init) INIT=true ;;
--dry-run) DRY_RUN=true ;;
--no-commit) DO_COMMIT=false; DO_PUSH=false ;;
--no-push) DO_PUSH=false ;;
-h|--help)
sed -n '2,22p' "$0"
exit 0
;;
*)
echo "알 수 없는 옵션: $arg (--help)"
exit 1
;;
esac
done
log() { echo "[git_fetch_all] $*"; }
warn() { echo "[git_fetch_all] ⚠️ $*" >&2; }
ok() { echo "[git_fetch_all] ✅ $*"; }
cd "${ROOT_DIR}"
if [[ ! -f .gitmodules ]]; then
echo "ERROR: .gitmodules 없음 — submodule 미설정"
exit 1
fi
resolve_monorepo_git_dir() {
if [[ -n "${MONOREPO_GIT_DIR}" && -d "${MONOREPO_GIT_DIR}" ]]; then
echo "${MONOREPO_GIT_DIR}"
elif git -C "${ROOT_DIR}" rev-parse --git-dir >/dev/null 2>&1; then
git -C "${ROOT_DIR}" rev-parse --git-dir
fi
}
submodule_pinned_sha() {
local path="$1"
local git_dir
git_dir="$(resolve_monorepo_git_dir || true)"
[[ -n "${git_dir}" ]] || return 0
git --git-dir="${git_dir}" ls-tree HEAD "${path}" 2>/dev/null | awk '{print $3}'
}
clone_submodule_repo() {
local path="$1" url="$2" branch="$3"
local target="${ROOT_DIR}/${path}"
local sha
sha="$(submodule_pinned_sha "${path}" || true)"
log " clone ${url} → ${path} (branch=${branch})"
rm -rf "${target}"
if ! git clone --branch "${branch}" --single-branch "${url}" "${target}"; then
warn "${path} — clone 실패 (서버 GitHub 미접속 시 sync-submodules-rsync.sh 사용)"
return 1
fi
if [[ -n "${sha}" ]]; then
git -C "${target}" checkout "${sha}" 2>/dev/null || true
fi
}
restore_monorepo_overlays() {
if [[ -f "${ROOT_DIR}/local-dev/docker-compose.rag.local.yml" ]]; then
cp "${ROOT_DIR}/local-dev/docker-compose.rag.local.yml" \
"${ROOT_DIR}/exAiChatBot-chatbot2.0-agent/docker-compose.local.yml"
log "복원: exAiChatBot/docker-compose.local.yml (monorepo overlay)"
fi
if [[ -f "${ROOT_DIR}/server-dev/docker-compose.rag.server.yml" ]]; then
cp "${ROOT_DIR}/server-dev/docker-compose.rag.server.yml" \
"${ROOT_DIR}/exAiChatBot-chatbot2.0-agent/docker-compose.server.yml"
log "복원: exAiChatBot/docker-compose.server.yml (monorepo overlay)"
fi
}
# GitHub push 전: exdev 런타임 주소를 커밋하지 않고 운영 기본값(172.16.180.130) 유지
# - 런타임(exdev): start-all-server.sh ENV / .env / EXDEV_LOCAL=true → 127.0.0.1·host.docker.internal
# - 커밋/push: application.yml·env.template·chatbot_tool_client.py 기본값을 운영 IP로 고정
PROD_WAS_HOST="172.16.180.130"
PROD_WAS_API="http://${PROD_WAS_HOST}:8086/api"
PROD_RAG_CHAT="http://${PROD_WAS_HOST}:28012/agent/chat"
PROD_MONGO_URI="mongodb://exDev:ehfhrhdtk!!@${PROD_WAS_HOST}:27017/chatbot?heartbeatFrequencyMS=30000&minPoolSize=8&maxIdleTimeMS=18000000"
# 운영 TROAD Oracle (start-all 은 SPRING_DATASOURCE_TROAD_* ENV 로 localhost 오버라이드)
PROD_TROAD_JDBC_URL="jdbc:oracle:thin:@172.16.163.76:1522/TROAD"
PROD_TROAD_USERNAME="TROAD_APP"
PROD_TROAD_PASSWORD='!ryxhdapp2025'
restore_production_endpoints_before_commit() {
local path="$1"
local dir="${ROOT_DIR}/${path}"
case "${path}" in
chatbotApi-chatbot2.0-agent)
local yml="${dir}/src/main/resources/application.yml"
if [[ -f "${yml}" ]]; then
# 수동/임시로 바꾼 로컬 Mongo URI 를 운영 기본값으로 복원 (start-all 은 ENV 로만 오버라이드)
# URI 의 @ 는 perl 치환부 배열 보간을 피하기 위해 ENV 로 전달
PROD_MONGO_URI="${PROD_MONGO_URI}" perl -i -pe \
's|uri:\s*mongodb://\S+|uri: $ENV{PROD_MONGO_URI}|g' "${yml}"
# troad datasource 를 로컬 Oracle 로 바꿔 둔 경우 운영값 복원
PROD_TROAD_JDBC_URL="${PROD_TROAD_JDBC_URL}" \
PROD_TROAD_USERNAME="${PROD_TROAD_USERNAME}" \
PROD_TROAD_PASSWORD="${PROD_TROAD_PASSWORD}" \
perl -i -0pe '
s|(troad:\s*\n(?:[ \t]+[^\n]*\n)*?[ \t]+url:\s*)\S+|$1$ENV{PROD_TROAD_JDBC_URL}|g;
s|(troad:\s*\n(?:[ \t]+[^\n]*\n)*?[ \t]+username:\s*)\S+|$1$ENV{PROD_TROAD_USERNAME}|g;
s|(troad:\s*\n(?:[ \t]+[^\n]*\n)*?[ \t]+password:\s*).*|$1"$ENV{PROD_TROAD_PASSWORD}"|g;
' "${yml}"
fi
;;
kakaoChatbotSkill-chatbot2.0-agent)
local yml="${dir}/src/main/resources/application.yml"
if [[ -f "${yml}" ]]; then
# ${ENV:default} 형태의 default 만 운영 IP로 고정 (실행 시 ENV 로컬 오버라이드는 유지)
PROD_WAS_API="${PROD_WAS_API}" PROD_RAG_CHAT="${PROD_RAG_CHAT}" PROD_WAS_HOST="${PROD_WAS_HOST}" \
perl -i -pe '
s|CHATBOT_API_BASE_URL:[^}]+}|CHATBOT_API_BASE_URL:$ENV{PROD_WAS_API}/}|g;
s|RAG_AGENT_CHAT_URL:[^}]+}|RAG_AGENT_CHAT_URL:$ENV{PROD_RAG_CHAT}}|g;
s|base-url:\s*http://127\.0\.0\.1:8086/api/?|base-url: \${CHATBOT_API_BASE_URL:$ENV{PROD_WAS_API}/}|g;
s|base-url:\s*http://host\.docker\.internal:8086/api/?|base-url: \${CHATBOT_API_BASE_URL:$ENV{PROD_WAS_API}/}|g;
s|url:\s*http://127\.0\.0\.1:28012/agent/chat|url: \${RAG_AGENT_CHAT_URL:$ENV{PROD_RAG_CHAT}}|g;
s|url:\s*http://host\.docker\.internal:28012/agent/chat|url: \${RAG_AGENT_CHAT_URL:$ENV{PROD_RAG_CHAT}}|g;
' "${yml}"
fi
;;
exAiChatBot-chatbot2.0-agent)
if [[ -f "${dir}/env.template" ]]; then
PROD_WAS_API="${PROD_WAS_API}" perl -i -pe \
's|^CHATBOT_API_BASE_URL=.*|CHATBOT_API_BASE_URL=$ENV{PROD_WAS_API}|' "${dir}/env.template"
# EXDEV_LOCAL 은 커밋 템플릿에 넣지 않음 (server-dev 전용)
perl -i -ne 'print unless /^EXDEV_LOCAL=/' "${dir}/env.template"
fi
if [[ -f "${dir}/env.example" ]]; then
PROD_WAS_API="${PROD_WAS_API}" perl -i -pe \
's|^CHATBOT_API_BASE_URL=.*|CHATBOT_API_BASE_URL=$ENV{PROD_WAS_API}|' "${dir}/env.example" 2>/dev/null || true
perl -i -ne 'print unless /^EXDEV_LOCAL=/' "${dir}/env.example" 2>/dev/null || true
fi
if [[ -f "${dir}/scripts/agent/chatbot_tool_client.py" ]]; then
# 운영 기본값(_PROD_WAS_API / legacy getenv default)만 복원.
PROD_WAS_API="${PROD_WAS_API}" perl -i -pe '
s|_PROD_WAS_API = "http://[^"]+"|_PROD_WAS_API = "$ENV{PROD_WAS_API}"|g;
s|os\.getenv\("TOOL_API_BASE_URL", "http://[^"]+"\)|os.getenv("TOOL_API_BASE_URL", "$ENV{PROD_WAS_API}")|g;
s|os\.getenv\("CHATBOT_API_BASE_URL", "http://[^"]+"\)|os.getenv("CHATBOT_API_BASE_URL", "$ENV{PROD_WAS_API}")|g;
' "${dir}/scripts/agent/chatbot_tool_client.py"
fi
# monorepo overlay compose 는 push 대상에서 제외 (로컬 exdev 전용)
git -C "${dir}" checkout HEAD -- docker-compose.server.yml docker-compose.local.yml 2>/dev/null || true
# .env 는 gitignore — 스테이징 실수 방지
git -C "${dir}" reset HEAD -- .env .env.local 2>/dev/null || true
rm -f "${dir}/.env.local" 2>/dev/null || true
;;
esac
}
# 복원 후에도 개발서버 전용 호스트가 커밋 대상 설정 파일에 남으면 push 중단
assert_no_exdev_endpoints_for_push() {
local path="$1"
local dir="${ROOT_DIR}/${path}"
local hits=""
case "${path}" in
chatbotApi-chatbot2.0-agent)
hits="$(grep -nE 'mongodb://(127\.0\.0\.1|localhost)|host\.docker\.internal|jdbc:oracle:thin:@(127\.0\.0\.1|localhost)' \
"${dir}/src/main/resources/application.yml" 2>/dev/null || true)"
# troad 블록이 로컬 계정(OPR_MAIN)으로 바뀌었는지도 차단
if awk '
/^[[:space:]]*troad:[[:space:]]*$/ { in_troad=1; next }
in_troad && /^[[:space:]]*[a-zA-Z0-9_-]+:[[:space:]]*$/ && $0 !~ /^[[:space:]]*troad:/ { in_troad=0 }
in_troad && /username:[[:space:]]*OPR_MAIN/ { found=1 }
END { exit found ? 0 : 1 }
' "${dir}/src/main/resources/application.yml" 2>/dev/null; then
hits+=$'\n'"troad.username=OPR_MAIN (exdev local Oracle — must not commit)"
fi
;;
kakaoChatbotSkill-chatbot2.0-agent)
hits="$(grep -nE '127\.0\.0\.1:(8086|28012)|host\.docker\.internal:(8086|28012)' \
"${dir}/src/main/resources/application.yml" 2>/dev/null || true)"
;;
exAiChatBot-chatbot2.0-agent)
hits="$(grep -nE '^(CHATBOT_API_BASE_URL|TOOL_API_BASE_URL)=.*(127\.0\.0\.1|host\.docker\.internal)|EXDEV_LOCAL=' \
"${dir}/env.template" "${dir}/env.example" 2>/dev/null || true)"
hits+="$(grep -nE 'getenv\(\"TOOL_API_BASE_URL\", \"http://(127\.0\.0\.1|host\.docker\.internal)' \
"${dir}/scripts/agent/chatbot_tool_client.py" 2>/dev/null || true)"
;;
esac
if [[ -n "${hits}" ]]; then
warn "${path}: 개발서버(exdev) 주소가 남아 push 중단"
echo "${hits}" | sed 's/^/ /'
warn "start-all-server.sh 런타임 ENV 로만 exdev 주소를 쓰고, 소스 기본값은 운영 IP 를 유지하세요"
return 1
fi
return 0
}
submodule_is_cloned() {
local path="$1"
[[ -e "${ROOT_DIR}/${path}/.git" ]]
}
repo_is_dirty() {
! git diff --quiet || ! git diff --cached --quiet || [[ -n "$(git ls-files --others --exclude-standard)" ]]
}
ensure_branch() {
local branch="$1"
local git_dir
# submodule 은 .git 이 파일일 수 있음 → rev-parse 사용
git_dir="$(git rev-parse --git-dir 2>/dev/null || echo .git)"
if [[ -d "${git_dir}/rebase-merge" || -d "${git_dir}/rebase-apply" ]]; then
warn "rebase 진행 중 — 수동 해결 후 재실행 (git rebase --continue / --abort)"
return 1
fi
if [[ -f "${git_dir}/MERGE_HEAD" ]]; then
warn "merge 진행 중 — 수동 해결 후 재실행"
return 1
fi
local cur
cur="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo DETACHED)"
if [[ "${cur}" == "${branch}" ]]; then
return 0
fi
if git show-ref --verify --quiet "refs/heads/${branch}"; then
git checkout "${branch}"
elif git show-ref --verify --quiet "refs/remotes/origin/${branch}"; then
git checkout -B "${branch}" "origin/${branch}"
else
git checkout -B "${branch}"
fi
}
sync_one() {
local path="$1"
local url branch old new stashed=0 ahead
url="$(git config -f .gitmodules "submodule.${path}.url" 2>/dev/null || true)"
if [[ -z "${url}" ]]; then
warn "${path} — .gitmodules 에 없음, 건너뜀"
return 1
fi
# 사용자 요구: main 금지, chatbot2.0-agent 고정
branch="$(git config -f .gitmodules "submodule.${path}.branch" || echo "${TARGET_BRANCH}")"
branch="${branch:-${TARGET_BRANCH}}"
if [[ "${branch}" != "${TARGET_BRANCH}" ]]; then
warn "${path} — .gitmodules branch=${branch} → ${TARGET_BRANCH} 로 강제"
branch="${TARGET_BRANCH}"
fi
log ">>> ${path}"
log " url=${url} branch=${branch}"
if ! submodule_is_cloned "${path}"; then
if $DRY_RUN; then
log " [dry-run] submodule clone 필요 — ./git_fetch_all.sh --init"
return 0
fi
if $INIT; then
clone_submodule_repo "${path}" "${url}" "${branch}" || return 1
else
warn "${path} — submodule 미초기화. --init 옵션 사용"
return 1
fi
fi
(
cd "${ROOT_DIR}/${path}"
unset GIT_DIR GIT_WORK_TREE
old="$(git rev-parse HEAD 2>/dev/null || echo none)"
if $DRY_RUN; then
ensure_branch "${branch}" || true
git fetch origin "${branch}" 2>/dev/null || git fetch origin
new="$(git rev-parse "origin/${branch}" 2>/dev/null || echo unknown)"
if [[ "${old}" == "${new}" ]]; then
log " [dry-run] 원격 최신: ${old:0:7}"
else
log " [dry-run] 원격 변경 ${old:0:7} → ${new:0:7}"
git log --oneline "HEAD..origin/${branch}" 2>/dev/null | sed 's/^/ /' || true
fi
if repo_is_dirty; then
log " [dry-run] 로컬 미커밋 변경 있음 → commit/push 대상"
git status -sb | sed 's/^/ /'
else
log " [dry-run] 로컬 미커밋 변경 없음"
fi
exit 0
fi
# 1) 원격 fetch + 브랜치 고정
git fetch origin "${branch}"
ensure_branch "${branch}" || exit 1
git branch --set-upstream-to="origin/${branch}" "${branch}" 2>/dev/null || true
# 2) 로컬 미커밋 변경이 pull 을 막으면 stash
if repo_is_dirty; then
log " 로컬 변경 stash 후 원격 반영..."
git stash push -u -m "git_fetch_all auto-stash $(date '+%Y-%m-%d %H:%M:%S')" || true
stashed=1
fi
# 3) GitHub 변경사항 merge (ff → rebase → merge)
if [[ "$(git rev-parse HEAD)" != "$(git rev-parse "origin/${branch}")" ]]; then
log " 원격 변경 merge: origin/${branch}"
if git merge-base --is-ancestor HEAD "origin/${branch}" 2>/dev/null; then
git merge --ff-only "origin/${branch}"
else
if ! git pull --rebase origin "${branch}"; then
warn "${path} — rebase 실패, merge 로 재시도"
git rebase --abort 2>/dev/null || true
if ! git pull --no-rebase origin "${branch}"; then
warn "${path} — merge 충돌. 수동 해결 필요"
git status -sb | sed 's/^/ /'
if [[ "${stashed}" -eq 1 ]]; then
git stash pop || true
fi
exit 1
fi
fi
fi
ok "${path} 원격 반영 $(git log -1 --oneline)"
else
log " 원격과 동일 (fetch 완료)"
fi
# 4) stash 복원
if [[ "${stashed}" -eq 1 ]]; then
log " stash 복원..."
if ! git stash pop; then
warn "${path} — stash pop 충돌. 수동 해결 후 commit/push"
git status -sb | sed 's/^/ /'
exit 1
fi
fi
# 5) 로컬 수정사항 commit (exdev 주소는 복원 후·검증 통과 시에만)
if $DO_COMMIT && repo_is_dirty; then
log " 로컬 변경 commit..."
# push 전에 운영 WAS/RAG/Mongo 기본 주소 복원 (exdev 주소는 start-all-server.sh ENV 전용)
restore_production_endpoints_before_commit "${path}"
if ! assert_no_exdev_endpoints_for_push "${path}"; then
exit 1
fi
log " 운영 엔드포인트 기본값 유지: WAS ${PROD_WAS_API} / Mongo ${PROD_WAS_HOST}"
git add -A
# .env 등 ignore 대상이 스테이징되지 않도록 한 번 더 정리
git reset HEAD -- .env .env.local 2>/dev/null || true
if git diff --cached --quiet; then
log " staged 변경 없음 (ignore 된 파일만 있을 수 있음)"
else
# staged diff 에 exdev 호스트가 남아 있으면 push 금지
if git diff --cached -U0 | grep -E '^\+' | grep -qE '127\.0\.0\.1:(27017|8086|28012)|host\.docker\.internal|EXDEV_LOCAL=true'; then
warn "${path}: staged diff 에 개발서버 주소가 포함됨 — commit 중단"
git diff --cached --stat | sed 's/^/ /'
exit 1
fi
git commit -m "$(cat <<EOF
sync: monorepo local updates ($(date '+%Y-%m-%d %H:%M'))
git_fetch_all.sh 로 chatbot2.0-agent 동기화 후 로컬 수정을 반영한다.
EOF
)"
ok "${path} commit $(git log -1 --oneline)"
fi
elif ! $DO_COMMIT && repo_is_dirty; then
log " [--no-commit] 미커밋 변경 유지 (운영 주소 복원 미적용)"
git status -sb | sed 's/^/ /'
fi
# 6) push (chatbot2.0-agent 만) — push 직전 운영 주소 재검증
if $DO_PUSH; then
restore_production_endpoints_before_commit "${path}"
if ! assert_no_exdev_endpoints_for_push "${path}"; then
exit 1
fi
# 복원으로 생긴 미커밋이 있으면 반영
if $DO_COMMIT && repo_is_dirty; then
git add -A
git reset HEAD -- .env .env.local 2>/dev/null || true
if ! git diff --cached --quiet; then
git commit -m "$(cat <<EOF
chore: restore production endpoints before push
git_fetch_all.sh — exdev 런타임 주소를 커밋에서 제거한다.
EOF
)"
fi
fi
ahead="$(git rev-list --count "origin/${branch}..HEAD" 2>/dev/null || echo 0)"
if [[ "${ahead}" != "0" ]]; then
log " push origin ${branch} (ahead ${ahead})..."
if git push origin "${branch}"; then
ok "${path} push 완료"
else
warn "${path} — push 실패 (대용량 파일/권한 등). 로그 확인"
exit 1
fi
else
log " push 생략 (원격과 동일)"
fi
else
log " [--no-push] push 생략"
fi
new="$(git rev-parse HEAD)"
log " HEAD ${old:0:7} → ${new:0:7} $(git log -1 --oneline)"
)
}
failures=0
for path in "${TARGET_PATHS[@]}"; do
if ! sync_one "${path}"; then
failures=$((failures + 1))
fi
echo ""
done
if ! $DRY_RUN; then
restore_monorepo_overlays
fi
log "=========================================="
log "submodule 상태:"
git submodule status 2>/dev/null || true
for path in "${TARGET_PATHS[@]}"; do
if submodule_is_cloned "${path}"; then
printf " %-36s %s\n" "${path}" "$(git -C "${ROOT_DIR}/${path}" log -1 --oneline 2>/dev/null || echo '?')"
fi
done
log "=========================================="
if [[ ${failures} -gt 0 ]]; then
warn "실패 ${failures}개 — 로그 확인"
exit 1
fi
if $DRY_RUN; then
log "완료 — dry-run (변경 없음)"
elif $DO_PUSH; then
log "완료 — ${TARGET_BRANCH}: fetch/merge → commit → push"
else
log "완료 — ${TARGET_BRANCH}: fetch/merge$($DO_COMMIT && echo ' → commit' || true) (push 생략)"
fi
log "monorepo 포인터 갱신: git add *-chatbot2.0-agent && git status"