Deploy: exdev sync 2026-08-13 13:47:23
This commit is contained in:
+490
-490
@@ -1,490 +1,490 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# 4개 submodule: 로컬 변경 → 공식 GitHub chatbot2.0-agent commit + push
|
# 4개 submodule: 로컬 변경 → 공식 GitHub chatbot2.0-agent commit + push
|
||||||
#
|
#
|
||||||
# ★ 공식 GitHub(KoreaExpressWayCorp) 반영은 이 스크립트로만 수행한다.
|
# ★ 공식 GitHub(KoreaExpressWayCorp) 반영은 이 스크립트로만 수행한다.
|
||||||
# start-all-server.sh / push-to-gitea.sh 는 개발서버(Gitea/exdev)만 다룬다.
|
# start-all-server.sh / push-to-gitea.sh 는 개발서버(Gitea/exdev)만 다룬다.
|
||||||
#
|
#
|
||||||
# ./git_fetch_all.sh # fetch/merge + commit + push (공식 GitHub)
|
# ./git_fetch_all.sh # fetch/merge + commit + push (공식 GitHub)
|
||||||
# ./git_fetch_all.sh --init # submodule 미초기화 시 clone 포함
|
# ./git_fetch_all.sh --init # submodule 미초기화 시 clone 포함
|
||||||
# ./git_fetch_all.sh --dry-run # 실행 없이 상태만 출력
|
# ./git_fetch_all.sh --dry-run # 실행 없이 상태만 출력
|
||||||
# ./git_fetch_all.sh --no-push # commit까지, push 생략
|
# ./git_fetch_all.sh --no-push # commit까지, push 생략
|
||||||
# ./git_fetch_all.sh --no-commit # fetch/merge만 (commit·push 생략)
|
# ./git_fetch_all.sh --no-commit # fetch/merge만 (commit·push 생략)
|
||||||
#
|
#
|
||||||
# ★ 운영(GitHub) push 전용 — 개발서버(exdev) 주소를 커밋/push 하지 않는다.
|
# ★ 운영(GitHub) push 전용 — 개발서버(exdev) 주소를 커밋/push 하지 않는다.
|
||||||
# start-all-server.sh 가 런타임 ENV 로만 exdev(127.0.0.1 Mongo / localhost Oracle TROAD /
|
# start-all-server.sh 가 런타임 ENV 로만 exdev(127.0.0.1 Mongo / localhost Oracle TROAD /
|
||||||
# host.docker.internal RAG)를 적용한다.
|
# host.docker.internal RAG)를 적용한다.
|
||||||
# 이 스크립트는 commit 직전에 운영 기본값(172.16.*)을 파일에 복원한 뒤 push 한다.
|
# 이 스크립트는 commit 직전에 운영 기본값(172.16.*)을 파일에 복원한 뒤 push 한다.
|
||||||
# (.env / EXDEV_LOCAL / server-dev 오버레이는 GitHub 대상이 아님)
|
# (.env / EXDEV_LOCAL / server-dev 오버레이는 GitHub 대상이 아님)
|
||||||
#
|
#
|
||||||
# 대상 (.gitmodules) — 모두 branch=chatbot2.0-agent (main 사용 안 함):
|
# 대상 (.gitmodules) — 모두 branch=chatbot2.0-agent (main 사용 안 함):
|
||||||
# chatbotApi-chatbot2.0-agent
|
# chatbotApi-chatbot2.0-agent
|
||||||
# chatbotApp-chatbot2.0-agent
|
# chatbotApp-chatbot2.0-agent
|
||||||
# exAiChatBot-chatbot2.0-agent
|
# exAiChatBot-chatbot2.0-agent
|
||||||
# kakaoChatbotSkill-chatbot2.0-agent
|
# kakaoChatbotSkill-chatbot2.0-agent
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# bare repo 배포 시 상위에서 GIT_DIR 이 export 되면 submodule git 이 깨짐
|
# bare repo 배포 시 상위에서 GIT_DIR 이 export 되면 submodule git 이 깨짐
|
||||||
MONOREPO_GIT_DIR="${MONOREPO_GIT_DIR:-${GIT_DIR:-}}"
|
MONOREPO_GIT_DIR="${MONOREPO_GIT_DIR:-${GIT_DIR:-}}"
|
||||||
unset GIT_DIR GIT_WORK_TREE
|
unset GIT_DIR GIT_WORK_TREE
|
||||||
|
|
||||||
INIT=false
|
INIT=false
|
||||||
DRY_RUN=false
|
DRY_RUN=false
|
||||||
DO_COMMIT=true
|
DO_COMMIT=true
|
||||||
DO_PUSH=true
|
DO_PUSH=true
|
||||||
TARGET_BRANCH="chatbot2.0-agent"
|
TARGET_BRANCH="chatbot2.0-agent"
|
||||||
|
|
||||||
# 명시 대상 (순서 고정). .gitmodules 와 일치해야 함.
|
# 명시 대상 (순서 고정). .gitmodules 와 일치해야 함.
|
||||||
TARGET_PATHS=(
|
TARGET_PATHS=(
|
||||||
"chatbotApi-chatbot2.0-agent"
|
"chatbotApi-chatbot2.0-agent"
|
||||||
"chatbotApp-chatbot2.0-agent"
|
"chatbotApp-chatbot2.0-agent"
|
||||||
"exAiChatBot-chatbot2.0-agent"
|
"exAiChatBot-chatbot2.0-agent"
|
||||||
"kakaoChatbotSkill-chatbot2.0-agent"
|
"kakaoChatbotSkill-chatbot2.0-agent"
|
||||||
)
|
)
|
||||||
|
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--init) INIT=true ;;
|
--init) INIT=true ;;
|
||||||
--dry-run) DRY_RUN=true ;;
|
--dry-run) DRY_RUN=true ;;
|
||||||
--no-commit) DO_COMMIT=false; DO_PUSH=false ;;
|
--no-commit) DO_COMMIT=false; DO_PUSH=false ;;
|
||||||
--no-push) DO_PUSH=false ;;
|
--no-push) DO_PUSH=false ;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
sed -n '2,22p' "$0"
|
sed -n '2,22p' "$0"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "알 수 없는 옵션: $arg (--help)"
|
echo "알 수 없는 옵션: $arg (--help)"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
log() { echo "[git_fetch_all] $*"; }
|
log() { echo "[git_fetch_all] $*"; }
|
||||||
warn() { echo "[git_fetch_all] ⚠️ $*" >&2; }
|
warn() { echo "[git_fetch_all] ⚠️ $*" >&2; }
|
||||||
ok() { echo "[git_fetch_all] ✅ $*"; }
|
ok() { echo "[git_fetch_all] ✅ $*"; }
|
||||||
|
|
||||||
cd "${ROOT_DIR}"
|
cd "${ROOT_DIR}"
|
||||||
|
|
||||||
if [[ ! -f .gitmodules ]]; then
|
if [[ ! -f .gitmodules ]]; then
|
||||||
echo "ERROR: .gitmodules 없음 — submodule 미설정"
|
echo "ERROR: .gitmodules 없음 — submodule 미설정"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
resolve_monorepo_git_dir() {
|
resolve_monorepo_git_dir() {
|
||||||
if [[ -n "${MONOREPO_GIT_DIR}" && -d "${MONOREPO_GIT_DIR}" ]]; then
|
if [[ -n "${MONOREPO_GIT_DIR}" && -d "${MONOREPO_GIT_DIR}" ]]; then
|
||||||
echo "${MONOREPO_GIT_DIR}"
|
echo "${MONOREPO_GIT_DIR}"
|
||||||
elif git -C "${ROOT_DIR}" rev-parse --git-dir >/dev/null 2>&1; then
|
elif git -C "${ROOT_DIR}" rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
git -C "${ROOT_DIR}" rev-parse --git-dir
|
git -C "${ROOT_DIR}" rev-parse --git-dir
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
submodule_pinned_sha() {
|
submodule_pinned_sha() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local git_dir
|
local git_dir
|
||||||
git_dir="$(resolve_monorepo_git_dir || true)"
|
git_dir="$(resolve_monorepo_git_dir || true)"
|
||||||
[[ -n "${git_dir}" ]] || return 0
|
[[ -n "${git_dir}" ]] || return 0
|
||||||
git --git-dir="${git_dir}" ls-tree HEAD "${path}" 2>/dev/null | awk '{print $3}'
|
git --git-dir="${git_dir}" ls-tree HEAD "${path}" 2>/dev/null | awk '{print $3}'
|
||||||
}
|
}
|
||||||
|
|
||||||
clone_submodule_repo() {
|
clone_submodule_repo() {
|
||||||
local path="$1" url="$2" branch="$3"
|
local path="$1" url="$2" branch="$3"
|
||||||
local target="${ROOT_DIR}/${path}"
|
local target="${ROOT_DIR}/${path}"
|
||||||
local sha
|
local sha
|
||||||
|
|
||||||
sha="$(submodule_pinned_sha "${path}" || true)"
|
sha="$(submodule_pinned_sha "${path}" || true)"
|
||||||
log " clone ${url} → ${path} (branch=${branch})"
|
log " clone ${url} → ${path} (branch=${branch})"
|
||||||
rm -rf "${target}"
|
rm -rf "${target}"
|
||||||
if ! git clone --branch "${branch}" --single-branch "${url}" "${target}"; then
|
if ! git clone --branch "${branch}" --single-branch "${url}" "${target}"; then
|
||||||
warn "${path} — clone 실패 (서버 GitHub 미접속 시 sync-submodules-rsync.sh 사용)"
|
warn "${path} — clone 실패 (서버 GitHub 미접속 시 sync-submodules-rsync.sh 사용)"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if [[ -n "${sha}" ]]; then
|
if [[ -n "${sha}" ]]; then
|
||||||
git -C "${target}" checkout "${sha}" 2>/dev/null || true
|
git -C "${target}" checkout "${sha}" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
restore_monorepo_overlays() {
|
restore_monorepo_overlays() {
|
||||||
if [[ -f "${ROOT_DIR}/local-dev/docker-compose.rag.local.yml" ]]; then
|
if [[ -f "${ROOT_DIR}/local-dev/docker-compose.rag.local.yml" ]]; then
|
||||||
cp "${ROOT_DIR}/local-dev/docker-compose.rag.local.yml" \
|
cp "${ROOT_DIR}/local-dev/docker-compose.rag.local.yml" \
|
||||||
"${ROOT_DIR}/exAiChatBot-chatbot2.0-agent/docker-compose.local.yml"
|
"${ROOT_DIR}/exAiChatBot-chatbot2.0-agent/docker-compose.local.yml"
|
||||||
log "복원: exAiChatBot/docker-compose.local.yml (monorepo overlay)"
|
log "복원: exAiChatBot/docker-compose.local.yml (monorepo overlay)"
|
||||||
fi
|
fi
|
||||||
if [[ -f "${ROOT_DIR}/server-dev/docker-compose.rag.server.yml" ]]; then
|
if [[ -f "${ROOT_DIR}/server-dev/docker-compose.rag.server.yml" ]]; then
|
||||||
cp "${ROOT_DIR}/server-dev/docker-compose.rag.server.yml" \
|
cp "${ROOT_DIR}/server-dev/docker-compose.rag.server.yml" \
|
||||||
"${ROOT_DIR}/exAiChatBot-chatbot2.0-agent/docker-compose.server.yml"
|
"${ROOT_DIR}/exAiChatBot-chatbot2.0-agent/docker-compose.server.yml"
|
||||||
log "복원: exAiChatBot/docker-compose.server.yml (monorepo overlay)"
|
log "복원: exAiChatBot/docker-compose.server.yml (monorepo overlay)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# GitHub push 전: exdev 런타임 주소를 커밋하지 않고 운영 기본값(172.16.180.130) 유지
|
# GitHub push 전: exdev 런타임 주소를 커밋하지 않고 운영 기본값(172.16.180.130) 유지
|
||||||
# - 런타임(exdev): start-all-server.sh ENV / .env / EXDEV_LOCAL=true → 127.0.0.1·host.docker.internal
|
# - 런타임(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로 고정
|
# - 커밋/push: application.yml·env.template·chatbot_tool_client.py 기본값을 운영 IP로 고정
|
||||||
PROD_WAS_HOST="172.16.180.130"
|
PROD_WAS_HOST="172.16.180.130"
|
||||||
PROD_WAS_API="http://${PROD_WAS_HOST}:8086/api"
|
PROD_WAS_API="http://${PROD_WAS_HOST}:8086/api"
|
||||||
PROD_RAG_CHAT="http://${PROD_WAS_HOST}:28012/agent/chat"
|
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"
|
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 오버라이드)
|
# 운영 TROAD Oracle (start-all 은 SPRING_DATASOURCE_TROAD_* ENV 로 localhost 오버라이드)
|
||||||
PROD_TROAD_JDBC_URL="jdbc:oracle:thin:@172.16.163.76:1522/TROAD"
|
PROD_TROAD_JDBC_URL="jdbc:oracle:thin:@172.16.163.76:1522/TROAD"
|
||||||
PROD_TROAD_USERNAME="TROAD_APP"
|
PROD_TROAD_USERNAME="TROAD_APP"
|
||||||
PROD_TROAD_PASSWORD='!ryxhdapp2025'
|
PROD_TROAD_PASSWORD='!ryxhdapp2025'
|
||||||
|
|
||||||
restore_production_endpoints_before_commit() {
|
restore_production_endpoints_before_commit() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local dir="${ROOT_DIR}/${path}"
|
local dir="${ROOT_DIR}/${path}"
|
||||||
|
|
||||||
case "${path}" in
|
case "${path}" in
|
||||||
chatbotApi-chatbot2.0-agent)
|
chatbotApi-chatbot2.0-agent)
|
||||||
local yml="${dir}/src/main/resources/application.yml"
|
local yml="${dir}/src/main/resources/application.yml"
|
||||||
if [[ -f "${yml}" ]]; then
|
if [[ -f "${yml}" ]]; then
|
||||||
# 수동/임시로 바꾼 로컬 Mongo URI 를 운영 기본값으로 복원 (start-all 은 ENV 로만 오버라이드)
|
# 수동/임시로 바꾼 로컬 Mongo URI 를 운영 기본값으로 복원 (start-all 은 ENV 로만 오버라이드)
|
||||||
# URI 의 @ 는 perl 치환부 배열 보간을 피하기 위해 ENV 로 전달
|
# URI 의 @ 는 perl 치환부 배열 보간을 피하기 위해 ENV 로 전달
|
||||||
PROD_MONGO_URI="${PROD_MONGO_URI}" perl -i -pe \
|
PROD_MONGO_URI="${PROD_MONGO_URI}" perl -i -pe \
|
||||||
's|uri:\s*mongodb://\S+|uri: $ENV{PROD_MONGO_URI}|g' "${yml}"
|
's|uri:\s*mongodb://\S+|uri: $ENV{PROD_MONGO_URI}|g' "${yml}"
|
||||||
# troad datasource 를 로컬 Oracle 로 바꿔 둔 경우 운영값 복원
|
# troad datasource 를 로컬 Oracle 로 바꿔 둔 경우 운영값 복원
|
||||||
PROD_TROAD_JDBC_URL="${PROD_TROAD_JDBC_URL}" \
|
PROD_TROAD_JDBC_URL="${PROD_TROAD_JDBC_URL}" \
|
||||||
PROD_TROAD_USERNAME="${PROD_TROAD_USERNAME}" \
|
PROD_TROAD_USERNAME="${PROD_TROAD_USERNAME}" \
|
||||||
PROD_TROAD_PASSWORD="${PROD_TROAD_PASSWORD}" \
|
PROD_TROAD_PASSWORD="${PROD_TROAD_PASSWORD}" \
|
||||||
perl -i -0pe '
|
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]+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]+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;
|
s|(troad:\s*\n(?:[ \t]+[^\n]*\n)*?[ \t]+password:\s*).*|$1"$ENV{PROD_TROAD_PASSWORD}"|g;
|
||||||
' "${yml}"
|
' "${yml}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
kakaoChatbotSkill-chatbot2.0-agent)
|
kakaoChatbotSkill-chatbot2.0-agent)
|
||||||
local yml="${dir}/src/main/resources/application.yml"
|
local yml="${dir}/src/main/resources/application.yml"
|
||||||
if [[ -f "${yml}" ]]; then
|
if [[ -f "${yml}" ]]; then
|
||||||
# ${ENV:default} 형태의 default 만 운영 IP로 고정 (실행 시 ENV 로컬 오버라이드는 유지)
|
# ${ENV:default} 형태의 default 만 운영 IP로 고정 (실행 시 ENV 로컬 오버라이드는 유지)
|
||||||
PROD_WAS_API="${PROD_WAS_API}" PROD_RAG_CHAT="${PROD_RAG_CHAT}" PROD_WAS_HOST="${PROD_WAS_HOST}" \
|
PROD_WAS_API="${PROD_WAS_API}" PROD_RAG_CHAT="${PROD_RAG_CHAT}" PROD_WAS_HOST="${PROD_WAS_HOST}" \
|
||||||
perl -i -pe '
|
perl -i -pe '
|
||||||
s|CHATBOT_API_BASE_URL:[^}]+}|CHATBOT_API_BASE_URL:$ENV{PROD_WAS_API}/}|g;
|
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|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://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|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://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;
|
s|url:\s*http://host\.docker\.internal:28012/agent/chat|url: \${RAG_AGENT_CHAT_URL:$ENV{PROD_RAG_CHAT}}|g;
|
||||||
' "${yml}"
|
' "${yml}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
exAiChatBot-chatbot2.0-agent)
|
exAiChatBot-chatbot2.0-agent)
|
||||||
if [[ -f "${dir}/env.template" ]]; then
|
if [[ -f "${dir}/env.template" ]]; then
|
||||||
PROD_WAS_API="${PROD_WAS_API}" perl -i -pe \
|
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"
|
's|^CHATBOT_API_BASE_URL=.*|CHATBOT_API_BASE_URL=$ENV{PROD_WAS_API}|' "${dir}/env.template"
|
||||||
# EXDEV_LOCAL 은 커밋 템플릿에 넣지 않음 (server-dev 전용)
|
# EXDEV_LOCAL 은 커밋 템플릿에 넣지 않음 (server-dev 전용)
|
||||||
perl -i -ne 'print unless /^EXDEV_LOCAL=/' "${dir}/env.template"
|
perl -i -ne 'print unless /^EXDEV_LOCAL=/' "${dir}/env.template"
|
||||||
fi
|
fi
|
||||||
if [[ -f "${dir}/env.example" ]]; then
|
if [[ -f "${dir}/env.example" ]]; then
|
||||||
PROD_WAS_API="${PROD_WAS_API}" perl -i -pe \
|
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
|
'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
|
perl -i -ne 'print unless /^EXDEV_LOCAL=/' "${dir}/env.example" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
if [[ -f "${dir}/scripts/agent/chatbot_tool_client.py" ]]; then
|
if [[ -f "${dir}/scripts/agent/chatbot_tool_client.py" ]]; then
|
||||||
# 운영 기본값(_PROD_WAS_API / legacy getenv default)만 복원.
|
# 운영 기본값(_PROD_WAS_API / legacy getenv default)만 복원.
|
||||||
PROD_WAS_API="${PROD_WAS_API}" perl -i -pe '
|
PROD_WAS_API="${PROD_WAS_API}" perl -i -pe '
|
||||||
s|_PROD_WAS_API = "http://[^"]+"|_PROD_WAS_API = "$ENV{PROD_WAS_API}"|g;
|
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\("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;
|
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"
|
' "${dir}/scripts/agent/chatbot_tool_client.py"
|
||||||
fi
|
fi
|
||||||
# monorepo overlay compose 는 push 대상에서 제외 (로컬 exdev 전용)
|
# monorepo overlay compose 는 push 대상에서 제외 (로컬 exdev 전용)
|
||||||
git -C "${dir}" checkout HEAD -- docker-compose.server.yml docker-compose.local.yml 2>/dev/null || true
|
git -C "${dir}" checkout HEAD -- docker-compose.server.yml docker-compose.local.yml 2>/dev/null || true
|
||||||
# .env 는 gitignore — 스테이징 실수 방지
|
# .env 는 gitignore — 스테이징 실수 방지
|
||||||
git -C "${dir}" reset HEAD -- .env .env.local 2>/dev/null || true
|
git -C "${dir}" reset HEAD -- .env .env.local 2>/dev/null || true
|
||||||
rm -f "${dir}/.env.local" 2>/dev/null || true
|
rm -f "${dir}/.env.local" 2>/dev/null || true
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
# 복원 후에도 개발서버 전용 호스트가 커밋 대상 설정 파일에 남으면 push 중단
|
# 복원 후에도 개발서버 전용 호스트가 커밋 대상 설정 파일에 남으면 push 중단
|
||||||
assert_no_exdev_endpoints_for_push() {
|
assert_no_exdev_endpoints_for_push() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local dir="${ROOT_DIR}/${path}"
|
local dir="${ROOT_DIR}/${path}"
|
||||||
local hits=""
|
local hits=""
|
||||||
|
|
||||||
case "${path}" in
|
case "${path}" in
|
||||||
chatbotApi-chatbot2.0-agent)
|
chatbotApi-chatbot2.0-agent)
|
||||||
hits="$(grep -nE 'mongodb://(127\.0\.0\.1|localhost)|host\.docker\.internal|jdbc:oracle:thin:@(127\.0\.0\.1|localhost)' \
|
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)"
|
"${dir}/src/main/resources/application.yml" 2>/dev/null || true)"
|
||||||
# troad 블록이 로컬 계정(OPR_MAIN)으로 바뀌었는지도 차단
|
# troad 블록이 로컬 계정(OPR_MAIN)으로 바뀌었는지도 차단
|
||||||
if awk '
|
if awk '
|
||||||
/^[[:space:]]*troad:[[:space:]]*$/ { in_troad=1; next }
|
/^[[:space:]]*troad:[[:space:]]*$/ { in_troad=1; next }
|
||||||
in_troad && /^[[:space:]]*[a-zA-Z0-9_-]+:[[:space:]]*$/ && $0 !~ /^[[:space:]]*troad:/ { in_troad=0 }
|
in_troad && /^[[:space:]]*[a-zA-Z0-9_-]+:[[:space:]]*$/ && $0 !~ /^[[:space:]]*troad:/ { in_troad=0 }
|
||||||
in_troad && /username:[[:space:]]*OPR_MAIN/ { found=1 }
|
in_troad && /username:[[:space:]]*OPR_MAIN/ { found=1 }
|
||||||
END { exit found ? 0 : 1 }
|
END { exit found ? 0 : 1 }
|
||||||
' "${dir}/src/main/resources/application.yml" 2>/dev/null; then
|
' "${dir}/src/main/resources/application.yml" 2>/dev/null; then
|
||||||
hits+=$'\n'"troad.username=OPR_MAIN (exdev local Oracle — must not commit)"
|
hits+=$'\n'"troad.username=OPR_MAIN (exdev local Oracle — must not commit)"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
kakaoChatbotSkill-chatbot2.0-agent)
|
kakaoChatbotSkill-chatbot2.0-agent)
|
||||||
hits="$(grep -nE '127\.0\.0\.1:(8086|28012)|host\.docker\.internal:(8086|28012)' \
|
hits="$(grep -nE '127\.0\.0\.1:(8086|28012)|host\.docker\.internal:(8086|28012)' \
|
||||||
"${dir}/src/main/resources/application.yml" 2>/dev/null || true)"
|
"${dir}/src/main/resources/application.yml" 2>/dev/null || true)"
|
||||||
;;
|
;;
|
||||||
exAiChatBot-chatbot2.0-agent)
|
exAiChatBot-chatbot2.0-agent)
|
||||||
hits="$(grep -nE '^(CHATBOT_API_BASE_URL|TOOL_API_BASE_URL)=.*(127\.0\.0\.1|host\.docker\.internal)|EXDEV_LOCAL=' \
|
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)"
|
"${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)' \
|
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)"
|
"${dir}/scripts/agent/chatbot_tool_client.py" 2>/dev/null || true)"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if [[ -n "${hits}" ]]; then
|
if [[ -n "${hits}" ]]; then
|
||||||
warn "${path}: 개발서버(exdev) 주소가 남아 push 중단"
|
warn "${path}: 개발서버(exdev) 주소가 남아 push 중단"
|
||||||
echo "${hits}" | sed 's/^/ /'
|
echo "${hits}" | sed 's/^/ /'
|
||||||
warn "start-all-server.sh 런타임 ENV 로만 exdev 주소를 쓰고, 소스 기본값은 운영 IP 를 유지하세요"
|
warn "start-all-server.sh 런타임 ENV 로만 exdev 주소를 쓰고, 소스 기본값은 운영 IP 를 유지하세요"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
submodule_is_cloned() {
|
submodule_is_cloned() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
[[ -e "${ROOT_DIR}/${path}/.git" ]]
|
[[ -e "${ROOT_DIR}/${path}/.git" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
repo_is_dirty() {
|
repo_is_dirty() {
|
||||||
! git diff --quiet || ! git diff --cached --quiet || [[ -n "$(git ls-files --others --exclude-standard)" ]]
|
! git diff --quiet || ! git diff --cached --quiet || [[ -n "$(git ls-files --others --exclude-standard)" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_branch() {
|
ensure_branch() {
|
||||||
local branch="$1"
|
local branch="$1"
|
||||||
local git_dir
|
local git_dir
|
||||||
# submodule 은 .git 이 파일일 수 있음 → rev-parse 사용
|
# submodule 은 .git 이 파일일 수 있음 → rev-parse 사용
|
||||||
git_dir="$(git rev-parse --git-dir 2>/dev/null || echo .git)"
|
git_dir="$(git rev-parse --git-dir 2>/dev/null || echo .git)"
|
||||||
if [[ -d "${git_dir}/rebase-merge" || -d "${git_dir}/rebase-apply" ]]; then
|
if [[ -d "${git_dir}/rebase-merge" || -d "${git_dir}/rebase-apply" ]]; then
|
||||||
warn "rebase 진행 중 — 수동 해결 후 재실행 (git rebase --continue / --abort)"
|
warn "rebase 진행 중 — 수동 해결 후 재실행 (git rebase --continue / --abort)"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if [[ -f "${git_dir}/MERGE_HEAD" ]]; then
|
if [[ -f "${git_dir}/MERGE_HEAD" ]]; then
|
||||||
warn "merge 진행 중 — 수동 해결 후 재실행"
|
warn "merge 진행 중 — 수동 해결 후 재실행"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local cur
|
local cur
|
||||||
cur="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo DETACHED)"
|
cur="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo DETACHED)"
|
||||||
if [[ "${cur}" == "${branch}" ]]; then
|
if [[ "${cur}" == "${branch}" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if git show-ref --verify --quiet "refs/heads/${branch}"; then
|
if git show-ref --verify --quiet "refs/heads/${branch}"; then
|
||||||
git checkout "${branch}"
|
git checkout "${branch}"
|
||||||
elif git show-ref --verify --quiet "refs/remotes/origin/${branch}"; then
|
elif git show-ref --verify --quiet "refs/remotes/origin/${branch}"; then
|
||||||
git checkout -B "${branch}" "origin/${branch}"
|
git checkout -B "${branch}" "origin/${branch}"
|
||||||
else
|
else
|
||||||
git checkout -B "${branch}"
|
git checkout -B "${branch}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
sync_one() {
|
sync_one() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local url branch old new stashed=0 ahead
|
local url branch old new stashed=0 ahead
|
||||||
|
|
||||||
url="$(git config -f .gitmodules "submodule.${path}.url" 2>/dev/null || true)"
|
url="$(git config -f .gitmodules "submodule.${path}.url" 2>/dev/null || true)"
|
||||||
if [[ -z "${url}" ]]; then
|
if [[ -z "${url}" ]]; then
|
||||||
warn "${path} — .gitmodules 에 없음, 건너뜀"
|
warn "${path} — .gitmodules 에 없음, 건너뜀"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 사용자 요구: main 금지, chatbot2.0-agent 고정
|
# 사용자 요구: main 금지, chatbot2.0-agent 고정
|
||||||
branch="$(git config -f .gitmodules "submodule.${path}.branch" || echo "${TARGET_BRANCH}")"
|
branch="$(git config -f .gitmodules "submodule.${path}.branch" || echo "${TARGET_BRANCH}")"
|
||||||
branch="${branch:-${TARGET_BRANCH}}"
|
branch="${branch:-${TARGET_BRANCH}}"
|
||||||
if [[ "${branch}" != "${TARGET_BRANCH}" ]]; then
|
if [[ "${branch}" != "${TARGET_BRANCH}" ]]; then
|
||||||
warn "${path} — .gitmodules branch=${branch} → ${TARGET_BRANCH} 로 강제"
|
warn "${path} — .gitmodules branch=${branch} → ${TARGET_BRANCH} 로 강제"
|
||||||
branch="${TARGET_BRANCH}"
|
branch="${TARGET_BRANCH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log ">>> ${path}"
|
log ">>> ${path}"
|
||||||
log " url=${url} branch=${branch}"
|
log " url=${url} branch=${branch}"
|
||||||
|
|
||||||
if ! submodule_is_cloned "${path}"; then
|
if ! submodule_is_cloned "${path}"; then
|
||||||
if $DRY_RUN; then
|
if $DRY_RUN; then
|
||||||
log " [dry-run] submodule clone 필요 — ./git_fetch_all.sh --init"
|
log " [dry-run] submodule clone 필요 — ./git_fetch_all.sh --init"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
if $INIT; then
|
if $INIT; then
|
||||||
clone_submodule_repo "${path}" "${url}" "${branch}" || return 1
|
clone_submodule_repo "${path}" "${url}" "${branch}" || return 1
|
||||||
else
|
else
|
||||||
warn "${path} — submodule 미초기화. --init 옵션 사용"
|
warn "${path} — submodule 미초기화. --init 옵션 사용"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
(
|
(
|
||||||
cd "${ROOT_DIR}/${path}"
|
cd "${ROOT_DIR}/${path}"
|
||||||
unset GIT_DIR GIT_WORK_TREE
|
unset GIT_DIR GIT_WORK_TREE
|
||||||
|
|
||||||
old="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
old="$(git rev-parse HEAD 2>/dev/null || echo none)"
|
||||||
|
|
||||||
if $DRY_RUN; then
|
if $DRY_RUN; then
|
||||||
ensure_branch "${branch}" || true
|
ensure_branch "${branch}" || true
|
||||||
git fetch origin "${branch}" 2>/dev/null || git fetch origin
|
git fetch origin "${branch}" 2>/dev/null || git fetch origin
|
||||||
new="$(git rev-parse "origin/${branch}" 2>/dev/null || echo unknown)"
|
new="$(git rev-parse "origin/${branch}" 2>/dev/null || echo unknown)"
|
||||||
if [[ "${old}" == "${new}" ]]; then
|
if [[ "${old}" == "${new}" ]]; then
|
||||||
log " [dry-run] 원격 최신: ${old:0:7}"
|
log " [dry-run] 원격 최신: ${old:0:7}"
|
||||||
else
|
else
|
||||||
log " [dry-run] 원격 변경 ${old:0:7} → ${new:0:7}"
|
log " [dry-run] 원격 변경 ${old:0:7} → ${new:0:7}"
|
||||||
git log --oneline "HEAD..origin/${branch}" 2>/dev/null | sed 's/^/ /' || true
|
git log --oneline "HEAD..origin/${branch}" 2>/dev/null | sed 's/^/ /' || true
|
||||||
fi
|
fi
|
||||||
if repo_is_dirty; then
|
if repo_is_dirty; then
|
||||||
log " [dry-run] 로컬 미커밋 변경 있음 → commit/push 대상"
|
log " [dry-run] 로컬 미커밋 변경 있음 → commit/push 대상"
|
||||||
git status -sb | sed 's/^/ /'
|
git status -sb | sed 's/^/ /'
|
||||||
else
|
else
|
||||||
log " [dry-run] 로컬 미커밋 변경 없음"
|
log " [dry-run] 로컬 미커밋 변경 없음"
|
||||||
fi
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 1) 원격 fetch + 브랜치 고정
|
# 1) 원격 fetch + 브랜치 고정
|
||||||
git fetch origin "${branch}"
|
git fetch origin "${branch}"
|
||||||
ensure_branch "${branch}" || exit 1
|
ensure_branch "${branch}" || exit 1
|
||||||
git branch --set-upstream-to="origin/${branch}" "${branch}" 2>/dev/null || true
|
git branch --set-upstream-to="origin/${branch}" "${branch}" 2>/dev/null || true
|
||||||
|
|
||||||
# 2) 로컬 미커밋 변경이 pull 을 막으면 stash
|
# 2) 로컬 미커밋 변경이 pull 을 막으면 stash
|
||||||
if repo_is_dirty; then
|
if repo_is_dirty; then
|
||||||
log " 로컬 변경 stash 후 원격 반영..."
|
log " 로컬 변경 stash 후 원격 반영..."
|
||||||
git stash push -u -m "git_fetch_all auto-stash $(date '+%Y-%m-%d %H:%M:%S')" || true
|
git stash push -u -m "git_fetch_all auto-stash $(date '+%Y-%m-%d %H:%M:%S')" || true
|
||||||
stashed=1
|
stashed=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 3) GitHub 변경사항 merge (ff → rebase → merge)
|
# 3) GitHub 변경사항 merge (ff → rebase → merge)
|
||||||
if [[ "$(git rev-parse HEAD)" != "$(git rev-parse "origin/${branch}")" ]]; then
|
if [[ "$(git rev-parse HEAD)" != "$(git rev-parse "origin/${branch}")" ]]; then
|
||||||
log " 원격 변경 merge: origin/${branch}"
|
log " 원격 변경 merge: origin/${branch}"
|
||||||
if git merge-base --is-ancestor HEAD "origin/${branch}" 2>/dev/null; then
|
if git merge-base --is-ancestor HEAD "origin/${branch}" 2>/dev/null; then
|
||||||
git merge --ff-only "origin/${branch}"
|
git merge --ff-only "origin/${branch}"
|
||||||
else
|
else
|
||||||
if ! git pull --rebase origin "${branch}"; then
|
if ! git pull --rebase origin "${branch}"; then
|
||||||
warn "${path} — rebase 실패, merge 로 재시도"
|
warn "${path} — rebase 실패, merge 로 재시도"
|
||||||
git rebase --abort 2>/dev/null || true
|
git rebase --abort 2>/dev/null || true
|
||||||
if ! git pull --no-rebase origin "${branch}"; then
|
if ! git pull --no-rebase origin "${branch}"; then
|
||||||
warn "${path} — merge 충돌. 수동 해결 필요"
|
warn "${path} — merge 충돌. 수동 해결 필요"
|
||||||
git status -sb | sed 's/^/ /'
|
git status -sb | sed 's/^/ /'
|
||||||
if [[ "${stashed}" -eq 1 ]]; then
|
if [[ "${stashed}" -eq 1 ]]; then
|
||||||
git stash pop || true
|
git stash pop || true
|
||||||
fi
|
fi
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
ok "${path} 원격 반영 $(git log -1 --oneline)"
|
ok "${path} 원격 반영 $(git log -1 --oneline)"
|
||||||
else
|
else
|
||||||
log " 원격과 동일 (fetch 완료)"
|
log " 원격과 동일 (fetch 완료)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 4) stash 복원
|
# 4) stash 복원
|
||||||
if [[ "${stashed}" -eq 1 ]]; then
|
if [[ "${stashed}" -eq 1 ]]; then
|
||||||
log " stash 복원..."
|
log " stash 복원..."
|
||||||
if ! git stash pop; then
|
if ! git stash pop; then
|
||||||
warn "${path} — stash pop 충돌. 수동 해결 후 commit/push"
|
warn "${path} — stash pop 충돌. 수동 해결 후 commit/push"
|
||||||
git status -sb | sed 's/^/ /'
|
git status -sb | sed 's/^/ /'
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 5) 로컬 수정사항 commit (exdev 주소는 복원 후·검증 통과 시에만)
|
# 5) 로컬 수정사항 commit (exdev 주소는 복원 후·검증 통과 시에만)
|
||||||
if $DO_COMMIT && repo_is_dirty; then
|
if $DO_COMMIT && repo_is_dirty; then
|
||||||
log " 로컬 변경 commit..."
|
log " 로컬 변경 commit..."
|
||||||
# push 전에 운영 WAS/RAG/Mongo 기본 주소 복원 (exdev 주소는 start-all-server.sh ENV 전용)
|
# push 전에 운영 WAS/RAG/Mongo 기본 주소 복원 (exdev 주소는 start-all-server.sh ENV 전용)
|
||||||
restore_production_endpoints_before_commit "${path}"
|
restore_production_endpoints_before_commit "${path}"
|
||||||
if ! assert_no_exdev_endpoints_for_push "${path}"; then
|
if ! assert_no_exdev_endpoints_for_push "${path}"; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
log " 운영 엔드포인트 기본값 유지: WAS ${PROD_WAS_API} / Mongo ${PROD_WAS_HOST}"
|
log " 운영 엔드포인트 기본값 유지: WAS ${PROD_WAS_API} / Mongo ${PROD_WAS_HOST}"
|
||||||
git add -A
|
git add -A
|
||||||
# .env 등 ignore 대상이 스테이징되지 않도록 한 번 더 정리
|
# .env 등 ignore 대상이 스테이징되지 않도록 한 번 더 정리
|
||||||
git reset HEAD -- .env .env.local 2>/dev/null || true
|
git reset HEAD -- .env .env.local 2>/dev/null || true
|
||||||
if git diff --cached --quiet; then
|
if git diff --cached --quiet; then
|
||||||
log " staged 변경 없음 (ignore 된 파일만 있을 수 있음)"
|
log " staged 변경 없음 (ignore 된 파일만 있을 수 있음)"
|
||||||
else
|
else
|
||||||
# staged diff 에 exdev 호스트가 남아 있으면 push 금지
|
# 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
|
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 중단"
|
warn "${path}: staged diff 에 개발서버 주소가 포함됨 — commit 중단"
|
||||||
git diff --cached --stat | sed 's/^/ /'
|
git diff --cached --stat | sed 's/^/ /'
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
git commit -m "$(cat <<EOF
|
git commit -m "$(cat <<EOF
|
||||||
sync: monorepo local updates ($(date '+%Y-%m-%d %H:%M'))
|
sync: monorepo local updates ($(date '+%Y-%m-%d %H:%M'))
|
||||||
|
|
||||||
git_fetch_all.sh 로 chatbot2.0-agent 동기화 후 로컬 수정을 반영한다.
|
git_fetch_all.sh 로 chatbot2.0-agent 동기화 후 로컬 수정을 반영한다.
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
ok "${path} commit $(git log -1 --oneline)"
|
ok "${path} commit $(git log -1 --oneline)"
|
||||||
fi
|
fi
|
||||||
elif ! $DO_COMMIT && repo_is_dirty; then
|
elif ! $DO_COMMIT && repo_is_dirty; then
|
||||||
log " [--no-commit] 미커밋 변경 유지 (운영 주소 복원 미적용)"
|
log " [--no-commit] 미커밋 변경 유지 (운영 주소 복원 미적용)"
|
||||||
git status -sb | sed 's/^/ /'
|
git status -sb | sed 's/^/ /'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 6) push (chatbot2.0-agent 만) — push 직전 운영 주소 재검증
|
# 6) push (chatbot2.0-agent 만) — push 직전 운영 주소 재검증
|
||||||
if $DO_PUSH; then
|
if $DO_PUSH; then
|
||||||
restore_production_endpoints_before_commit "${path}"
|
restore_production_endpoints_before_commit "${path}"
|
||||||
if ! assert_no_exdev_endpoints_for_push "${path}"; then
|
if ! assert_no_exdev_endpoints_for_push "${path}"; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# 복원으로 생긴 미커밋이 있으면 반영
|
# 복원으로 생긴 미커밋이 있으면 반영
|
||||||
if $DO_COMMIT && repo_is_dirty; then
|
if $DO_COMMIT && repo_is_dirty; then
|
||||||
git add -A
|
git add -A
|
||||||
git reset HEAD -- .env .env.local 2>/dev/null || true
|
git reset HEAD -- .env .env.local 2>/dev/null || true
|
||||||
if ! git diff --cached --quiet; then
|
if ! git diff --cached --quiet; then
|
||||||
git commit -m "$(cat <<EOF
|
git commit -m "$(cat <<EOF
|
||||||
chore: restore production endpoints before push
|
chore: restore production endpoints before push
|
||||||
|
|
||||||
git_fetch_all.sh — exdev 런타임 주소를 커밋에서 제거한다.
|
git_fetch_all.sh — exdev 런타임 주소를 커밋에서 제거한다.
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
ahead="$(git rev-list --count "origin/${branch}..HEAD" 2>/dev/null || echo 0)"
|
ahead="$(git rev-list --count "origin/${branch}..HEAD" 2>/dev/null || echo 0)"
|
||||||
if [[ "${ahead}" != "0" ]]; then
|
if [[ "${ahead}" != "0" ]]; then
|
||||||
log " push origin ${branch} (ahead ${ahead})..."
|
log " push origin ${branch} (ahead ${ahead})..."
|
||||||
if git push origin "${branch}"; then
|
if git push origin "${branch}"; then
|
||||||
ok "${path} push 완료"
|
ok "${path} push 완료"
|
||||||
else
|
else
|
||||||
warn "${path} — push 실패 (대용량 파일/권한 등). 로그 확인"
|
warn "${path} — push 실패 (대용량 파일/권한 등). 로그 확인"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log " push 생략 (원격과 동일)"
|
log " push 생략 (원격과 동일)"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log " [--no-push] push 생략"
|
log " [--no-push] push 생략"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new="$(git rev-parse HEAD)"
|
new="$(git rev-parse HEAD)"
|
||||||
log " HEAD ${old:0:7} → ${new:0:7} $(git log -1 --oneline)"
|
log " HEAD ${old:0:7} → ${new:0:7} $(git log -1 --oneline)"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
failures=0
|
failures=0
|
||||||
for path in "${TARGET_PATHS[@]}"; do
|
for path in "${TARGET_PATHS[@]}"; do
|
||||||
if ! sync_one "${path}"; then
|
if ! sync_one "${path}"; then
|
||||||
failures=$((failures + 1))
|
failures=$((failures + 1))
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
done
|
done
|
||||||
|
|
||||||
if ! $DRY_RUN; then
|
if ! $DRY_RUN; then
|
||||||
restore_monorepo_overlays
|
restore_monorepo_overlays
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "=========================================="
|
log "=========================================="
|
||||||
log "submodule 상태:"
|
log "submodule 상태:"
|
||||||
git submodule status 2>/dev/null || true
|
git submodule status 2>/dev/null || true
|
||||||
for path in "${TARGET_PATHS[@]}"; do
|
for path in "${TARGET_PATHS[@]}"; do
|
||||||
if submodule_is_cloned "${path}"; then
|
if submodule_is_cloned "${path}"; then
|
||||||
printf " %-36s %s\n" "${path}" "$(git -C "${ROOT_DIR}/${path}" log -1 --oneline 2>/dev/null || echo '?')"
|
printf " %-36s %s\n" "${path}" "$(git -C "${ROOT_DIR}/${path}" log -1 --oneline 2>/dev/null || echo '?')"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
log "=========================================="
|
log "=========================================="
|
||||||
|
|
||||||
if [[ ${failures} -gt 0 ]]; then
|
if [[ ${failures} -gt 0 ]]; then
|
||||||
warn "실패 ${failures}개 — 로그 확인"
|
warn "실패 ${failures}개 — 로그 확인"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if $DRY_RUN; then
|
if $DRY_RUN; then
|
||||||
log "완료 — dry-run (변경 없음)"
|
log "완료 — dry-run (변경 없음)"
|
||||||
elif $DO_PUSH; then
|
elif $DO_PUSH; then
|
||||||
log "완료 — ${TARGET_BRANCH}: fetch/merge → commit → push"
|
log "완료 — ${TARGET_BRANCH}: fetch/merge → commit → push"
|
||||||
else
|
else
|
||||||
log "완료 — ${TARGET_BRANCH}: fetch/merge$($DO_COMMIT && echo ' → commit' || true) (push 생략)"
|
log "완료 — ${TARGET_BRANCH}: fetch/merge$($DO_COMMIT && echo ' → commit' || true) (push 생략)"
|
||||||
fi
|
fi
|
||||||
log "monorepo 포인터 갱신: git add *-chatbot2.0-agent && git status"
|
log "monorepo 포인터 갱신: git add *-chatbot2.0-agent && git status"
|
||||||
|
|||||||
+124
-124
@@ -1,124 +1,124 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Minimal rsync stand-in for Windows Git Bash (tar-over-ssh).
|
# Minimal rsync stand-in for Windows Git Bash (tar-over-ssh).
|
||||||
# Supports the flags used by server-dev/sync-submodules-rsync.sh:
|
# Supports the flags used by server-dev/sync-submodules-rsync.sh:
|
||||||
# rsync -az [--delete] [--exclude PATTERN ...] SRC/ HOST:DEST/
|
# rsync -az [--delete] [--exclude PATTERN ...] SRC/ HOST:DEST/
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
DELETE=false
|
DELETE=false
|
||||||
EXCLUDES=()
|
EXCLUDES=()
|
||||||
ARGS=()
|
ARGS=()
|
||||||
|
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-az|-a|-z|-v|-q|-P|--progress) shift ;;
|
-az|-a|-z|-v|-q|-P|--progress) shift ;;
|
||||||
--delete) DELETE=true; shift ;;
|
--delete) DELETE=true; shift ;;
|
||||||
--exclude)
|
--exclude)
|
||||||
EXCLUDES+=("$2"); shift 2 ;;
|
EXCLUDES+=("$2"); shift 2 ;;
|
||||||
--exclude=*)
|
--exclude=*)
|
||||||
EXCLUDES+=("${1#--exclude=}"); shift ;;
|
EXCLUDES+=("${1#--exclude=}"); shift ;;
|
||||||
-*)
|
-*)
|
||||||
# ignore unknown short/long flags used by callers
|
# ignore unknown short/long flags used by callers
|
||||||
shift ;;
|
shift ;;
|
||||||
*)
|
*)
|
||||||
ARGS+=("$1"); shift ;;
|
ARGS+=("$1"); shift ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if ((${#ARGS[@]} < 2)); then
|
if ((${#ARGS[@]} < 2)); then
|
||||||
echo "rsync-shim: need SRC and DEST" >&2
|
echo "rsync-shim: need SRC and DEST" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SRC="${ARGS[0]}"
|
SRC="${ARGS[0]}"
|
||||||
DEST="${ARGS[1]}"
|
DEST="${ARGS[1]}"
|
||||||
|
|
||||||
# Git Bash /d/foo → Windows D:/foo (OpenSSH scp 용)
|
# Git Bash /d/foo → Windows D:/foo (OpenSSH scp 용)
|
||||||
to_win_path() {
|
to_win_path() {
|
||||||
local p="$1"
|
local p="$1"
|
||||||
if [[ "${p}" =~ ^/([a-zA-Z])/(.*)$ ]]; then
|
if [[ "${p}" =~ ^/([a-zA-Z])/(.*)$ ]]; then
|
||||||
local drive rest
|
local drive rest
|
||||||
drive="$(echo "${BASH_REMATCH[1]}" | tr 'a-z' 'A-Z')"
|
drive="$(echo "${BASH_REMATCH[1]}" | tr 'a-z' 'A-Z')"
|
||||||
rest="${BASH_REMATCH[2]}"
|
rest="${BASH_REMATCH[2]}"
|
||||||
echo "${drive}:/${rest}"
|
echo "${drive}:/${rest}"
|
||||||
else
|
else
|
||||||
echo "${p}"
|
echo "${p}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
copy_file_remote() {
|
copy_file_remote() {
|
||||||
local src_file="$1"
|
local src_file="$1"
|
||||||
local dest="$2"
|
local dest="$2"
|
||||||
local win_src
|
local win_src
|
||||||
win_src="$(to_win_path "${src_file}")"
|
win_src="$(to_win_path "${src_file}")"
|
||||||
if [[ -f "${win_src}" || -f "${src_file}" ]]; then
|
if [[ -f "${win_src}" || -f "${src_file}" ]]; then
|
||||||
local local_src="${src_file}"
|
local local_src="${src_file}"
|
||||||
[[ -f "${win_src}" ]] && local_src="${win_src}"
|
[[ -f "${win_src}" ]] && local_src="${win_src}"
|
||||||
scp -o BatchMode=yes -q "${local_src}" "${dest}" && return 0
|
scp -o BatchMode=yes -q "${local_src}" "${dest}" && return 0
|
||||||
fi
|
fi
|
||||||
# fallback: tar stream single file
|
# fallback: tar stream single file
|
||||||
local base dir
|
local base dir
|
||||||
base="$(basename "${src_file}")"
|
base="$(basename "${src_file}")"
|
||||||
dir="$(cd "$(dirname "${src_file}")" && pwd)"
|
dir="$(cd "$(dirname "${src_file}")" && pwd)"
|
||||||
local host="${dest%%:*}"
|
local host="${dest%%:*}"
|
||||||
local remote="${dest#*:}"
|
local remote="${dest#*:}"
|
||||||
local remote_dir remote_base
|
local remote_dir remote_base
|
||||||
if [[ "${remote}" == */ ]]; then
|
if [[ "${remote}" == */ ]]; then
|
||||||
remote_dir="${remote%/}"
|
remote_dir="${remote%/}"
|
||||||
remote_base="${base}"
|
remote_base="${base}"
|
||||||
else
|
else
|
||||||
remote_dir="$(dirname "${remote}")"
|
remote_dir="$(dirname "${remote}")"
|
||||||
remote_base="$(basename "${remote}")"
|
remote_base="$(basename "${remote}")"
|
||||||
fi
|
fi
|
||||||
ssh -o BatchMode=yes "${host}" "mkdir -p '${remote_dir}'"
|
ssh -o BatchMode=yes "${host}" "mkdir -p '${remote_dir}'"
|
||||||
tar -C "${dir}" -cf - "${base}" \
|
tar -C "${dir}" -cf - "${base}" \
|
||||||
| ssh -o BatchMode=yes "${host}" "tar -C '${remote_dir}' -xf - && if [[ '${remote_base}' != '${base}' ]]; then mv '${remote_dir}/${base}' '${remote_dir}/${remote_base}'; fi"
|
| ssh -o BatchMode=yes "${host}" "tar -C '${remote_dir}' -xf - && if [[ '${remote_base}' != '${base}' ]]; then mv '${remote_dir}/${base}' '${remote_dir}/${remote_base}'; fi"
|
||||||
}
|
}
|
||||||
|
|
||||||
# file → file copy (overlay yml etc.)
|
# file → file copy (overlay yml etc.)
|
||||||
if [[ -f "${SRC}" && "${DEST}" == *:* ]]; then
|
if [[ -f "${SRC}" && "${DEST}" == *:* ]]; then
|
||||||
copy_file_remote "${SRC}" "${DEST}"
|
copy_file_remote "${SRC}" "${DEST}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# normalize trailing slash semantics like rsync SRC/
|
# normalize trailing slash semantics like rsync SRC/
|
||||||
SRC_DIR="${SRC%/}"
|
SRC_DIR="${SRC%/}"
|
||||||
if [[ ! -d "${SRC_DIR}" ]]; then
|
if [[ ! -d "${SRC_DIR}" ]]; then
|
||||||
# allow copying a single file into a remote dir
|
# allow copying a single file into a remote dir
|
||||||
if [[ -f "${SRC_DIR}" ]]; then
|
if [[ -f "${SRC_DIR}" ]]; then
|
||||||
copy_file_remote "${SRC_DIR}" "${DEST}"
|
copy_file_remote "${SRC_DIR}" "${DEST}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
echo "rsync-shim: source not found: ${SRC}" >&2
|
echo "rsync-shim: source not found: ${SRC}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${DEST}" != *:* ]]; then
|
if [[ "${DEST}" != *:* ]]; then
|
||||||
echo "rsync-shim: only remote DEST (host:path) supported" >&2
|
echo "rsync-shim: only remote DEST (host:path) supported" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
HOST="${DEST%%:*}"
|
HOST="${DEST%%:*}"
|
||||||
REMOTE_PATH="${DEST#*:}"
|
REMOTE_PATH="${DEST#*:}"
|
||||||
REMOTE_PATH="${REMOTE_PATH%/}"
|
REMOTE_PATH="${REMOTE_PATH%/}"
|
||||||
|
|
||||||
TAR_EXCLUDES=()
|
TAR_EXCLUDES=()
|
||||||
for pat in "${EXCLUDES[@]}"; do
|
for pat in "${EXCLUDES[@]}"; do
|
||||||
TAR_EXCLUDES+=(--exclude "${pat}")
|
TAR_EXCLUDES+=(--exclude "${pat}")
|
||||||
done
|
done
|
||||||
|
|
||||||
ssh -o BatchMode=yes "${HOST}" "mkdir -p '${REMOTE_PATH}'"
|
ssh -o BatchMode=yes "${HOST}" "mkdir -p '${REMOTE_PATH}'"
|
||||||
|
|
||||||
if $DELETE; then
|
if $DELETE; then
|
||||||
# wipe remote dir contents then unpack (approx --delete)
|
# wipe remote dir contents then unpack (approx --delete)
|
||||||
# 서버 public-* 등이 root/docker 소유일 수 있어 실패해도 이어서 덮어쓴다
|
# 서버 public-* 등이 root/docker 소유일 수 있어 실패해도 이어서 덮어쓴다
|
||||||
ssh -o BatchMode=yes "${HOST}" \
|
ssh -o BatchMode=yes "${HOST}" \
|
||||||
"chmod -R u+w '${REMOTE_PATH}' 2>/dev/null || true; \
|
"chmod -R u+w '${REMOTE_PATH}' 2>/dev/null || true; \
|
||||||
find '${REMOTE_PATH}' -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true" \
|
find '${REMOTE_PATH}' -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true" \
|
||||||
|| true
|
|| true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# shellcheck disable=SC2086
|
# shellcheck disable=SC2086
|
||||||
tar -C "${SRC_DIR}" "${TAR_EXCLUDES[@]}" -cf - . \
|
tar -C "${SRC_DIR}" "${TAR_EXCLUDES[@]}" -cf - . \
|
||||||
| ssh -o BatchMode=yes "${HOST}" "tar -C '${REMOTE_PATH}' -xf -"
|
| ssh -o BatchMode=yes "${HOST}" "tar -C '${REMOTE_PATH}' -xf -"
|
||||||
|
|
||||||
echo "rsync-shim: synced ${SRC_DIR}/ -> ${HOST}:${REMOTE_PATH}/"
|
echo "rsync-shim: synced ${SRC_DIR}/ -> ${HOST}:${REMOTE_PATH}/"
|
||||||
|
|||||||
+154
-154
@@ -1,154 +1,154 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# exAichatbot_agent — Gitea push → exdev 서버 빌드·배포
|
# exAichatbot_agent — Gitea push → exdev 서버 빌드·배포
|
||||||
# 설치: /Volumes/ADATA/git/deploy-exAichatbot_agent.sh
|
# 설치: /Volumes/ADATA/git/deploy-exAichatbot_agent.sh
|
||||||
# 호출: post-receive (main 브랜치 push)
|
# 호출: post-receive (main 브랜치 push)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
GIT_DIR="${GIT_DIR:-/Volumes/ADATA/gitea/data/gitea-repositories/aidev/exaichatbot_agent.git}"
|
GIT_DIR="${GIT_DIR:-/Volumes/ADATA/gitea/data/gitea-repositories/aidev/exaichatbot_agent.git}"
|
||||||
WORK_TREE="${WORK_TREE:-/Users/aidev/dev/exAichatbot_agent}"
|
WORK_TREE="${WORK_TREE:-/Users/aidev/dev/exAichatbot_agent}"
|
||||||
BRANCH="${BRANCH:-main}"
|
BRANCH="${BRANCH:-main}"
|
||||||
DEPLOY_LOG="${DEPLOY_LOG:-/Volumes/ADATA/logs/deploy/exAichatbot_agent.log}"
|
DEPLOY_LOG="${DEPLOY_LOG:-/Volumes/ADATA/logs/deploy/exAichatbot_agent.log}"
|
||||||
LOG_TAG="[exAichatbot_agent-deploy]"
|
LOG_TAG="[exAichatbot_agent-deploy]"
|
||||||
|
|
||||||
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/Applications/Docker.app/Contents/Resources/bin:${PATH:-/usr/bin:/bin}"
|
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/Applications/Docker.app/Contents/Resources/bin:${PATH:-/usr/bin:/bin}"
|
||||||
|
|
||||||
mkdir -p "$(dirname "$DEPLOY_LOG")"
|
mkdir -p "$(dirname "$DEPLOY_LOG")"
|
||||||
exec >> "$DEPLOY_LOG" 2>&1
|
exec >> "$DEPLOY_LOG" 2>&1
|
||||||
|
|
||||||
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $LOG_TAG $*"; }
|
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $LOG_TAG $*"; }
|
||||||
fail() { log "[ERROR] $*"; exit 1; }
|
fail() { log "[ERROR] $*"; exit 1; }
|
||||||
|
|
||||||
LOCK_FILE="/tmp/exAichatbot_agent_deploy.lock"
|
LOCK_FILE="/tmp/exAichatbot_agent_deploy.lock"
|
||||||
if [[ -f "$LOCK_FILE" ]]; then
|
if [[ -f "$LOCK_FILE" ]]; then
|
||||||
LOCK_PID="$(cat "$LOCK_FILE" 2>/dev/null || echo "")"
|
LOCK_PID="$(cat "$LOCK_FILE" 2>/dev/null || echo "")"
|
||||||
if [[ -n "$LOCK_PID" ]] && kill -0 "$LOCK_PID" 2>/dev/null; then
|
if [[ -n "$LOCK_PID" ]] && kill -0 "$LOCK_PID" 2>/dev/null; then
|
||||||
fail "다른 배포 진행 중 (PID: $LOCK_PID)"
|
fail "다른 배포 진행 중 (PID: $LOCK_PID)"
|
||||||
fi
|
fi
|
||||||
rm -f "$LOCK_FILE"
|
rm -f "$LOCK_FILE"
|
||||||
fi
|
fi
|
||||||
echo $$ > "$LOCK_FILE"
|
echo $$ > "$LOCK_FILE"
|
||||||
DEPLOY_OK=false
|
DEPLOY_OK=false
|
||||||
trap 'rm -f "$LOCK_FILE"; if ! $DEPLOY_OK; then log "[ERROR] 배포 실패"; fi; log "락 해제"' EXIT
|
trap 'rm -f "$LOCK_FILE"; if ! $DEPLOY_OK; then log "[ERROR] 배포 실패"; fi; log "락 해제"' EXIT
|
||||||
|
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--start-only) START_ONLY=true ;;
|
--start-only) START_ONLY=true ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
START_ONLY="${START_ONLY:-false}"
|
START_ONLY="${START_ONLY:-false}"
|
||||||
|
|
||||||
log "======================================================"
|
log "======================================================"
|
||||||
log " exAichatbot Agent 2.0 배포 시작"
|
log " exAichatbot Agent 2.0 배포 시작"
|
||||||
log " GIT_DIR : $GIT_DIR"
|
log " GIT_DIR : $GIT_DIR"
|
||||||
log " WORK_TREE : $WORK_TREE"
|
log " WORK_TREE : $WORK_TREE"
|
||||||
log " BRANCH : $BRANCH"
|
log " BRANCH : $BRANCH"
|
||||||
log " START_ONLY: $START_ONLY"
|
log " START_ONLY: $START_ONLY"
|
||||||
log "======================================================"
|
log "======================================================"
|
||||||
|
|
||||||
[[ -d "$GIT_DIR" ]] || fail "GIT_DIR 없음: $GIT_DIR"
|
[[ -d "$GIT_DIR" ]] || fail "GIT_DIR 없음: $GIT_DIR"
|
||||||
mkdir -p "$WORK_TREE"
|
mkdir -p "$WORK_TREE"
|
||||||
|
|
||||||
chmod +x "$WORK_TREE/server-dev/"*.sh 2>/dev/null || true
|
chmod +x "$WORK_TREE/server-dev/"*.sh 2>/dev/null || true
|
||||||
chmod +x "$WORK_TREE/git_fetch_all.sh" 2>/dev/null || true
|
chmod +x "$WORK_TREE/git_fetch_all.sh" 2>/dev/null || true
|
||||||
chmod +x "$WORK_TREE/chatbotApp-chatbot2.0-agent/build.sh" 2>/dev/null || true
|
chmod +x "$WORK_TREE/chatbotApp-chatbot2.0-agent/build.sh" 2>/dev/null || true
|
||||||
|
|
||||||
sync_worktree_submodules() {
|
sync_worktree_submodules() {
|
||||||
local tree="$1"
|
local tree="$1"
|
||||||
local path ready=0
|
local path ready=0
|
||||||
for path in chatbotApp-chatbot2.0-agent chatbotApi-chatbot2.0-agent \
|
for path in chatbotApp-chatbot2.0-agent chatbotApi-chatbot2.0-agent \
|
||||||
exAiChatBot-chatbot2.0-agent kakaoChatbotSkill-chatbot2.0-agent; do
|
exAiChatBot-chatbot2.0-agent kakaoChatbotSkill-chatbot2.0-agent; do
|
||||||
if [[ -f "${tree}/${path}/build.gradle" ]] || [[ -f "${tree}/${path}/build.sh" ]] \
|
if [[ -f "${tree}/${path}/build.gradle" ]] || [[ -f "${tree}/${path}/build.sh" ]] \
|
||||||
|| [[ -f "${tree}/${path}/docker/service-slim.Dockerfile" ]]; then
|
|| [[ -f "${tree}/${path}/docker/service-slim.Dockerfile" ]]; then
|
||||||
ready=$((ready + 1))
|
ready=$((ready + 1))
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [[ ${ready} -ge 4 ]]; then
|
if [[ ${ready} -ge 4 ]]; then
|
||||||
log "submodule 소스 이미 존재 (Gitea/rsync) — 공식 GitHub fetch 생략"
|
log "submodule 소스 이미 존재 (Gitea/rsync) — 공식 GitHub fetch 생략"
|
||||||
if [[ -f "${tree}/server-dev/docker-compose.rag.server.yml" ]]; then
|
if [[ -f "${tree}/server-dev/docker-compose.rag.server.yml" ]]; then
|
||||||
cp "${tree}/server-dev/docker-compose.rag.server.yml" \
|
cp "${tree}/server-dev/docker-compose.rag.server.yml" \
|
||||||
"${tree}/exAiChatBot-chatbot2.0-agent/docker-compose.server.yml" 2>/dev/null || true
|
"${tree}/exAiChatBot-chatbot2.0-agent/docker-compose.server.yml" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
# 공식 GitHub 는 deploy 경로에서 절대 동기화/push 하지 않음.
|
# 공식 GitHub 는 deploy 경로에서 절대 동기화/push 하지 않음.
|
||||||
# 소스가 비어 있으면 로컬 Mac 의 rsync (sync-submodules-rsync.sh) 가 필요.
|
# 소스가 비어 있으면 로컬 Mac 의 rsync (sync-submodules-rsync.sh) 가 필요.
|
||||||
log "[WARN] submodule 소스 부족 (${ready}/4) — 공식 GitHub fetch 안 함. 로컬 rsync 필요"
|
log "[WARN] submodule 소스 부족 (${ready}/4) — 공식 GitHub fetch 안 함. 로컬 rsync 필요"
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve_java17_home() {
|
resolve_java17_home() {
|
||||||
local home="" candidate
|
local home="" candidate
|
||||||
for candidate in \
|
for candidate in \
|
||||||
"/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home" \
|
"/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home" \
|
||||||
"/usr/local/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home"; do
|
"/usr/local/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home"; do
|
||||||
if [[ -x "${candidate}/bin/java" ]]; then
|
if [[ -x "${candidate}/bin/java" ]]; then
|
||||||
home="${candidate}"
|
home="${candidate}"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [[ -z "${home}" ]] && /usr/libexec/java_home -v 17 >/dev/null 2>&1; then
|
if [[ -z "${home}" ]] && /usr/libexec/java_home -v 17 >/dev/null 2>&1; then
|
||||||
home="$(/usr/libexec/java_home -v 17)"
|
home="$(/usr/libexec/java_home -v 17)"
|
||||||
fi
|
fi
|
||||||
[[ -n "${home}" ]] || fail "Java 17 필요 — brew install openjdk@17"
|
[[ -n "${home}" ]] || fail "Java 17 필요 — brew install openjdk@17"
|
||||||
echo "${home}"
|
echo "${home}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 서비스 중지 전에 Java 컴파일만 검증 — 실패 시 가동 중인 스택을 유지한다.
|
# 서비스 중지 전에 Java 컴파일만 검증 — 실패 시 가동 중인 스택을 유지한다.
|
||||||
# Gitea post-receive / 비로그인 SSH 는 PATH 에 brew java 가 없고 /usr/bin/java 는 stub 이므로
|
# Gitea post-receive / 비로그인 SSH 는 PATH 에 brew java 가 없고 /usr/bin/java 는 stub 이므로
|
||||||
# JAVA_HOME 을 반드시 export 해야 gradlew 부트스트랩이 성공한다.
|
# JAVA_HOME 을 반드시 export 해야 gradlew 부트스트랩이 성공한다.
|
||||||
preflight_java_compile() {
|
preflight_java_compile() {
|
||||||
local java_home dir
|
local java_home dir
|
||||||
java_home="$(resolve_java17_home)"
|
java_home="$(resolve_java17_home)"
|
||||||
export JAVA_HOME="${java_home}"
|
export JAVA_HOME="${java_home}"
|
||||||
export PATH="${JAVA_HOME}/bin:/opt/homebrew/bin:/opt/homebrew/sbin:${PATH}"
|
export PATH="${JAVA_HOME}/bin:/opt/homebrew/bin:/opt/homebrew/sbin:${PATH}"
|
||||||
log "[Phase 0] Java 사전 컴파일 검증 (Java 17: ${JAVA_HOME})"
|
log "[Phase 0] Java 사전 컴파일 검증 (Java 17: ${JAVA_HOME})"
|
||||||
for dir in \
|
for dir in \
|
||||||
"${WORK_TREE}/kakaoChatbotSkill-chatbot2.0-agent" \
|
"${WORK_TREE}/kakaoChatbotSkill-chatbot2.0-agent" \
|
||||||
"${WORK_TREE}/chatbotApi-chatbot2.0-agent"; do
|
"${WORK_TREE}/chatbotApi-chatbot2.0-agent"; do
|
||||||
[[ -x "${dir}/gradlew" ]] || fail "gradlew 없음: ${dir}"
|
[[ -x "${dir}/gradlew" ]] || fail "gradlew 없음: ${dir}"
|
||||||
log " compileJava: $(basename "${dir}")"
|
log " compileJava: $(basename "${dir}")"
|
||||||
if ! (
|
if ! (
|
||||||
cd "${dir}"
|
cd "${dir}"
|
||||||
export JAVA_HOME
|
export JAVA_HOME
|
||||||
export PATH="${JAVA_HOME}/bin:${PATH}"
|
export PATH="${JAVA_HOME}/bin:${PATH}"
|
||||||
./gradlew -Dorg.gradle.java.home="${JAVA_HOME}" compileJava --quiet
|
./gradlew -Dorg.gradle.java.home="${JAVA_HOME}" compileJava --quiet
|
||||||
); then
|
); then
|
||||||
fail "사전 컴파일 실패: $(basename "${dir}") — 기존 서비스는 그대로 유지됨"
|
fail "사전 컴파일 실패: $(basename "${dir}") — 기존 서비스는 그대로 유지됨"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
log " ✅ Java 사전 컴파일 OK"
|
log " ✅ Java 사전 컴파일 OK"
|
||||||
}
|
}
|
||||||
|
|
||||||
if ! $START_ONLY; then
|
if ! $START_ONLY; then
|
||||||
log "[Phase 2] checkout (Gitea work tree) — 공식 GitHub 미사용"
|
log "[Phase 2] checkout (Gitea work tree) — 공식 GitHub 미사용"
|
||||||
git --work-tree="$WORK_TREE" --git-dir="$GIT_DIR" checkout -f "$BRANCH"
|
git --work-tree="$WORK_TREE" --git-dir="$GIT_DIR" checkout -f "$BRANCH"
|
||||||
rm -f "$WORK_TREE/.git"
|
rm -f "$WORK_TREE/.git"
|
||||||
sync_worktree_submodules "$WORK_TREE"
|
sync_worktree_submodules "$WORK_TREE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
preflight_java_compile
|
preflight_java_compile
|
||||||
|
|
||||||
log "[Phase 1] stop-all-server.sh"
|
log "[Phase 1] stop-all-server.sh"
|
||||||
"$WORK_TREE/server-dev/stop-all-server.sh" --keep-caddy || true
|
"$WORK_TREE/server-dev/stop-all-server.sh" --keep-caddy || true
|
||||||
|
|
||||||
log "[Phase 3] .env 준비"
|
log "[Phase 3] .env 준비"
|
||||||
ENV_FILE="$WORK_TREE/exAiChatBot-chatbot2.0-agent/.env"
|
ENV_FILE="$WORK_TREE/exAiChatBot-chatbot2.0-agent/.env"
|
||||||
if [[ ! -f "$ENV_FILE" ]]; then
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
cp "$WORK_TREE/server-dev/env.exdev-server.template" "$ENV_FILE"
|
cp "$WORK_TREE/server-dev/env.exdev-server.template" "$ENV_FILE"
|
||||||
log ".env 생성 (env.exdev-server.template)"
|
log ".env 생성 (env.exdev-server.template)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "[Phase 4] start-all-server.sh (기동)"
|
log "[Phase 4] start-all-server.sh (기동)"
|
||||||
START_ARGS=(--skip-legacy-stop --no-pull --no-push)
|
START_ARGS=(--skip-legacy-stop --no-pull --no-push)
|
||||||
if $START_ONLY || [[ -f "$WORK_TREE/chatbotApp-chatbot2.0-agent/public/index.html" ]]; then
|
if $START_ONLY || [[ -f "$WORK_TREE/chatbotApp-chatbot2.0-agent/public/index.html" ]]; then
|
||||||
START_ARGS+=(--no-build)
|
START_ARGS+=(--no-build)
|
||||||
fi
|
fi
|
||||||
"$WORK_TREE/server-dev/start-all-server.sh" "${START_ARGS[@]}"
|
"$WORK_TREE/server-dev/start-all-server.sh" "${START_ARGS[@]}"
|
||||||
|
|
||||||
COMMIT="$(git --git-dir="$GIT_DIR" rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
COMMIT="$(git --git-dir="$GIT_DIR" rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||||
DEPLOY_OK=true
|
DEPLOY_OK=true
|
||||||
log "======================================================"
|
log "======================================================"
|
||||||
log " 배포 완료 — $COMMIT"
|
log " 배포 완료 — $COMMIT"
|
||||||
log " UI: https://chatbot.exdev.co.kr/chatbot/ai/"
|
log " UI: https://chatbot.exdev.co.kr/chatbot/ai/"
|
||||||
log "======================================================"
|
log "======================================================"
|
||||||
|
|||||||
@@ -1,86 +1,86 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Gitea push 후 chatbot.exdev.co.kr 배포 완료 대기
|
# Gitea push 후 chatbot.exdev.co.kr 배포 완료 대기
|
||||||
# - UI 응답 또는 배포 로그의 "배포 완료" 확인
|
# - UI 응답 또는 배포 로그의 "배포 완료" 확인
|
||||||
# - "배포 실패"/컴파일 오류는 즉시 종료 (900s 무의미 대기 방지)
|
# - "배포 실패"/컴파일 오류는 즉시 종료 (900s 무의미 대기 방지)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
EXDEV_SSH="${EXDEV_SSH:-aidev@chatbot.exdev.co.kr}"
|
EXDEV_SSH="${EXDEV_SSH:-aidev@chatbot.exdev.co.kr}"
|
||||||
DEPLOY_LOG="${DEPLOY_LOG:-/Volumes/ADATA/logs/deploy/exAichatbot_agent.log}"
|
DEPLOY_LOG="${DEPLOY_LOG:-/Volumes/ADATA/logs/deploy/exAichatbot_agent.log}"
|
||||||
PUBLIC_URL="${PUBLIC_BASE_URL:-https://chatbot.exdev.co.kr}/chatbot/ai/"
|
PUBLIC_URL="${PUBLIC_BASE_URL:-https://chatbot.exdev.co.kr}/chatbot/ai/"
|
||||||
MAX_WAIT="${DEPLOY_WAIT_SEC:-900}"
|
MAX_WAIT="${DEPLOY_WAIT_SEC:-900}"
|
||||||
|
|
||||||
log() { echo "[wait-deploy] $*"; }
|
log() { echo "[wait-deploy] $*"; }
|
||||||
|
|
||||||
ssh_cmd() {
|
ssh_cmd() {
|
||||||
ssh -o BatchMode=yes -o ConnectTimeout=15 "${EXDEV_SSH}" "$@"
|
ssh -o BatchMode=yes -o ConnectTimeout=15 "${EXDEV_SSH}" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
if ! ssh_cmd "test -f '${DEPLOY_LOG}'" 2>/dev/null; then
|
if ! ssh_cmd "test -f '${DEPLOY_LOG}'" 2>/dev/null; then
|
||||||
log "배포 로그 없음 — SSH로 deploy 스크립트 직접 실행"
|
log "배포 로그 없음 — SSH로 deploy 스크립트 직접 실행"
|
||||||
ssh_cmd "bash /Volumes/ADATA/git/deploy-exAichatbot_agent.sh" || true
|
ssh_cmd "bash /Volumes/ADATA/git/deploy-exAichatbot_agent.sh" || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 대기 시작 시점의 로그 길이 — 이후 새로 찍힌 줄만 성공/실패 판정에 사용
|
# 대기 시작 시점의 로그 길이 — 이후 새로 찍힌 줄만 성공/실패 판정에 사용
|
||||||
LOG_BASE_BYTES="$(ssh_cmd "wc -c < '${DEPLOY_LOG}' 2>/dev/null || echo 0" | tr -d '[:space:]')"
|
LOG_BASE_BYTES="$(ssh_cmd "wc -c < '${DEPLOY_LOG}' 2>/dev/null || echo 0" | tr -d '[:space:]')"
|
||||||
LOG_BASE_BYTES="${LOG_BASE_BYTES:-0}"
|
LOG_BASE_BYTES="${LOG_BASE_BYTES:-0}"
|
||||||
|
|
||||||
log "배포 완료 대기 (최대 ${MAX_WAIT}s) — ${EXDEV_SSH}"
|
log "배포 완료 대기 (최대 ${MAX_WAIT}s) — ${EXDEV_SSH}"
|
||||||
start_ts="$(date +%s)"
|
start_ts="$(date +%s)"
|
||||||
deadline=$((start_ts + MAX_WAIT))
|
deadline=$((start_ts + MAX_WAIT))
|
||||||
|
|
||||||
new_log_tail() {
|
new_log_tail() {
|
||||||
ssh_cmd "tail -c +$((LOG_BASE_BYTES + 1)) '${DEPLOY_LOG}' 2>/dev/null | tail -80" || true
|
ssh_cmd "tail -c +$((LOG_BASE_BYTES + 1)) '${DEPLOY_LOG}' 2>/dev/null | tail -80" || true
|
||||||
}
|
}
|
||||||
|
|
||||||
while (( $(date +%s) < deadline )); do
|
while (( $(date +%s) < deadline )); do
|
||||||
recent="$(new_log_tail)"
|
recent="$(new_log_tail)"
|
||||||
|
|
||||||
# 신규 로그에 실패 마커가 있으면 즉시 종료
|
# 신규 로그에 실패 마커가 있으면 즉시 종료
|
||||||
if echo "${recent}" | grep -qE '\[ERROR\] 배포 실패|ERROR: chatbotApi 컴파일 실패|BUILD FAILED|\[ERROR\] 다른 배포 진행 중'; then
|
if echo "${recent}" | grep -qE '\[ERROR\] 배포 실패|ERROR: chatbotApi 컴파일 실패|BUILD FAILED|\[ERROR\] 다른 배포 진행 중'; then
|
||||||
log "⚠️ 원격 배포 실패 감지 — 최근 로그:"
|
log "⚠️ 원격 배포 실패 감지 — 최근 로그:"
|
||||||
echo "${recent}" | tail -40
|
echo "${recent}" | tail -40
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if curl -sf --max-time 5 "${PUBLIC_URL}" 2>/dev/null | grep -q 'data-chatbot-app'; then
|
if curl -sf --max-time 5 "${PUBLIC_URL}" 2>/dev/null | grep -q 'data-chatbot-app'; then
|
||||||
# UI 살아있어도, 이번 배포가 아직 실패로 끝났으면 성공으로 치지 않음
|
# UI 살아있어도, 이번 배포가 아직 실패로 끝났으면 성공으로 치지 않음
|
||||||
if echo "${recent}" | grep -qE '\[ERROR\] 배포 실패|ERROR: chatbotApi 컴파일 실패|BUILD FAILED'; then
|
if echo "${recent}" | grep -qE '\[ERROR\] 배포 실패|ERROR: chatbotApi 컴파일 실패|BUILD FAILED'; then
|
||||||
log "⚠️ UI는 응답하나 이번 배포 로그에 실패가 있습니다"
|
log "⚠️ UI는 응답하나 이번 배포 로그에 실패가 있습니다"
|
||||||
echo "${recent}" | tail -40
|
echo "${recent}" | tail -40
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if echo "${recent}" | grep -q '배포 완료'; then
|
if echo "${recent}" | grep -q '배포 완료'; then
|
||||||
log "✅ UI 응답 + 배포 완료 로그: ${PUBLIC_URL}"
|
log "✅ UI 응답 + 배포 완료 로그: ${PUBLIC_URL}"
|
||||||
ssh_cmd "tail -8 '${DEPLOY_LOG}'" 2>/dev/null || true
|
ssh_cmd "tail -8 '${DEPLOY_LOG}'" 2>/dev/null || true
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
# start-only 직후 UI 가 이전 빌드로 살아 있을 수 있음 — 신규 성공 로그를 우선
|
# start-only 직후 UI 가 이전 빌드로 살아 있을 수 있음 — 신규 성공 로그를 우선
|
||||||
if echo "${recent}" | grep -q '배포 시작'; then
|
if echo "${recent}" | grep -q '배포 시작'; then
|
||||||
:
|
:
|
||||||
else
|
else
|
||||||
log "✅ UI 응답: ${PUBLIC_URL}"
|
log "✅ UI 응답: ${PUBLIC_URL}"
|
||||||
ssh_cmd "tail -8 '${DEPLOY_LOG}'" 2>/dev/null || true
|
ssh_cmd "tail -8 '${DEPLOY_LOG}'" 2>/dev/null || true
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if echo "${recent}" | grep -q '배포 완료'; then
|
if echo "${recent}" | grep -q '배포 완료'; then
|
||||||
last_line="$(ssh_cmd "tail -1 '${DEPLOY_LOG}'" 2>/dev/null || true)"
|
last_line="$(ssh_cmd "tail -1 '${DEPLOY_LOG}'" 2>/dev/null || true)"
|
||||||
if [[ "${last_line}" == *"배포 완료"* ]] || [[ "${last_line}" == *"락 해제"* ]]; then
|
if [[ "${last_line}" == *"배포 완료"* ]] || [[ "${last_line}" == *"락 해제"* ]]; then
|
||||||
# 성공 후 trap 이 락 해제를 찍을 수 있음
|
# 성공 후 trap 이 락 해제를 찍을 수 있음
|
||||||
if echo "${recent}" | grep -qE '\[ERROR\] 배포 실패|BUILD FAILED'; then
|
if echo "${recent}" | grep -qE '\[ERROR\] 배포 실패|BUILD FAILED'; then
|
||||||
log "⚠️ 배포 실패 로그 감지"
|
log "⚠️ 배포 실패 로그 감지"
|
||||||
echo "${recent}" | tail -40
|
echo "${recent}" | tail -40
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
log "✅ 배포 로그 확인"
|
log "✅ 배포 로그 확인"
|
||||||
ssh_cmd "tail -8 '${DEPLOY_LOG}'" 2>/dev/null || true
|
ssh_cmd "tail -8 '${DEPLOY_LOG}'" 2>/dev/null || true
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
|
|
||||||
log "⚠️ 배포 대기 타임아웃 — 로그 확인: ssh ${EXDEV_SSH} tail -50 ${DEPLOY_LOG}"
|
log "⚠️ 배포 대기 타임아웃 — 로그 확인: ssh ${EXDEV_SSH} tail -50 ${DEPLOY_LOG}"
|
||||||
new_log_tail | tail -40
|
new_log_tail | tail -40
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
Reference in New Issue
Block a user